diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..99a8c7683a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,207 @@ +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +#.env +#.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +.git +modules/server/logs +modules/agent/logs diff --git a/.editorconfig b/.editorconfig index e64c56b7b9..fffe95ac2f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,13 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + # http://editorconfig.org root = true @@ -10,17 +20,35 @@ trim_trailing_whitespace = true insert_final_newline = true max_line_length = 200 -[*.{json, yml}] +[*.{json,yml}] indent_style = space indent_size = 2 +max_line_length = off [*.md] insert_final_newline = false trim_trailing_whitespace = false +max_line_length = off +indent_style = space +indent_size = 2 [*.bat] end_of_line = crlf -[*.vue] +[*.sh] +end_of_line = lf +indent_style = space +indent_size = 2 +max_line_length = off + +[*.{vue,js,ts}] +indent_style = space +indent_size = 2 + +[*.ts] indent_style = space indent_size = 2 + +[*.{java,xml,sql}] +indent_style = space +indent_size = 4 diff --git a/.env b/.env new file mode 100644 index 0000000000..91dc2b61ce --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +JPOM_VERSION=2.11.6 +# Server Token 生产部署请更换 +SERVER_TOKEN=7094f673-2c53-4fc1-82e7-86e528449d97 diff --git a/.gitee/ISSUE_TEMPLATE/bug.yml b/.gitee/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000..16fd364408 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,75 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +name: Bug 反馈 +description: 当你在使用中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个页面存在问题,或者某些地方看起来不对劲。 +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + 感谢对项目的支持与关注。在提出问题之前,请确保你已查看相关使用文档、常见问题、实践案例: + - https://jpom.top + - type: checkboxes + attributes: + label: 这个问题是否已经存在? + options: + - label: 我已经搜索过现有的问题 (https://gitee.com/dromara/Jpom/issues) + required: true + - type: input + id: jpom_version + attributes: + label: Jpom 版本 + description: 你当前正在使用我们软件的哪个版本 + validations: + required: true + - type: input + id: jdk_version + attributes: + label: JDK 版本 + description: 你当前正在使用JDK的哪个版本 + validations: + required: true + - type: input + id: os_version + attributes: + label: 操作系统版本 + description: 你当前正在使用系统类型以及哪个版本 + validations: + required: true + - type: textarea + attributes: + label: 如何复现 + description: 请详细告诉我们如何复现你遇到的问题,并使用反引号```附上它 + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: true + - type: textarea + attributes: + label: 预期结果 + description: 请告诉我们你预期会发生什么。 + validations: + required: true + - type: textarea + attributes: + label: 实际结果 + description: 请告诉我们实际发生了什么。 + validations: + required: true + - type: textarea + attributes: + label: 截图或视频 + description: 如果可以的话,上传任何关于 bug 的截图。 + value: | + [在这里上传图片] diff --git a/.gitee/ISSUE_TEMPLATE/config.yaml b/.gitee/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000000..695b6e882b --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,15 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +blank_issues_enabled: false +contact_links: + - name: Jpom 官方文档 + url: https://jpom.top/ + about: 提供 Jpom 使用指南、教程、基本功能使用、介绍和常见问题解答 diff --git a/.gitee/ISSUE_TEMPLATE/feature.yml b/.gitee/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000000..3ecb1d832d --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,53 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +name: 功能建议 +description: 对本项目提出一个功能建议 +title: "[功能建议]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + 感谢提出功能建议,我们将仔细考虑! + - type: textarea + id: related-problem + attributes: + label: 你的功能建议是否和某个问题相关? + description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。 + validations: + required: false + - type: textarea + id: desired-solution + attributes: + label: 你希望看到什么解决方案? + description: 清晰并简洁地描述你希望发生的事情。 + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: 你考虑过哪些替代方案? + description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。 + validations: + required: false + - type: textarea + id: additional-context + attributes: + label: 你有其他上下文或截图吗? + description: 在此处添加有关功能请求的任何其他上下文或截图。 + validations: + required: false + - type: checkboxes + attributes: + label: 意向参与贡献 + options: + - label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区 + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..1637a6f7b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,5 @@ +### 使用的JDK版本、Jpom版本、操作系统及系统版本 + +### 问题描述(包括截图) + +### 报错信息 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..30da6a8be5 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + + build: + + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [ 18.x ] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - run: cd web-vue && npm i && npm run build + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml new file mode 100644 index 0000000000..1e54b74087 --- /dev/null +++ b/.github/workflows/codecov.yml @@ -0,0 +1,23 @@ +name: codecov2 +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Build with Maven cobertura + run: mvn cobertura:cobertura -Pcobertura + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..65f1d85fb0 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '23 6 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/package-testing.bak-yml b/.github/workflows/package-testing.bak-yml new file mode 100644 index 0000000000..ebb41d1625 --- /dev/null +++ b/.github/workflows/package-testing.bak-yml @@ -0,0 +1,43 @@ +name: Package Testing + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + + build: + + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [ 16.x ] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + #cache: 'npm' + #cache-dependency-path: './web-vue/package-lock.json' + #- run: npm ci + # working-directory: './web-vue' + #- run: npm run build + # working-directory: './web-vue' + - run: cd web-vue && npm i && npm run build + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package + #file: ./zhuzhu-project/target/site/jacoco/jacoco.xml +# run: | +# docker buildx create --use +# docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:2.8.16 -f ./modules/server/DockerfileRelease --push . +# docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push . diff --git a/.gitignore b/.gitignore index c94e2fa4b9..5b8bb87679 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,11 @@ target/ venv/ node_modules/ +# Jrebel +rebel.xml + +# +apidoc/ # dependencies @@ -15,7 +20,7 @@ _roadhog-api-doc # production dist/ -/.vscode +#/.vscode # misc .DS_Store @@ -26,8 +31,10 @@ yarn-error.log .idea yarn.lock package-lock.json +pnpm-lock.yaml *bak -.vscode +pom.xml.versionsBackup +#.vscode # visual studio code .history @@ -43,3 +50,6 @@ functions/* screenshot .firebase .eslintcache + +web-vue/stats.html +.baidubce.token diff --git a/.workflow/MasterPipeline.yml b/.workflow/MasterPipeline.yml index c008d7fc8b..1e3070d51e 100644 --- a/.workflow/MasterPipeline.yml +++ b/.workflow/MasterPipeline.yml @@ -1,56 +1,70 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + version: '1.0' name: master-pipeline displayName: MasterPipeline +triggers: + trigger: manual + push: + branches: + precise: + - master + - dev stages: - - stage: - name: compile + - name: compile displayName: 编译 + strategy: naturally + trigger: auto steps: - step: build@maven name: build_maven displayName: Maven 构建 - # 支持6、7、8、9、10、11六个版本 jdkVersion: 8 - # 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本 mavenVersion: 3.6.3 - # 构建命令 commands: - - mvn -B clean package -Dmaven.test.skip=true - # 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除 + - 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 .. + - mvn -B -e clean package -Dmaven.test.skip=true -Dmaven.compile.fork=true -s script/settings.xml artifacts: - # 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址 - - name: BUILD_ARTIFACT - # 构建产物获取路径,是指代码编译完毕之后构建物的所在路径,如通常jar包在target目录下。当前目录为代码库根目录 + - name: all_zip + path: + - modules/server/target/server-2.11.6-release.zip + - modules/agent/target/agent-2.11.6-release.zip + - name: server_zip + path: + - modules/server/target/server-2.11.6-release.zip + - name: agent_zip path: - - modules/server/target/*.zip - - modules/agent/target/*.zip + - modules/agent/target/agent-2.11.6-release.zip + settings: [] + strategy: + retry: '0' + - name: stage-0aa9dee2 + displayName: 打包 + strategy: naturally + trigger: auto + executor: [] + steps: - step: publish@general_artifacts name: publish_general_artifacts - displayName: 上传制品 - # 上游构建任务定义的产物名,默认BUILD_ARTIFACT - dependArtifact: BUILD_ARTIFACT - # 构建产物制品库,默认default,系统默认创建 - artifactRepository: default - # 上传到制品库时的制品命名,默认build - artifactName: jpom-server - dependsOn: build_maven -# - stage: -# name: release -# displayName: 发布 -# steps: -# - step: publish@release_artifacts -# name: publish_release_artifacts -# displayName: '发布' -# # 上游上传制品任务的产出 -# dependArtifact: jpom-server -# # 发行版制品库,默认release,系统默认创建 -# artifactRepository: release -# # 发布制品版本号 -# version: '1.0.0.0' -# # 是否开启版本号自增,默认开启 -# autoIncrement: true -triggers: - push: - branches: - include: - - master + displayName: 合并打包 + dependArtifact: all_zip + artifactName: jpom-2.11.6 + strategy: + retry: '0' +strategy: + blocking: true +permissions: + - role: admin + members: [] diff --git a/.workflow/docker-hub.yml b/.workflow/docker-hub.yml new file mode 100644 index 0000000000..db05b17b0b --- /dev/null +++ b/.workflow/docker-hub.yml @@ -0,0 +1,46 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +version: '1.0' +name: pipeline-20220528 +displayName: docker-hub +triggers: + trigger: manual + push: + branches: + precise: + - dev + tags: + prefix: + - v +stages: + - name: stage-1b917201 + displayName: 容器发布 + strategy: naturally + trigger: auto + executor: [] + steps: + - step: execute@docker + name: execute_by_docker + displayName: 基于镜像的脚本执行 + image: hub.docker.com/bash:latest + command: + - echo 'success!' + - '# 服务端' + - docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:2.8.22 -f ./modules/server/DockerfileRelease --push . + - '#' + - docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push . + strategy: + retry: '0' +strategy: + blocking: true +permissions: + - role: admin + members: [] diff --git a/.workflow/pipeline-opensca.yml b/.workflow/pipeline-opensca.yml new file mode 100644 index 0000000000..84f86e1e1b --- /dev/null +++ b/.workflow/pipeline-opensca.yml @@ -0,0 +1,33 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +version: '1.0' +name: pipeline-20221101 +displayName: pipeline-20221101 +triggers: + trigger: auto + push: + branches: + prefix: + - '' +stages: + - name: stage-98f2727d + displayName: 检测 + strategy: naturally + trigger: auto + executor: [] + steps: + - step: sc@opensca + name: open_sca + displayName: OpenSCA 开源组件检测 + detectPath: ./ + notify: [] + strategy: + retry: '0' diff --git a/CHANGELOG-BETA.md b/CHANGELOG-BETA.md new file mode 100644 index 0000000000..f04c73a588 --- /dev/null +++ b/CHANGELOG-BETA.md @@ -0,0 +1,590 @@ +# 🚀 版本日志 + +## 2.11.6.6-beta (2024-06-19) + +### 🐣 新增功能 + +1. 【all】新增 国际化语言支持:繁體中文(中國香港)、繁體中文(中國臺灣) + +### ⚠️ 注意 + +- 前端国际化翻译程度:98% +- 后端已翻译语言可以度:95%(部分异步执行日志等目前未支持) + +------ + +## 2.11.6.5-beta (2024-06-17) + +### 🐣 新增功能 + +1. 【server】新增 服务端脚本、SSH 脚本支持引用全局脚本库(`G@("xx")` xx 为脚本标记) +2. 【agent】新增 节点脚本支持引用全局脚本库(`G@("xx")` xx 为脚本标记) + +### 🐞 解决BUG、优化功能 + +1. 【agent】修复 不同工作空间下同一个机器节点相同的项目ID的项目数据被覆盖(感谢@小朱) +2. 【agent】优化 DSL 项目支持引用脚本库中的脚本(G@xxxx)xxxx 为脚本标记 +3. 【all】优化 新增系统语言配置 `jpom.system.lang` +4. 【server】优化 前端紧凑模式在浅色模式下也生效 + +### ⚠️ 注意 + +- 前端国际化翻译程度:90% +- 后端已翻译语言可以度:80%(部分异步执行日志等目前未支持) + +后端日志国际化需要新增或者修改 `jpom.system.lang` 配置项 + +------ + +## 2.11.6.4-beta (2024-06-14) + +### 🐣 新增功能 + +1. 【all】新增 支持 i18n 语言国际化(zh-CN、en-US) + +### ⚠️ 注意 + +#### 翻译进度 + +- 前端国际化翻译程度:80% +- 后端国际化翻译程度:90% + +#### 使用程度 + +- 前端已翻译语言可以度:100% +- 后端已翻译语言可以度:60%(后端日志、异步执行日志等目前未支持) + +### 🤝致谢 + +感谢 [@a20070322](https://gitee.com/a20070322) / Controllers 大佬贡献 Jpom 前端 i18n 工具。 + +------ + +## 2.11.6.3-beta (2024-06-14) + +### 🐣 新增功能 + +1. 【agent】新增 全局脚本库(DSL 项目可引用) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 gogs 仓库令牌导入异常(感谢@张飞鸿) +2. 【server】修复 自定义仓库令牌导入后页面异常(感谢@张飞鸿) + +------ + +## 2.11.6.2-beta (2024-06-06) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 没有端口的容器重建页面异常(感谢@冰淇淋还是冰激凌) +2. 【server】修复 工作空间菜单配置错误(感谢@Again... .) +3. 【server】优化 ssh 管理独立 tab 页面使用默认的字符串排序 +4. 【server】修复 服务端脚本无法执行、参数值描述不对应(感谢@冰淇淋还是冰激凌) + +------ + +## 2.11.6.1-beta (2024-06-04) + +### 🐣 新增功能 + +1. 【agent】新增 项目文件支持快捷复制到当前文件夹 +2. 【agent】新增 项目文件夹支持快捷压缩(感谢[@yiziyu](https://gitee.com/yiziyu) [Gitee issues I9737L](https://gitee.com/dromara/Jpom/issues/I9737L) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 部分参数、环境变量配置交互优化取消文本输入框采用标签模式(感谢@湘江夜色) +2. 【server】修复 部分页面中文描述未正常显示 +3. 【server】优化 文件发布支持选择脚本模板(感谢[@linCodeTest](https://gitee.com/linWorld) [Gitee issues I9P0EU](https://gitee.com/dromara/Jpom/issues/I9P0EU) ) +4. 【server】优化 升级 postgresql 版本(感谢@ʟᴊx💎💎) +5. 【server】修复 机器相关授权配置文件后缀输入框未正常显示(@感谢@ccx2480) + +------ + +## 2.11.5.2-beta (2024-05-30) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 自动续签采用无感模式(感谢@湘江夜色) +2. 【server】优化 容器构建执行配置自定义 host 参数(感谢@冰淇淋还是冰激凌) +3. 【all】升级 tomcat、yaml 版本(感谢@佳驰) +4. 【all】升级 bcprov-jdk18on 版本 + +------ + +## 2.11.5.1-beta (2024-04-30) + +### 🐣 新增功能 + +1. 【agent】新增 项目支持配置禁止扫描目录避免大目录页面超时(感谢@我) + +------ + +## 2.11.5.0-beta (2024-04-29) + +### 🐣 新增功能 + +1. 【all】新增 自由脚本方便调试机器节点 + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 资产管理 SSH 配置禁用命令无法回显(感谢@zhangw) +2. 【server】修复 资产管理 SSH 未配置授权目录时 NPE (感谢[@Anley](https://gitee.com/MrAnley) [Gitee issues I9J17G](https://gitee.com/dromara/Jpom/issues/I9J17G) ) +3. 【agent】优化 监控机器网络流程支持配置排除网卡或者仅统计对应的网卡 +4. 【server】修复 退出登录时页面会提示需要登录相关信息 +5. 【server】优化 页面检测新版本判断是否加入 beta +6. 【agent】优化 添加数据记录修改人(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I9JSY7](https://gitee.com/dromara/Jpom/issues/I9JSY7) ) +7. 【server】优化 插件端注册到服务端,网络测试支持 ping + telnet (感谢@泊凉青川) + +------ + +## 2.11.4.2-beta (2024-04-22) + +### 🐣 新增功能 + +1. 【server】新增 发布系统公告 + +### 🐞 解决BUG、优化功能 + +1. 【server】升级 前端组件版本 +2. 【all】优化 管理脚本删除 `-XX:-UseBiasedLocking` 使其能在高版本 jdk 运行 +3. 【server】修复 构建列表卡片模式按钮文字错乱 +4. 【server】修复 项目列表和逻辑节点卡片视图冲突 +5. 【server】修复 docker管理新增docker选择证书界面权重异常 (感谢[@伤感的风铃草](https://gitee.com/bwy-flc) [Gitee issues I9GYVA](https://gitee.com/dromara/Jpom/issues/I9GYVA) ) +6. 【server】修复 系统管理中用户管理中登录日志无法筛选 +7. 【server】优化 用户登录记录操作日志(保证操作监控能记录) +8. 【server】修复 系统管理中用户登录日志无法分页 +9. 【server】优化 Oauth2 支持配置创建账号配置权限组 +10. 【server】修复 文件发布权限为执行权限、文件发布记录删除无记录日志 (感谢@蓝枫) + +------ + +## 2.11.4.1-beta (2024-04-12) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 构建相关页面无法正常打开 +2. 【server】优化 Git 仓库地址不正确相关提示更准确(感谢@易自玉) + +------ + +## 2.11.4.0-beta (2024-04-12) + +### 🐣 新增功能 + +1. 【server】新增 Oauth2 新增【飞书账号】、【自建 Gitlab】登录(感谢[@鸡皮蒜毛与鸡毛蒜皮](https://gitee.com/cuia) [Gitee issues I9ELGS](https://gitee.com/dromara/Jpom/issues/I9ELGS) ) +2. 【server】新增 Oauth2 新增企业微信登录 + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 oauth2 第三方平台登录解析用户名将依次尝试:平台用户名、邮箱、uuid +2. 【server】修复 无法查询到分组信息(页面下拉框)(感谢[@Robot](https://gitee.com/robot1937) [Gitee issues I9FN9U](https://gitee.com/dromara/Jpom/issues/I9FN9U) ) +3. 【all】升级 hutool 版本 +4. 【server】修复 修复孤独数据描述错别字(感谢[@cuiyongsheng](https://github.com/Cuiys1458) [Github issues 77](https://github.com/dromara/Jpom/issues/77) ) +5. 【server】修复 前端地址栏输入二级路径 404 页面卡死问题 + +------ + +## 2.11.3.2-beta (2024-04-07) + +### 🐣 新增功能 + +1. 【server】新增 Oauth2 新增钉钉扫码登录 + +### 🐞 解决BUG、优化功能 + +1. 【agent】优化 DSL 项目支持配置在特定目录执行脚本(run.execPath) +2. 【agent】优化 管理脚本 -Xss 默认值修改为 512k(感谢@Again... ) +3. 【server】优化 管理脚本 -Xss 默认值修改为 1024k(感谢@Again... ) +4. 【server】优化 声明使用开源软件列表、增加本软件开源协议声明 + +------ + +## 2.11.3.1-beta (2024-04-02) + +### 🐣 新增功能 + +1. 【server】新增 数据库支持 *mariadb* + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 无法查询到分组信息(页面下拉框)(感谢@猫猫向钱跑) +2. 【server】修复 【项目文件管理远程下载】、【镜像创建容器】确认按钮无法使用(感谢@猫猫向钱跑) +3. 【server】修改 资产管理机器管理删除按钮无法正常使用(感谢@🇩) +4. 【server】修复 SSH 面板文件管理无法正常切换(感谢@勤思·) +5. 【server】优化 部分页面在火狐浏览器无法正常打开(感谢[@sparkarvin](https://gitee.com/arvinlovegood_admin) [Gitee issues I96IOA](https://gitee.com/dromara/Jpom/issues/I96IOA) ) + (感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 221](https://gitee.com/dromara/Jpom/pulls/221) ) + +------ + +## 2.11.3.0-beta (2024-03-21) + +### 🐣 新增功能 + +1. 【server】新增 数据库支持 *postgresql* (感谢[@王先生](https://gitee.com/whz_gmg1))[Gitee Pr 223](https://gitee.com/dromara/Jpom/pulls/223) + +### 🐞 解决BUG、优化功能 + +1. 【all】优化 新增 `jpom.system.command-use-sudo` 配置属性控制是否使用 sudo 执行部分系统命令 +2. 【server】优化 前端页面 keep-alive 可能导致的内存泄漏问题(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I9510M](https://gitee.com/dromara/Jpom/issues/I9510M) ) +3. 【server】修复 部分弹窗不生效问题(感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 215](https://gitee.com/dromara/Jpom/pulls/215) ) +4. 【server】优化 前端 ES lint 配置规范前端代码(感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 214](https://gitee.com/dromara/Jpom/pulls/214) / [Gitee Pr 215](https://gitee.com/dromara/Jpom/pulls/215) / [Gitee Pr 217](https://gitee.com/dromara/Jpom/pulls/217) ) +5. 【server】修复 docker 控制台网络选项卡页面空白(感谢@破冰) +6. 【server】修复 节点历史监控统计图表时间查询不生效(感谢@九問) +7. 【server】优化 SSH 脚本触发器支持传入参数当环境变量(感谢@小朱) +8. 【server】修复 h2迁移其它数据库时部分数据丢失(感谢[@王先生](https://gitee.com/whz_gmg1))[Gitee issues I9977K](https://gitee.com/dromara/Jpom/issues/I9977K) +9. 【server】优化 逐步引入新版表格(构建、项目、节点、资产机器)(感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 218](https://gitee.com/dromara/Jpom/pulls/218) / [Gitee Pr 220](https://gitee.com/dromara/Jpom/pulls/220) / [Gitee Pr 222](https://gitee.com/dromara/Jpom/pulls/222) ) +10. 【server】优化 工作空间概括构建日志支持快速查看详情(感谢@Roger.cao) + +------ + +## 2.11.2.3 (2024-02-29) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 系统文件占用空间统计周期调整为每天2次(感谢[@singlethread](https://gitee.com/zengwei_joni) [Gitee issues I9302U](https://gitee.com/dromara/Jpom/issues/I9302U) ) +2. 【server】优化 支持配置前端所有参数编码来规避部分安全规则检查(感谢[@zhaozxc2010](https://gitee.com/zhaozxc2010) [Gitee issues I8Z1VJ](https://gitee.com/dromara/Jpom/issues/I8Z1VJ) ) +3. 【server】优化 上传文件空文件提示文件路径(感谢[@SchuckBate](https://gitee.com/skBate) [Gitee issues I93FI6](https://gitee.com/dromara/Jpom/issues/I93FI6) ) +4. 【server】优化 监听日志文件消息发送失败后自动移除会话(感谢[@singlethread](https://gitee.com/zengwei_joni) [Gitee issues I93ZFX](https://gitee.com/dromara/Jpom/issues/I93ZFX) ) +5. 【server】优化 容器构建产物为文件时保存路径层级错误(感谢[@vfhky](https://github.com/vfhky) [Github Pr 71](https://github.com/dromara/Jpom/pull/71) ) + +------ + +## 2.11.2.2 (2024-02-22) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 升级 docker-java、jgit 版本 +2. 【all】优化 升级 commons-compress 版本 +3. 【agent】优化 升级 oshi 版本 +4. 【server】优化 新增配置节点 websocket 通讯消息大小限制(jpom.node.web-socket-message-size-limit)(感谢@长弘) + +------ + +## 2.11.2.1 (2024-02-19) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 构建代码未变动流程打断触发器未传入原因(statusMsg)(感谢@烛孩) +2. 【server】修复 项目控制台日志删除弹窗未能正常关闭(感谢@%) +3. 【server】修复 脚本日志时间筛选不生效(感谢[@zhaozxc2010](https://gitee.com/zhaozxc2010) [Gitee issues I8ZNKL](https://gitee.com/dromara/Jpom/issues/I8ZNKL) ) +4. 【server】优化 页面左侧菜单固定悬浮不跟随屏幕滚动条滚动(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZOOB](https://gitee.com/dromara/Jpom/issues/I8ZOOB) / [Gitee Pr 201](https://gitee.com/dromara/Jpom/pulls/201) ) +5. 【server】优化 新增机器节点提示未选择协议(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZDZT](https://gitee.com/dromara/Jpom/issues/I8ZDZT) / [Gitee Pr 202](https://gitee.com/dromara/Jpom/pulls/202) ) +6. 【server】修复 SSH 资产硬盘信息显示错误(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZY7K](https://gitee.com/dromara/Jpom/issues/I8ZY7K) ) +7. 【server】优化 表格搜索区域小屏幕适配 (感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZY0B](https://gitee.com/dromara/Jpom/issues/I8ZY0B) ) +8. 【server】优化 SSH 文件管理树操作优化 (感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I9054L](https://gitee.com/dromara/Jpom/issues/I9054L) / [Gitee issues I5DMKG](https://gitee.com/dromara/Jpom/issues/I5DMKG) ) +9. 【server】优化 整体页面顶部菜单吸顶效果(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I907Y8](https://gitee.com/dromara/Jpom/issues/I907Y8) ) +10. 【server】优化 资产监控线程池独立管理(感谢[@singlethread](https://gitee.com/zengwei_joni) [Gitee issues I918AB](https://gitee.com/dromara/Jpom/issues/I918AB) ) +11. 【server】优化 构建回滚使用构建独立线程池 +12. 【all】优化 升级 hutool 版本(主要解决版本号排序异常)(感谢 [@Tom Xin](https://gitee.com/meiMingle) [Gitee issues I8Z3TI](https://gitee.com/dromara/Jpom/issues/I8Z3TI) / [Hutool issues I8Z3VE](https://gitee.com/dromara/hutool/issues/I8Z3VE)) +13. 【all】优化 升级 fastjson 版本 +14. 【server】优化 页面整体滚动条兼容高版本浏览器(感谢@Controllers) + +### 🤝致谢 + +感谢 [@a20070322](https://gitee.com/a20070322) / Controllers 大佬帮助优化 Jpom 前端部分老大难问题。 + +------ + +## 2.11.2.0 (2024-01-22) + +### 🐞 解决BUG、优化功能 + +1. 【agent】修复 修改项目日志路径如果文件夹不存在报错(感谢@长弘) +2. 【server】修复 节点机器日志无法下载(感谢@Again...) +3. 【all】升级 hutool 版本 +4. 【agent】升级 oshi 版本 +5. 【server】升级 mwiede、apache-sshd 版本(感谢@*斌) +6. 【server】优化 项目列表 file 类型正常排序(不再排序到最后)(感谢[@pal865](https://gitee.com/pal865) [Gitee issues I8XU32](https://gitee.com/dromara/Jpom/issues/I8XU32) ) +7. 【all】修复 windows 环境保存配置并重启失败(感谢[@Robot](https://gitee.com/robot1937) [Gitee issues I8Y01T](https://gitee.com/dromara/Jpom/issues/I8Y01T) ) +8. 【server】修复 新版本页面部分分页切换失效(构建详情、资产机器、逻辑节点)(感谢@zac) + +------ + +## 2.11.1.5 (2024-01-18) + +### 🐞 解决BUG、优化功能 + +1. 【agent】修复 插件端非默认工作空间项目重启后变为孤独数据(感谢@ccx2480) +2. 【server】修复 新增节点分发项目数据为孤独数据 + +------ + +## 2.11.1.3/4-beta (2024-01-17) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 修复页面打包页面底部白屏 + +------ + +## 2.11.1.2-beta (2024-01-17) + +### 🐣 新增功能 + +1. 【server】新增 触发器调用次数统计、触发器统一管理 +2. 【server】新增 本地构建命令执行支持配置多线程方式(多线程接收输出流,避免极端情况卡死) + +### 🐞 解决BUG、优化功能 + +1. 【all】优化 机器状态新增:资源监控异常(资源监控异常不影响功能使用) +2. 【server】优化 取消登录页动态背景图 +3. 【server】修复 节点分发文件中心、静态文件后文件自动被删除(感谢@九問) +4. 【server】优化 容器构建支持配置容器资源(HostConfig)(感谢@珂儿) + +------ + +## 2.11.1.1-beta (2024-01-16) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 静态文件名太短(100个字符)(感谢@*斌) +2. 【server】修复 还原数据库弹窗内容提示为空(感谢@伤感的风铃草🌿) +3. 【server】优化 echarts 支持跟随深色模式 +4. 【server】修复 编辑节点分发服务端脚本弹窗被挡住(感谢@🇩) +5. 【server】优化 前端打包(缩减首屏加载时间)(感谢@曾梦想仗剑走天涯) + +------ + +## 2.11.1.0-beta (2024-01-15) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 docker TLS 证书无法查看全部、证书无法编辑(新版遗漏) +2. 【server】优化 docker 资产监控支持自定义配置 cron `jpom.assets.docker.monitor-cron` +3. 【server】修复 容器终端、容器日志无法正常使用 +4. 【server】修复 新版本页面多处无法正常使用相关问题(优化部分提示说明) + +------ + +## 2.11.0.13-beta (2024-01-12) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 部分 mysql 存储创建索引异常 +2. 【agent】优化 插件端自由脚本(分发文件脚本)大小限制调整为 5M(感谢@九問) +3. 【server】修复 新版本 UI 部分错别字、日志阅读查看无法正常使用等 +4. 【server】优化 编辑器样式、DSL 配置样式 + +------ + +## 2.11.0.12-beta (2024-01-12) + +### 🐣 新增功能 + +1. 【server】新增 资产总览统计 + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 mysql 存储使用游标查询报错(不使用游标)(感谢@🇩) +2. 【server】修复 新版 UI 下拉框搜索不生效 +3. 【server】优化 **添加**关键词替换为**新增** +4. 【server】优化 部分页面无数据提示 +5. 【server】修复 节点分发构建产物选择构建历史为匹配对应构建 +6. 【server】修复 部分操作构建环境变量丢失(保存并构建、后台构建、直接构建) + +------ + +## 2.11.0.11-beta (2024-01-11) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 编辑器无法加载样式 +2. 【server】优化 本地 git 联动严格执行开关 +3. 【server】修复 打包后终端弹窗样式错乱(感谢@🇩) +4. 【server】修复 切换账户、退出登录菜单未刷新问题(感谢@ccx2480) +5. 【server】修复 登录账户未跳转配置的第一个工作空间(未遵循自定义配置) + +------ + +## 2.11.0.10-beta (2024-01-10) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 系统Git拉取代码时,强制云端最新代码覆盖本地代码 +2. 【server】修复 升级页面更新日志样式错乱 +3. 【server】修复 切换账户后用户信息未自动刷新 +4. 【agent】修复 **部分操作引起项目节点分发属性丢失问题** +5. 【server】修复 无法新增项目(权限判断异常)(感谢@群友) +6. 【agent】修复 插件端环境变量值获取异常 +7. 【agent】优化 插件端 java 项目启动支持读取环境变量 +8. 【server】修复 项目列表选择错乱、批量操作无法正常使用(感谢@🇩) + +------ + +## 2.11.0.9-beta (2024-01-10) + +### 🐣 新增功能 + +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 +6. 【server】优化 节点项目、节点脚本操作鉴权(需要服务端缓存中有对应的数据) + +------ + +## 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 模式代替) +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/CHANGELOG.md b/CHANGELOG.md index cb8c373e05..028902c705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,405 +1,1669 @@ -# 版本日志 - -# 2.8.0 (beta) - -### 新增功能 - -1. 【server】新增工作空间概念(取代角色相关)【系统将自动创建默认工作空间、默认工作空间是不能删除】 -2. 【server】用户新增可以配置管理员功能【管理员可以管理系统中的账号、系统管理等功能(除升级系统、导入数据外)】 -3. 【server】新增超级管理员(第一次初始化系统等账号为超级管理员),超级可以拥有整个系统权限不受任何限制 -4. 【server】列表数据都新增分页、搜索、排序功能(搜索字段、排序字段正在完善补充中) -5. 【server】新增通过命令行重置 IP 白名单配置参数 `--rest:ip_config` -6. 【server】新增通过命令行重置超级管理员参数 `--rest:super_user_pwd` -7. 【server】新增通过命令行重新加载数据库初始化操作参数 `--rest:load_init_db` -8. 【server】构建新增`本地命令`发布方式 用户在服务端执行相关命令进行发布操作 -9. 【server】发布命令(SSH发布命令、本地命令)支持变量替换:`#{BUILD_ID}`、`#{BUILD_NAME}`、`#{BUILD_RESULT_FILE}`、`#{BUILD_NUMBER_ID}` -10. 【server】新增自动备份全量数据配置 `db.autoBackupIntervalDay` 默认一天备份一次,执行备份时间 凌晨0点或者中午12点 -11. 【agent】项目的 webhook 新增项目启动成功后通知,并且参数新增 `type` 指包括:`beforeStop`,`start`,`stop`,`beforeRestart` -12. 【agent】项目新增自启动配置项,在 agent 启动时候检查对应项目是否启动,未启动执行启动逻辑 -13. 【server】构建新增 webhook,实时通知构建进度 -14. 【server】节点分发新增分发间隔时间配置 - -### 解决BUG、优化功能 - -1. 【server】用户账号、节点、SSH、监控、节点分发等数据由 JSON 文件转存 h2 -2. 【server】取消节点、构建分组字段 -3. 【server】取消角色概念(新增工作空间取代) -4. 【server】操作监控数据由于数据字段不兼容将不自动升级需要用户重新配置 -5. 【server】系统参数相关配置都由 JSON 转存 h2(邮箱配置、IP白名单、节点分发白名单、节点升级) -6. 【server】关联节点项目支持绑定单个节点不同项目 -7. 【server】构建触发器新增跟随创建用户走,历史 url 将失效,需要重新生成 -8. 【server】仓库`假删`功能下线,已经删除的仓库将恢复正常(假删功能后续重新开发) -9. 【agent】项目数据新增工作空间字段、取消分组字段 -10. 【server】节点 ID 取消用户自定义采用系统生成 -11. 【server】优化节点弹窗和菜单折叠页面布局 -12. 【server】编辑节点、SSH、邮箱配置不回显密码字段 -13. 【server】优化 SSH 终端不能自动换行问题 - -> 注意: -> -> 【特别说明】:分组字段将失效,目前所有数据在升级后都将默认跟随`默认工作空间`。 -> -> 1: 升级该版本会自动将原 JSON 文件数据转存到 h2 中,如果转存成功旧数据文件将自动移动到数据目录中的 `backup_old_data` 文件夹中 -> -> 2: 升级过程请注意控制台日志是否出现异常 -> -> 3: 操作监控数据由于数据字段不兼容将不自动升级需要用户重新配置 -> -> 4: 监控报警记录、构建记录、操作记录由于字段兼容问题存在部分字段为空的情况 -> -> 5:非超级管理员用户会出现由于未分配工作空间不能正常登录或者不能使用的情况,需要分配工作空间才能登录 -> -> 6: 用户绑定工作空间后,用户在对应工作空间下可以创建、修改、删除对应的数据(包括但不限于管理节点) -> -> 7: 此次升级启动耗时可能需要2分钟以上(耗时根据数据量来决定),请耐心等待和观察控制台日志输出 -> -> 8: 一个节点建议不要被多个服务端绑定(可能出现数据工作空间错乱情况) +# 🚀 版本日志 + +## 2.11.7-release (2024-06-19) + +### 🐣 新增功能 + +1. 【agent】新增 项目文件支持快捷复制到当前文件夹 +2. 【agent】新增 项目文件夹支持快捷压缩(感谢[@yiziyu](https://gitee.com/yiziyu) [Gitee issues I9737L](https://gitee.com/dromara/Jpom/issues/I9737L) ) +3. 【agent】新增 全局脚本库(DSL 项目可引用) +4. 【all】新增 支持 i18n 语言国际化:简体中文、英语、繁體中文(中國香港)、繁體中文(中國臺灣) +5. 【server】新增 服务端脚本、SSH 脚本支持引用全局脚本库(`G@("xx")` xx 为脚本标记) +6. 【agent】新增 节点脚本支持引用全局脚本库(`G@("xx")` xx 为脚本标记) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 部分参数、环境变量配置交互优化取消文本输入框采用标签模式(感谢@湘江夜色) +2. 【server】修复 部分页面中文描述未正常显示 +3. 【server】优化 文件发布支持选择脚本模板(感谢[@linCodeTest](https://gitee.com/linWorld) [Gitee issues I9P0EU](https://gitee.com/dromara/Jpom/issues/I9P0EU) ) +4. 【server】优化 升级 postgresql 版本(感谢@ʟᴊx💎💎) +5. 【server】修复 机器相关授权配置文件后缀输入框未正常显示(@感谢@ccx2480) +6. 【server】修复 没有端口的容器重建页面异常(感谢@冰淇淋还是冰激凌) +7. 【server】优化 ssh 管理独立 tab 页面使用默认的字符串排序 +8. 【server】修复 服务端脚本无法执行、参数值描述不对应(感谢@冰淇淋还是冰激凌) +9. 【server】修复 gogs 仓库令牌导入异常(感谢@张飞鸿) +10. 【server】修复 自定义仓库令牌导入后页面异常(感谢@张飞鸿) +11. 【agent】修复 不同工作空间下同一个机器节点相同的项目ID的项目数据被覆盖(感谢@小朱) +12. 【agent】优化 DSL 项目支持引用脚本库中的脚本(G@xxxx)xxxx 为脚本标记 +13. 【all】优化 新增系统语言配置 `jpom.system.lang` +14. 【server】优化 前端紧凑模式在浅色模式下也生效 + +### ⚠️ 注意 + +- 前端国际化翻译程度:98% +- 后端已翻译语言可以度:95%(部分异步执行日志等目前未支持) + +后端日志国际化需要新增或者修改 `jpom.system.lang` 配置项 + +### 🤝致谢 + +感谢 [@a20070322](https://gitee.com/a20070322) / Controllers 大佬贡献 Jpom 前端 i18n 工具。 + ------ -# 2.7.3 +## 2.11.6-release (2024-05-30) -### 新增功能 - -1. 【server】新增自定义系统网页标题配置`jpom.name` -2. 【server】新增自定义系统网页 logo 配置`jpom.logoFile` -3. 【server】新增自定义系统登录页面标题配置`jpom.loginTitle` -4. 【server】新增自定义系统 logo 文字标题配置`jpom.subTitle` -5. 新增在线下载最新版本更新包功能(在线检测最新版本) -6. 【server】新增菜单`系统管理-数据库备份`,支持 Jpom 使用的 H2 数据库备份、还原 - -### 解决BUG、优化功能 - -1. 【server】修护构建产物为匹配符无法正常发布问题(感谢@Kay) -2. 【server】修护在线升级页面在二级路径下无法使用的问题 (感谢@hu丶向...🤡) -3. 【server】修护构建执行命令阻塞问题(感谢@小猿同学) -4. 【server】修护限制 IP 访问和插件端授权信息不正确状态码冲突(感谢@小龙、@大灰灰) -5. 取消 tools.jar 依赖 -6. 【server】优化初始化数据库流程,避免多次执行相同修改,节省启动时间 -7. 【fix】修护项目副本集乱码(感谢@ʟᴊx) -8. 【server】添加在线升级完成后的回调提示 -9. 【server】ssh安装节点按钮动态显示 -10. 【server】修护构建信息中脚本过长无法构建的bug(感谢@Dream) -11. 在网页的编辑器中修改配置文件时兼容tab键(感谢@Dream) - -> 取消 tools.jar 依赖后,Java 项目状态监控使用 `jps` 命令实现 - ------- - -# 2.7.2 (fix) - -### 新增功能 - -### 解决BUG、优化功能 - -1. 【agent】解决 nginx 编辑配置文件 url 编码问题 -3. 【server】新增配置构建命令支持不检测删除命令 `build.checkDeleteCommand` (感谢@Dream) - ------- - -# 2.7.1 (fix) - -### 新增功能 - -### 解决BUG、优化功能 - -1. 解决插件端请求参数 url 编码无法解析问题(感谢@知识就是力量) -2. 【agent】项目文件夹为空不再提示错误信息 -3. 【server】fix 编辑构建选择 ssh 发布无法保存 (感谢 @Peision [Gitee issues I4CQWA](https://gitee.com/dromara/Jpom/issues/I4CQWA) ) -4. 【server】fix ssh 终端未配置禁用命令不能输入空格问题 - ------- - -# 2.7.0 (beta) - -### 新增功能 - -1. **【server】构建中的仓库独立管理** -2. **【server】构建信息存储方式调整为 h2 数据库,不再存储到 json 文件中** -3. **【server】构建触发器地址变更** -4. 【agent】新增文件管理中允许编辑的文件后缀,以及对应后缀的文件编码 -5. 项目文件管理中新增编辑按钮,支持编辑文本文件( 新版本 UI 同步新增该功能) -6. 程序启动输出默认 IP 地址和当前运行端口信息 -7. bat 管理命令(windows)启动后输出日志文件,方便排查当前启动情况 -8. 【server】上传文件到插件端(节点)超时配置独立,采用 server 端全局配置,配置参数 `node.uploadFileTimeOut` - (感谢 @LW 根据 Gitee [issues I3O8YE](https://gitee.com/dromara/Jpom/issues/I3O8YE) ) -9. 【server】角色新增添加权限配置 (感谢@misaka [Gitee pr](https://gitee.com/dromara/Jpom/pulls/141) ) -10. 【server】节点升级上传新包成功后删除历史包 -11. 【server】新版本 UI 菜单系统管理、节点升级只有系统管理员可见 -12. 【server】新版本 UI 脚本模板同步添加执行参数(感谢@轻描淡写 [Gitee issues I43G4B](https://gitee.com/dromara/Jpom/issues/I43G4B) ) -13. 【server】新版本 UI 同步添加 common.js -14. 【agent】项目文件管理新增下载远程文件功能 -15. 【agent】节点首页监控新增实际使用内存占比(linux系统) (感谢@大灰灰) -16. 【server】ssh 新增操作记录(方便查看执行历史回溯操作) -17. 【server】新增 h2 控制台配置属性,基于 SpringBoot,配置参数`spring.h2.console.enabled` -18. 【server】节点分发支持下载远程文件 (感谢@落泪归枫 [Gitee issues I1LM27](https://gitee.com/dromara/Jpom/issues/I1LM27) ) -19. 【server】节点分发支持 file 类型项目 -20. 【agent】项目新增配置日志文件输出到指定目录 -21. 【server】构建产物目录支持通配符`AntPathMatcher`模式 (感谢@saysay [Gitee issues I455FM](https://gitee.com/dromara/Jpom/issues/I455FM) ) -22. 【server】新增 h2 数据库缓存大小配置 [CACHE_SIZE](http://www.h2database.com/html/features.html#cache_settings) `db.cacheSize -23. 【server】构建触发器新增延迟执行参数(感谢@Steve.Liu) -24. 【server】增加全局项目搜索功能 -25. 【agent】项目增加批量启动关闭重启 -26. 【server】节点分发文件支持上传非压缩包(感谢@Sam、風中飛絮 [Gitee issues I3YNA5](https://gitee.com/dromara/Jpom/issues/I3YNA5) ) -27. 【server】nginx 二级代理无法访问(感谢@hu丶向...🤡) -28. 【server】ssh文件管理新增在线编辑(感谢@嗳啨 [Gitee issues I4ADTA](https://gitee.com/dromara/Jpom/issues/I4ADTA) ) -29. 在线升级支持上传 zip 包自动解析(感谢@Sam) -30. 【server】ssh 安装插件端新增等待次数配置(感谢@hu丶向...🤡) -31. 【server】新增前端接口请求超时配置 `jpom.webApiTimeOut`(感谢@hu丶向...🤡) -32. 【server】构建支持 tag 通配符 (感谢@落泪归枫 [Gitee issues I1LM1V](https://gitee.com/dromara/Jpom/issues/I1LM1V) ) - -### 解决BUG、优化功能 - -1. 【server】添加节点时候限制超时时间,避免配置错误一直等待情况 -2. 【server】优化限制 IP 白名单相关判断,避免手动修改错误后一直限制访问 -3. 【server】添加 QQ 邮箱配置参照说明 [QQ邮箱官方文档](https://service.mail.qq.com/cgi-bin/help?subtype=1&&no=369&&id=28) -4. 【server】fix: 删除临时文件出现 `AccessDeniedException` 更新文件权限为可读(取消只读权限) -5. 【server】拉取 GIT 代码根据仓库路径添加 `synchronized` -6. 【server】节点管理页面支持刷新当前节点页面(刷新不再回到首页) -7. 【server】 jpom-service.sh 文件加载环境变量修改为 判断模式 -8. 【agent】fix: windows 环境保存配置文件错误问题 -9. 【agent】fix: 在线升级页面在没有配置白名单时候无法显示节点信息 -10. 【server】ssh 快捷安装插件端检查配置文件不在使用 SpringBoot 非 public 工具类 -11. 【server】请求节点发生异常打印具体堆栈、接口异常拦截器里面默认不打印堆栈 (根据 Gitee [issues I3O8YE](https://gitee.com/dromara/Jpom/issues/I3O8YE) ) -12. 【server】节点升级中偶尔出现无法获取到对应的版本信息问题(感谢@misaka Gitee issues [I41TDY](https://gitee.com/dromara/Jpom/issues/I41TDY) ) -13. 本地运行数据目录位置改为`${user.home}/jpom/xxxx`、日志路径改为项目模块下 -14. 【agent】升级 `commons-compress` 依赖 (来自 GitHub [advisories](https://github.com/advisories) ) -15. agent 和 server 间的 websocket 鉴权调整 -16. 【server】update: 刷新整个页面的时候重新加载菜单 -17. 历史监控图表查询报时间格式化错误(字符串工具类) (感谢@misaka [Gitee pr](https://gitee.com/dromara/Jpom/pulls/142) ) -18. 【agent】nginx 配置文件取消强制检测 server 节点 -19. 【server】仓库密码改为隐藏 -20. 解决退出登录验证码没有刷新问题 (感谢群友:Steve.Liu) -21. 【agent】节点分发清空发布无效(感谢@Sam) -22. 【server】编写分发项目时,当分发节点做替换、新增的操作后,点击确认,控制台报错(感谢@tan90°) - -> 【特别声明】当前版本 仓库和构建并没有接入动态数据权限,如果对权限敏感的用户建议等待下一个版本优化权限后再升级(如有疑问可以微信群沟通) - -> 注意1:由于构建信息全部存储到 h2 数据库中,之前到构建信息会自动同步,在升级后到第一次启动需观察控制台信息,启动成功后请检查构建信息,仓库信息是否同步正确 -> -> 注意2:构建的触发器地址有更新,需要重新获取触发器地址 -> -> 注意3:升级到该版本需要保证 agent、server 都保持同步,如果只升级 server 会出现项目控制台等功能无法正常使用 -> -> 注意4:升级 2.7.x 后不建议降级操作,会涉及到数据不兼容到情况 +### 🐣 新增功能 + +1. 【all】新增 自由脚本方便调试机器节点 +2. 【agent】新增 项目支持配置禁止扫描目录避免大目录页面超时(感谢@我) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 资产管理 SSH 配置禁用命令无法回显(感谢@zhangw) +2. 【server】修复 资产管理 SSH 未配置授权目录时 NPE (感谢[@Anley](https://gitee.com/MrAnley) [Gitee issues I9J17G](https://gitee.com/dromara/Jpom/issues/I9J17G) ) +3. 【agent】优化 监控机器网络流程支持配置排除网卡或者仅统计对应的网卡 +4. 【server】修复 退出登录时页面会提示需要登录相关信息 +5. 【server】优化 页面检测新版本判断是否加入 beta +6. 【agent】优化 添加数据记录修改人(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I9JSY7](https://gitee.com/dromara/Jpom/issues/I9JSY7) ) +7. 【server】优化 插件端注册到服务端,网络测试支持 ping + telnet (感谢@泊凉青川) +8. 【server】优化 自动续签采用无感模式(感谢@湘江夜色) +9. 【server】优化 容器构建执行配置自定义 host 参数(感谢@冰淇淋还是冰激凌) +10. 【all】升级 tomcat、yaml 版本(感谢@佳驰) +11. 【all】升级 bcprov-jdk18on 版本 + +------ + +## 2.11.5-release (2024-04-23) + +### 🐣 新增功能 + +1. 【server】新增 Oauth2 新增【飞书账号】、【自建 Gitlab】登录(感谢[@鸡皮蒜毛与鸡毛蒜皮](https://gitee.com/cuia) [Gitee issues I9ELGS](https://gitee.com/dromara/Jpom/issues/I9ELGS) ) +2. 【server】新增 Oauth2 新增企业微信登录 +3. 【server】新增 发布系统公告 + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 oauth2 第三方平台登录解析用户名将依次尝试:平台用户名、邮箱、uuid +2. 【server】修复 无法查询到分组信息(页面下拉框)(感谢[@Robot](https://gitee.com/robot1937) [Gitee issues I9FN9U](https://gitee.com/dromara/Jpom/issues/I9FN9U) ) +3. 【all】升级 hutool 版本 +4. 【server】修复 修复孤独数据描述错别字(感谢[@cuiyongsheng](https://github.com/Cuiys1458) [Github issues 77](https://github.com/dromara/Jpom/issues/77) ) +5. 【server】修复 前端地址栏输入二级路径 404 页面卡死问题 +6. 【server】优化 Git 仓库地址不正确相关提示更准确(感谢@易自玉) +7. 【server】升级 前端组件版本 +8. 【all】优化 管理脚本删除 `-XX:-UseBiasedLocking` 使其能在高版本 jdk 运行 +9. 【server】修复 构建列表卡片模式按钮文字错乱 +10. 【server】修复 项目列表和逻辑节点卡片视图冲突 +11. 【server】修复 docker管理新增docker选择证书界面权重异常 (感谢[@伤感的风铃草](https://gitee.com/bwy-flc) [Gitee issues I9GYVA](https://gitee.com/dromara/Jpom/issues/I9GYVA) ) +12. 【server】修复 系统管理中用户管理中登录日志无法筛选 +13. 【server】优化 用户登录记录操作日志(保证操作监控能记录) +14. 【server】修复 系统管理中用户登录日志无法分页 +15. 【server】优化 Oauth2 支持配置创建账号配置权限组 +16. 【server】修复 文件发布权限为执行权限、文件发布记录删除无记录日志 (感谢@蓝枫) +17. 【server】修复 资产管理 SSH 配置禁用命令无法回显(感谢@zhangw) ------ -# 2.6.4-patch +## 2.11.4 (2024-04-07) + +### 🐣 新增功能 + +1. 【server】新增 数据库支持 *postgresql* (感谢[@王先生](https://gitee.com/whz_gmg1))[Gitee Pr 223](https://gitee.com/dromara/Jpom/pulls/223) +2. 【server】新增 数据库支持 *mariadb* +3. 【server】新增 Oauth2 新增钉钉扫码登录 + +### 🐞 解决BUG、优化功能 + +1. 【all】优化 新增 `jpom.system.command-use-sudo` 配置属性控制是否使用 sudo 执行部分系统命令 +2. 【server】优化 前端页面 keep-alive 可能导致的内存泄漏问题(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I9510M](https://gitee.com/dromara/Jpom/issues/I9510M) ) +3. 【server】修复 部分弹窗不生效问题(感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 215](https://gitee.com/dromara/Jpom/pulls/215) ) +4. 【server】优化 前端 ES lint 配置规范前端代码(感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 214](https://gitee.com/dromara/Jpom/pulls/214) / [Gitee Pr 215](https://gitee.com/dromara/Jpom/pulls/215) / [Gitee Pr 217](https://gitee.com/dromara/Jpom/pulls/217) ) +5. 【server】修复 docker 控制台网络选项卡页面空白(感谢@破冰) +6. 【server】修复 节点历史监控统计图表时间查询不生效(感谢@九問) +7. 【server】优化 SSH 脚本触发器支持传入参数当环境变量(感谢@小朱) +8. 【server】修复 h2迁移其它数据库时部分数据丢失(感谢[@王先生](https://gitee.com/whz_gmg1))[Gitee issues I9977K](https://gitee.com/dromara/Jpom/issues/I9977K) +9. 【server】优化 逐步引入新版表格(构建、项目、节点、资产机器)(感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 218](https://gitee.com/dromara/Jpom/pulls/218) / [Gitee Pr 220](https://gitee.com/dromara/Jpom/pulls/220) / [Gitee Pr 222](https://gitee.com/dromara/Jpom/pulls/222) ) +10. 【server】优化 工作空间概括构建日志支持快速查看详情(感谢@Roger.cao) +11. 【server】修复 无法查询到分组信息(页面下拉框)(感谢@猫猫向钱跑) +12. 【server】修复 【项目文件管理远程下载】、【镜像创建容器】确认按钮无法使用(感谢@猫猫向钱跑) +13. 【server】修改 资产管理机器管理删除按钮无法正常使用(感谢@🇩) +14. 【server】修复 SSH 面板文件管理无法正常切换(感谢@勤思·) +15. 【server】优化 部分页面在火狐浏览器无法正常打开(感谢[@sparkarvin](https://gitee.com/arvinlovegood_admin) [Gitee issues I96IOA](https://gitee.com/dromara/Jpom/issues/I96IOA) ) + (感谢[@a20070322](https://gitee.com/a20070322) [Gitee Pr 221](https://gitee.com/dromara/Jpom/pulls/221) ) +16. 【agent】优化 DSL 项目支持配置在特定目录执行脚本(run.execPath) +17. 【agent】优化 管理脚本 -Xss 默认值修改为 512k(感谢@Again... ) +18. 【server】优化 管理脚本 -Xss 默认值修改为 1024k(感谢@Again... ) +19. 【server】优化 声明使用开源软件列表、增加本软件开源协议声明 -### 新增功能 +------ + +## 2.11.3 (2024-03-01) + +### 🐞 解决BUG、优化功能 + +1. 【agent】修复 修改项目日志路径如果文件夹不存在报错(感谢@长弘) +2. 【server】修复 节点机器日志无法下载(感谢@Again...) +3. 【agent】升级 oshi 版本 +4. 【server】升级 mwiede、apache-sshd 版本(感谢@*斌) +5. 【server】优化 项目列表 file 类型正常排序(不再排序到最后)(感谢[@pal865](https://gitee.com/pal865) [Gitee issues I8XU32](https://gitee.com/dromara/Jpom/issues/I8XU32) ) +6. 【all】修复 windows 环境保存配置并重启失败(感谢[@Robot](https://gitee.com/robot1937) [Gitee issues I8Y01T](https://gitee.com/dromara/Jpom/issues/I8Y01T) ) +7. 【server】修复 新版本页面部分分页切换失效(构建详情、资产机器、逻辑节点)(感谢@zac) +8. 【server】优化 构建代码未变动流程打断触发器未传入原因(statusMsg)(感谢@烛孩) +9. 【server】修复 项目控制台日志删除弹窗未能正常关闭(感谢@%) +10. 【server】修复 脚本日志时间筛选不生效(感谢[@zhaozxc2010](https://gitee.com/zhaozxc2010) [Gitee issues I8ZNKL](https://gitee.com/dromara/Jpom/issues/I8ZNKL) ) +11. 【server】优化 页面左侧菜单固定悬浮不跟随屏幕滚动条滚动(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZOOB](https://gitee.com/dromara/Jpom/issues/I8ZOOB) / [Gitee Pr 201](https://gitee.com/dromara/Jpom/pulls/201) ) +12. 【server】优化 新增机器节点提示未选择协议(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZDZT](https://gitee.com/dromara/Jpom/issues/I8ZDZT) / [Gitee Pr 202](https://gitee.com/dromara/Jpom/pulls/202) ) +13. 【server】修复 SSH 资产硬盘信息显示错误(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZY7K](https://gitee.com/dromara/Jpom/issues/I8ZY7K) ) +14. 【server】优化 表格搜索区域小屏幕适配 (感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I8ZY0B](https://gitee.com/dromara/Jpom/issues/I8ZY0B) ) +15. 【server】优化 SSH 文件管理树操作优化 (感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I9054L](https://gitee.com/dromara/Jpom/issues/I9054L) / [Gitee issues I5DMKG](https://gitee.com/dromara/Jpom/issues/I5DMKG) ) +16. 【server】优化 整体页面顶部菜单吸顶效果(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I907Y8](https://gitee.com/dromara/Jpom/issues/I907Y8) ) +17. 【server】优化 资产监控线程池独立管理(感谢[@singlethread](https://gitee.com/zengwei_joni) [Gitee issues I918AB](https://gitee.com/dromara/Jpom/issues/I918AB) ) +18. 【server】优化 构建回滚使用构建独立线程池 +19. 【all】优化 升级 hutool 版本(主要解决版本号排序异常)(感谢 [@Tom Xin](https://gitee.com/meiMingle) [Gitee issues I8Z3TI](https://gitee.com/dromara/Jpom/issues/I8Z3TI) / [Hutool issues I8Z3VE](https://gitee.com/dromara/hutool/issues/I8Z3VE)) +20. 【all】优化 升级 fastjson 版本 +21. 【server】优化 页面整体滚动条兼容高版本浏览器(感谢@Controllers) +22. 【server】优化 升级 docker-java、jgit 版本 +23. 【all】优化 升级 commons-compress 版本 +24. 【server】优化 新增配置节点 websocket 通讯消息大小限制(jpom.node.web-socket-message-size-limit)(感谢@长弘) +25. 【server】优化 系统文件占用空间统计周期调整为每天2次(感谢[@singlethread](https://gitee.com/zengwei_joni) [Gitee issues I9302U](https://gitee.com/dromara/Jpom/issues/I9302U) ) +26. 【server】优化 支持配置前端所有参数编码来规避部分安全规则检查(感谢[@zhaozxc2010](https://gitee.com/zhaozxc2010) [Gitee issues I8Z1VJ](https://gitee.com/dromara/Jpom/issues/I8Z1VJ) ) +27. 【server】优化 上传文件空文件提示文件路径(感谢[@SchuckBate](https://gitee.com/skBate) [Gitee issues I93FI6](https://gitee.com/dromara/Jpom/issues/I93FI6) ) +28. 【server】优化 监听日志文件消息发送失败后自动移除会话(感谢[@singlethread](https://gitee.com/zengwei_joni) [Gitee issues I93ZFX](https://gitee.com/dromara/Jpom/issues/I93ZFX) ) +29. 【server】优化 容器构建产物为文件时保存路径层级错误(感谢[@vfhky](https://github.com/vfhky))[Github Pr 71](https://github.com/dromara/Jpom/pull/71) ) +30. 【server】优化 个性配置区内容主题支持“跟随系统”(感谢[@a20070322](https://gitee.com/a20070322) [Gitee issues I94SPA](https://gitee.com/dromara/Jpom/issues/I94SPA) ) + +------ -### 解决BUG、优化功能 +## 2.11.2 (2024-03-01) -1. 【server】构建触发器新增延迟执行参数(感谢@Steve.Liu) -2. 【server】数据库字段类型超大的 varchar 改为 CLOB(感谢@Alex) -3. 【server】获取仓库分支方式修改(避免大仓库执行时间太长)(感谢@自作多情) +### ⚠️ 注意 + +此版本是一个空版本,为了更好地兼容后续 beta 和 release 版本发布特意创建的空版本 + +2.11.2 版本等同于 2.11.3 版本 ------ -# 2.6.3-patch +## 2.11.1 (2024-01-18) + +### 🐣 新增功能 -### 新增功能 +1. 【server】新增 触发器调用次数统计、触发器统一管理 +2. 【server】新增 本地构建命令执行支持配置多线程方式(多线程接收输出流,避免极端情况卡死) -### 解决BUG、优化功能 +### 🐞 解决BUG、优化功能 -1. 【agent】mac 进程号转换问题修护 -2. 【server】节点分发的项目白名单路径回显错误(感谢@tan90°) -3. 【agent】自定义日志路径自动创建(感谢@tan90°) +1. 【server】修复 docker TLS 证书无法查看全部、证书无法编辑(新版遗漏) +2. 【server】优化 docker 资产监控支持自定义配置 cron `jpom.assets.docker.monitor-cron` +3. 【server】修复 容器终端、容器日志无法正常使用 +4. 【server】修复 新版本页面多处无法正常使用相关问题(优化部分提示说明) +5. 【server】修复 静态文件名太短(100个字符)(感谢@*斌) +6. 【server】修复 还原数据库弹窗内容提示为空(感谢@伤感的风铃草🌿) +7. 【server】优化 echarts 支持跟随深色模式 +8. 【server】修复 编辑节点分发服务端脚本弹窗被挡住(感谢@🇩) +9. 【server】优化 前端打包(缩减首屏加载时间)(感谢@曾梦想仗剑走天涯) +10. 【all】优化 机器状态新增:资源监控异常(资源监控异常不影响功能使用) +11. 【server】优化 取消登录页动态背景图 +12. 【server】修复 节点分发文件中心、静态文件后文件自动被删除(感谢@九問) +13. 【server】优化 容器构建支持配置容器资源(HostConfig)(感谢@珂儿) +14. 【agent】修复 插件端非默认工作空间项目重启后变为孤独数据(感谢@ccx2480) +15. 【server】修复 新增节点分发项目数据为孤独数据 ------ -# 2.6.2-patch +## 2.11.0 (2024-01-12) + +### 🐣 新增功能 + +1. 【all】新增 **项目支持软链其他项目(代替项目副本)** +2. 【server】新增 **工作空间管理中新增概括总览页面、资产总览统计** +3. 【server】升级 **页面 UI 组件、VUE 版本升级到最新(支持配置浅色、深色主题、左边菜单主题)** +4. 【all】新增 **孤独数据管理(查看孤独数据、修正孤独数据)**(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I8UNXZ](https://gitee.com/dromara/Jpom/issues/I8UNXZ)) +5. 【server】新增 节点分发可以指定构建历史产物发布 +6. 【server】新增 节点分发可以指定文件中心发布 +7. 【server】新增 DSL 项目新增 reload 事件(可以开启文件变动触发) +8. 【server】新增 静态文件授权服务端指定目录到工作空间来管理(分发)(感谢@*斌) +9. 【server】新增 节点分发可以指定静态文件发布 +10. 【server】修复 没有工作空间权限时页面循环跳转(感谢[@王先生](https://gitee.com/whz_gmg1) [Gitee issues I8RR01](https://gitee.com/dromara/Jpom/issues/I8RR01)) +11. 【all】优化 授权目录判断逻辑 +12. 【agent】取消 插件端授权目录关闭包含判断(`jpom.whitelist.check-starts-with`) +13. 【server】优化 触发器清理优化、删除用户主动删除关联触发器 +14. 【server】优化 DSL 项目控制台支持快捷编辑节点脚本(查看流程信息) +15. 【server】修复 项目触发器无法调用 + +### 🐞 解决BUG、优化功能 + +1. 【all】下架 **全面下架项目副本功能(请使用 DSL 模式或者软链项目代替)** +2. 【all】下架 **全面节点证书管理功能(请使用工作空间证书代替)** +3. 【all】下架 **全面架节点 NGINX 管理功能(请使用 DSL 模式代替)** +4. 【server】优化 **构建编辑页面交互、编辑器样式、DSL 配置样式** +5. 【server】优化 **节点管理仅保留项目管理、脚本管理、脚本日志(其他功能迁移到机器资产管理)** +6. 【all】优化 查看插件端和服务端的系统日志 websocket 地址 +7. 【server】优化 监控机器系统负载保留2位小数 +8. 【server】下架 取消节点管理员权限 +9. 【server】修复 文件变动触发器不生效的问题 +10. 【all】优化 项目操作接口合并(4 合 1) +11. 【server】优化 配置授权目录需要使用到绝对路径 +12. 【server】优化 重新同步节点项目、节点脚本缓存交互 +13. 【server】优化 SSH 脚本执行模板独立(`/exec/template.sh` -> `/ssh/template.sh`) +14. 【server】优化 服务端脚本支持加载脚本模板来实现自动加载部分环境变量 +15. 【all】优化 关闭 Process 方式 +16. 【server】优化 支持批量删除构建信息(感谢@奇奇) +17. 【server】修复 删除项目、删除分发检查关联构建失败问题 +18. 【all】优化 日志记录器提升日志记录性能 +19. 【server】优化 取消/停止构建采用异常来打断子进程 +20. 【server】修复 本地构建无法取消 +21. 【server】修复 服务端脚本触发器、节点脚本触发器提示找不到用户(感谢@LYY) +22. 【server】修复 部分低频功能无法正常使用(项目备份文件管理等) +23. 【server】修复 部分执行异常未输出到操作日志文件中(感谢@闫淼淼) +24. 【server】优化 系统Git拉取代码时,强制云端最新代码覆盖本地代码 +25. 【agent】优化 插件端 java 项目启动支持读取环境变量 +26. 【agent】修复 插件端环境变量值获取异常 +27. 【server】优化 本地 git 联动严格执行开关 +28. 【server】修复 登录账户未跳转配置的第一个工作空间(未遵循自定义配置) +29. 【server】修复 部分操作构建环境变量丢失(保存并构建、后台构建、直接构建) +30. 【agent】优化 插件端自由脚本(分发文件脚本)大小限制调整为 5M(感谢@九問) + +### ⚠️ 注意 + +1. 全面下架项目副本功能(请使用 DSL 模式或者软链项目代替)如果您当前使用到此功能请先手动备份相关数据 +2. 升级后项目副本数据会被人工或者系统更新项目数据自动删除(请一定提前做好备份操作) +3. 全面下架节点证书管理功能(请使用工作空间证书代替)如果您当前使用到此功能请先手动备份相关数据 +4. 全面下架全下架节点 NGINX 管理功能(请使用 DSL 模式代替)如果您当前使用到此功能请先手动备份相关数据 + +>❓ 为什么要下架上述功能:由于版本迭代已经有更好的新功能可以代替之前旧功能,并且新功能从另一种角度更方便。下架也是为了我们后续版本维护迭代更高效 -### 新增功能 +------ + +1. 如果您配置了授权目录但是保存项目报错您可以尝试重新报错一下授权目录来自动修复授权目录配置数据 +2. 项目控制台日志默认路径调整为插件端数据目录下`project-log/${projectId}/${projectId}.log` +3. 项目控制台日志备份默认路径调整为插件端数据目录下`project-log/${projectId}/back/${projectId}-xxxxxxx.log` -### 解决BUG、优化功能 +--------- -1. 【server】清除构建目录失败(感谢@大灰灰) -2. 【server】fix: 在线升级页面在没有配置白名单时候无法显示节点信息 -3. 【agent】fix: windows 环境保存配置文件错误问题 -4. 【agent】升级 commons-compress 依赖 (来自 GitHub advisories ) -5. 【server】优化限制 IP 白名单相关判断,避免手动修改错误后一直限制访问 +如果您自定义过 SSH 脚本默认那么您需要重新同步一下脚本模板`/exec/template.sh` -> `/ssh/template.sh` + +新版本 `/exec/template.sh` 中仅在服务端中生效(本地构建脚本、服务端脚本、本地发布脚本) ------ -# 2.6.1-patch +- 【白名单】关键词统一调整为【授权】 +- 【黑名单】关键词统一调整为【禁止】 +- 部分【添加】关键词统一调整为【新增】 -### 新增功能 +### 🤝致谢 -### 解决BUG、优化功能 +感谢所有参与 2.11.0 beta 版本(2.11.0.0 ~ 2.11.0.13)内测的用户。 -1. 【agent】 当自定义配置授权信息后增加控制台输出信息,避免用户无感(感谢@南) -2. 【server】增加构建日志表构建命令字段长度,变更后长度为5000 -3. 【server】调整编辑构建弹窗布局 -4. 【server】ssh 发布命令调整为 sh 命令统一执行,避免类似 `nohup` 一直阻塞不响应 -5. 【server】拦截器文件权限异常,提醒检查目录权限 +------ + +## 2.10.47 (2023-12-25) + +### 🐣 新增功能 + +1. 【server】新增 容器构建支持自定义插件(感谢[@远方](https://gitee.com/WaHaHaqqww) [Gitee issues I8PEWI](https://gitee.com/dromara/Jpom/issues/I8PEWI)) +2. 【server】新增 容器管理新增导出、导入镜像 +3. 【server】新增 环境变量支持触发器获取、修改 +4. 【server】优化 容器日志、集群任务日志支持下载(感谢@在时间里流浪) +5. 【all】新增 部分项目支持迁移工作空间和逻辑节点(感谢@奇奇) +6. 【server】优化 资产管理 SSH 支持配置禁用监控(避免频繁登录)`jpom.assets.ssh.disable-monitor-group-name`(感谢@Again...) +7. 【server】优化 资产管理 SSH 支持配置监控周期:`jpom.assets.ssh.monitor-cron` + +### 🐞 解决BUG、优化功能 + +1. 【server】升级 数据库 h2 、mwiede、web axios 版本 +2. 【server】修复 构建事件脚本未修改执行状态和退出码问题 +3. 【server】优化 构建事件脚本支持多选(顺序执行其中有一个中断将结束执行后续脚本)(感谢@loyal) +4. 【server】优化 服务端脚本触发类型新增构建事件 +5. 【agent】删除 项目副本中弃用兼容字段 `parendId` +6. 【server】优化 Docker 集群任务日志支持筛选行数、是否显示时间戳(感谢@在时间里流浪) +7. 【server】优化 项目控制台日志输出 N 人查看改为 N 个会话(@冬) +8. 【server】优化 添加超级管理员账号提醒勿使用常用账号 +9. 【server】优化 逻辑节点节目取消全局 loading(感谢@小菜鸡) +10. 【server】优化 新增个性化配置全屏打开日志弹窗(构建、SSH、脚本、Docker等日志)(感谢@张飞鸿) +11. 【server】修复 项目副本无法保存(修改中不能删除副本集、请到副本集中删除) +12. 【server】优化 服务端中可以支持创建编辑项目、创建节点脚本啦!!! +13. 【server】优化 项目列表支持删除项目、自动刷新项目 +14. 【server】优化 仓库支持查看关联的构建 +15. 【server】修复 删除服务端脚本日志如果脚本不存在不能删除 +16. 【server】优化 资产机器卡片试图部分场景未对齐问题 +17. 【server】优化 部分页面在小屏兼容(资产 Docker、节点分发) +18. 【server】优化 节点脚本支持解绑(避免无非使用的服务器无非删除脚本) +19. 【server】优化 白名单配置提示 nginx、证书功能将下线 +20. 【all】移除 插件端配置远程下载 host 输入框 +21. 【server】优化 导入 SSH、项目 CSV 数据自动识别编码格式 +22. 【server】优化 执行 SSH 脚本获取流异常:getInputStream() should be called before connect() + +### ⚠️ 注意 + +新增容器构建自定义插件说明: + +1. 到 【系统管理】->【配置管理】->【系统配置目录】 中找到 `docker/uses` 目录 +2. 添加插件配置文件 `/docker/uses/java8/install.sh` 其中 `java8` 为新增的插件名 +3. 注意脚本中需要自行控制插件相关依赖的文件,需要将最新的文件保持到 `/opt/${name}/` 目录下,其中 ${name} 为插件名 +4. 辅助说明1:插件支持自定义环境变量 +5. 辅助说明2:自定义环境变量中需要引用插件目录请使用 `${JPOM_PLUGIN_PATH}` +6. 系统默认集成了:`java`、`maven`、`node`、`go`、`python3`、`gradle`、`cache` 默认插件有依赖版本检查如果您的网络不通建议自行创建插件来规避默认检查 ------ -# 2.6.0-beta +## 2.10.46 (2023-12-18) -### 新增功能 +### 🐞 解决BUG、优化功能 -1. 【server】新增配置 h2 数据账号密码参数(注意之前已经存在的数据不能直接配置、会出现登录不成功情况) -2. 【agent】项目新增配置控制台日志输出目录 (感谢@落泪归枫 [Gitee I22O4N](https://gitee.com/dromara/Jpom/issues/I22O4N)) -3. 【server】新增配置 jwt token 签名 key 参数 -4. 【server】ssh 新增配置禁止执行的命令,避免执行高风险命令 -5. 【server】构建发布方式为 ssh 检查发布命令是否包含禁止执行的命令 -6. 【server】新增 ssh 执行命令初始化环境变量配置 `ssh.initEnv` +1. 【server】修复 容器构建下载产物未关闭文件流占用句柄问题(感谢@在时间里流浪) +2. 【all】优化 ConcurrentHashMap 修改为线程安全的 hutoll[SafeConcurrentHashMap](感谢@在时间里流浪) +3. 【all】升级 mwiede.jsch、oshi、fastjson、hutool、spring-boot、docker-java +4. 【server】优化 SSH 脚本在部分场景阻塞卡死(ChannelType.EXEC 不添加超时时间) +5. 【server】优化 SSH 脚本执行输出退出码 +6. 【server】优化 解决构建流程,环境变量env里出现value==null出现null报错 + (感谢 [@周冰](https://gitee.com/NineAsk) [Gitee pr 197](https://gitee.com/dromara/Jpom/pulls/197) ) +7. 【server】优化 SSH 脚本执行记录退出码 +8. 【server】优化 服务端脚本执行记录新增执行状态和退出码 -### 解决BUG、优化功能 +------ -1. 【agent】 修护 nginx 重载判断问题(@大灰灰大 码云 issue [I40UE7](https://gitee.com/dromara/Jpom/issues/I40UE7) ) -2. 【server】修护 ssh 上传文件时候不会自动创建多级文件夹(@大灰灰大) -3. 【server】角色动态权限显示分组 -4. 【agent】 新增 stop 项目等待进程关闭时间配置 `project.stopWaitTime`、停止项目输出 kill 执行结果 -5. bat 管理命令更新环境变量,避免部分服务器出现无法找到 taskkill 命令( 感谢@Sunny°晴天、[@zt0330](https://gitee.com/zt0330) ) -6. 升级SpringBoot、Hutool等 第三方依赖版本 -7. 去掉旧版本 ui (thymeleaf、layui) -8. 【server】fix: ssh 分发执行命令找不到环境变量问题 -9. 【server】在线升级显示打包时间、并发执行分发 jar 包、部分逻辑优化 -10. 【server】 构建历史增加下载构建产物按钮(感谢@房东的喵。) -11. 【server】项目控制台新增心跳消息,避免超过一定时间后无法操作的情况 -12. 【server】ssh 新增心跳消息,避免超过一定时间后无法操作的情况 -13. 【server】系统缓存中的文件占用空间大小调整为定时更新(10分钟) -14. 【server】修复 bug:分发列表页面点击【创建分发项目】按钮之后不能正常显示【分发节点】感谢 @xingenhi [点击查看提交记录](https://gitee.com/dromara/Jpom/commit/bd38528fbd3067d220b7569f08449d7796e07c74) [@Hotstrip](https://gitee.com/hotstrip) -15. 【server】fix: 编辑管理员时用户名不可修改 -16. 【server】折叠显示部分列表操作按钮(减少误操作) +## 2.10.45 (2023-10-17) -> 注意:当前版本为 beta 版本。项目中升级了较多依赖版本、新增了部分重要配置(建议确认好后再配置).如果大家在升级后使用中发现任何问题请及时到微信群反馈,我们会尽快协助排查解决 -> -> 1. 如果是已经安装 Jpom、升级到当前版本请勿直接配置数据库账号密码,如果需要配置请手动连接数据库人工修改密码后再配置 - ------- - -# 2.5.2 - -### 新增功能 - -1. 【agent+server】 新增节点批量升级功能(注意,之前的节点版本不支持该功能需要升级当前版本后才能使用该功能) -2. 【server】节点配置的超时时间单位由毫秒改为秒,并且最小值为2秒 -3. 【server】新增构建合并分支日志(便于判断分支冲突问题) - -### 解决BUG、优化功能 - -1. 【server】fix bug: - 分发列表页面,展开某个节点之后点击操作按钮会出现新的一行无效数据。[点击查看提交记录](https://gitee.com/dromara/Jpom/commit/e28b14bcf3dce402ce170a40f9bb93c4d25d0935) [@Hotstrip](https://gitee.com/hotstrip) -2. 【server】fix bug: - 项目监控页面,线程数据加载失败问题 [点击查看提交记录](https://gitee.com/dromara/Jpom/commit/b11c5443db6468a2bf7f6a9fa933f8d965899624) [@Hotstrip](https://gitee.com/hotstrip) -3. 【server】fix bug: 修复低版本浏览器不支持 `.replaceAll()` - 方法 [点击查看提交记录](https://gitee.com/dromara/Jpom/commit/0fb475963153b76546409ac3065a0efe9e647541) [@杨巍](https://gitee.com/fat_magpie_beijing_tony) -4. 【server】update: 更新分发列表 -- 关联分发项目页面操作逻辑(跟老版本操作逻辑一致)[点击查看提交记录](https://gitee.com/dromara/Jpom/commit/cd6e4ae89f833e5e7ef11bd12c324a487de27b1a) [@李道甫](https://gitee.com/koushare_dfli) -5. 【server】update: 优化项目文件管理页面,加载目录树时会多次显示 loading 层 [点击查看提交记录](https://gitee.com/dromara/Jpom/commit/71b3779bffb36259e0980ce25d4e4082a9d7c2e6) [@Hotstrip](https://gitee.com/hotstrip) -6. 【server】fix bug: 修复节点请求超时可能导致节点项目列表为空 bug [点击查看提交记录](https://gitee.com/dromara/Jpom/commit/e3182dfa04c27e63a29d67b292a7bfef834f875e) [@Hotstrip](https://gitee.com/hotstrip) -7. 【agent】 fix bug: index 获取进程列表 NPE (感谢@夏末秋初) -8. 【server】fix bug: 修复上传项目压缩文件创建项目目录异常[点击这里查看对应 issue](https://gitee.com/dromara/Jpom/issues/I29FRJ) -9. 【server】fix bug:创建构建时,如果选择 - svn,隐藏掉分支选项。[点击这里查看对应 issue](https://gitee.com/dromara/Jpom/issues/I3TA6S) [感谢 Alexa 提出 issue](https://gitee.com/alexa1989) [@Hotstrip](https://gitee.com/dromara/Jpom/compare/180914f4ddda4dc34fa2df9b169bac7b593dedb0...aa6bb065b6f507ad0bf42225a2aad40e2d25597f) -10. 【server】 fix bug: ssh 构建发布清空历史文件失败(感谢@金晨曦) -11. 【server】update 构建初始化仓库拉取指定分支,不先拉取主分支再切换到指定分支(感谢@大灰灰) -12. 【server】程序关闭时候自动关闭 h2 数据连接池,避免数据库文件被损坏 -13. 【server】style: - 优化logo,登录页面,初始化页面 [点击查看对应提交记录](https://gitee.com/dromara/Jpom/commit/5d4783f0be7d44bb04275b059ccd1509620c5828) [@长得丑活得久i](https://gitee.com/zsf_008) -14. 【server】fix bug: - 修复在没有配置nginx白名单时访问nginx列表数据一直加载中问题[点击这里查看对应 issue](https://github.com/dromara/Jpom/issues/5) [@长得丑活得久i](https://gitee.com/zsf_008) -15. 新增 .gitattributes 文件控制命令文件的编码格式以及换行符(感谢@ℳ๓₯㎕斌) - ------- - -# 2.5.1 - -### 新增功能 - -1. 【Server】保存邮箱信息时候验证邮箱配置是否正确(感谢@maybe) -2. 【Server】Token 机制采用 jwt -3. 【Server】git 构建新增进度日志输出 -4. 【Server】添加操作监控相关 api 和页面功能 -5. 【Server】完善 JWT token 过期自动续签功能 -6. 【Server】添加前端页面引导系统(使用 introJs) -7. 【Server】访问 ip 限制,支持配置白名单和黑名单来控制 ip 访问权限 -8. 【Server】添加服务自启动脚本创建方案,下面贴一下 Server 端自启动方式: - -### 解决BUG、优化功能 - -1. 【Server】全局网络请求新增 loading 状态控制 -2. 【Server】获取构建日志关闭 loading 状态 -3. 【Agent】控制台日志支持定时清空,避免日志文件太大(感谢@南有乔木) -4. 【Server】在线升级状态判断修复 -5. 【Server】修复项目获取进程信息失败(感谢@onlyonezhongjinhui GitHub issues#7) -6. 【Server】项目文件管理中显示项目文件存放真实目录 -7. 【Server】项目文件管理中文件夹不存在时,loading不消失(感谢@onlyonezhongjinhui GitHub issues#6) -8. 【Server】文件管理列表不能正常加载二级以上的目录 -9. 【Server】添加监控判断用户是否配置报警联系方式(感谢@maybe) -10. 【Server】初始化安装不能自动登录 -11. 【Server】页面组件采用国际化采用 zh_cn -12. 【Server】服务器中验证码无法加载(感谢@何好听 Gitee issues#I3E7XQ) -13. 【Agent】解决控制台输出 `Failed to check connection: java.net.ConnectException: Connection refused: connect`,因为没有关闭对应的 jmx -14. 【Agent】解决首页控制台 java 进程列表慢的问题(采用定时拉取并缓存) -15. 【server】fix bug: - 节点列表页面,展开某个节点之后点击操作按钮会出现新的一行无效数据。 [点击查看提交记录](https://gitee.com/dromara/Jpom/commit/b9ecdfa649d27c46bca696e6df088a0908056ff6) -16. 【server】fix bug: 节点列表页面,在没有安装节点的情况下,点击终端按钮会在控制台报错。[点击这里查看对应 issue](https://gitee.com/dromara/Jpom/issues/I3J4UI) -17. 【server】fix bug: 节点管理里面的 Nginx 管理,关闭服务的接口参数传递错了。[点击这里查看对应 issue](https://gitee.com/dromara/Jpom/issues/I3IFZY) -18. 【server】优化系统配置页面的样式,在小屏幕设备上会出现多个竖方向上的滚动条,甚至有时候会遮住底部的操作按钮 -19. 【server】ssh 终端命令交互优化(改优化取消之前版本快捷解压功能,删除命令检查) -20. 【server】优化表格的排版和高度等样式,适配页面。详情见 [issue](https://gitee.com/dromara/Jpom/issues/I3EE2R) -20. 【server】优化节点分发关联操作界面。 - -> 注意事项: -> 1. ssh 终端的删除命令检查临时取消(后面版本会重新优化) -> 2. 该版本新增配置 Jpom 服务方式,需要更新 Server.sh、Agent.sh 文件,在线升级仅升级应用程序不会升级对应的管理命令文件,如果需要使用到该功能还需要手动覆盖更新对应的文件。(如果自定义过管理命令文件则需要差异覆盖) - -> 开机自启动: +### 🐣 新增功能 + +1. 【server】新增 SSH 新增独立管理面板(感谢[@超人那个超i](https://gitee.com/chao_a) [Gitee issues I7UFEX](https://gitee.com/dromara/Jpom/issues/I7UFEX)) +2. 【agent】新增 DSL 项目支持配置脚本环境变量(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I80PTK](https://gitee.com/dromara/Jpom/issues/I80PTK)) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 构建产物同步到文件中心支持独立配置保留天数(感谢[@zhangxin2477](https://gitee.com/zhangxin1229) [Gitee issues I82G2F](https://gitee.com/dromara/Jpom/issues/I82G2F)) +2. 【server】优化 不能删除超级管理员账号 +3. 【agent】修复 Agent.sh 脚本的缺少方法问题(感谢[@Siwen Yu](https://github.com/yusiwen) [Github issues 64](https://github.com/dromara/Jpom/issues/64)) +4. 【server】优化 系统管理查看操作日志显示全部工作空间 +5. 【server】优化 容器构建判断构建异常(严格模式异常中断构建)(感谢@在时间里流浪) +6. 【server】修复 构建流程中断触发 success 事件(感谢@在时间里流浪) +7. 【server】优化 SSH 独立管理面板支持快捷使用文件管理 +8. 【server】优化 构建详情页面支持快捷回滚、查看构建日志(感谢[@縁來只爲伱](https://gitee.com/taochach) [Gitee issues I7YSNH](https://gitee.com/dromara/Jpom/issues/I7YSNH)) +9. 【all】升级 hutool、commons-compress +10. 【agent】修复 重启项目偶发 NPE(监听日志关闭事件)(感谢[@caiqy](https://gitee.com/caiqiaoyu) [Gitee issues I7Z2U6](https://gitee.com/dromara/Jpom/issues/I7Z2U6)) +11. 【server】优化 构建支持配置环境变量实现产物打包为 `tar.gz` (**USE_TAR_GZ=1**) +12. 【server】修复 文件管理偶发无法查看发片下载地址 + +------ + +## 2.10.44 (2023-09-06) + +### 🐣 新增功能 + +1. 【server】新增 支持 git submodules + (感谢 [@Croce](https://gitee.com/Croce) [Gitee pr 195](https://gitee.com/dromara/Jpom/pulls/195) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 资产管理共享仓库新建归属到工作空间问题(感谢@沈钊) +2. 【server】升级 springboot 、oshi、docker-java、jgit +3. 【server】升级 mwiede.jsch 版本 +4. 【server】优化 构建回滚创建新的构建记录(感谢[@Smith](https://gitee.com/autools) [Gitee issues I7VEJA](https://gitee.com/dromara/Jpom/issues/I7VEJA)) +5. 【server】修复 新增资产无法正常监控问题(感谢@乔、@MichelleChung、@Pluto) +6. 【server】优化 编辑集群地址不验证,调整到心跳检测验证(感谢@黄纲) +7. 【server】优化 构建新增环境变量:BUILD_ORIGINAL_RESULT_DIR_FILE、BUILD_RESULT_DIR_FILE(发布流程)(感谢@黄纲) + +------ + +## 2.10.43 (2023-08-25) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 未配置集群地址时无法切换工作空间(感谢@黄纲) + +------ + +## 2.10.42 (2023-08-24) + +### 🐣 新增功能 + +1. 【server】新增 集群化管理工作空间(感谢@定格、[@paobu](https://gitee.com/iniushi) [Gitee issues I7UG5V](https://gitee.com/dromara/Jpom/issues/I7UG5V)) +2. 【server】优化 ssh 相关功能支持 openssh8+ + (感谢 [@孤城落寞](https://gitee.com/gclm) [Gitee pr 193](https://gitee.com/dromara/Jpom/pulls/193) ) +3. 【server】新增 SSH + 文件管理修改文件权限功能(感谢 [@MichelleChung](https://gitee.com/michelle1028) [Gitee issues I6VDXS](https://gitee.com/dromara/Jpom/issues/I6VDXS) ) +4. 【server】新增 Docker 容器重建功能,即删除原有的容器,重新创建一个新的容器 +5. 【server】新增 Docker 管理增加 SSH 连接 + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 资产管理 SSH 管理系统名称显示未知问题(感谢@勤思·) +2. 【server】优化 资产管理 Docker 管理支持配置分组 +3. 【server】优化 仓库管理支持配置分组 +4. 【server】优化 SSH 文件夹支持前端排序(感谢@勤思·) +5. 【server】优化 仓库账号、 SSH 证书密码支持选择环境变量 +6. 【all】升级 commons-compress、fastjson、hutool 版本 +7. 【server】优化 maven 依赖冲突 +8. 【server】优化 文件发布-节点发布文件名使用真实名称(感谢@勤思·) +9. 【server】优化 文件发布-ssh发布新增变量:FILE_NAME、FILE_EXT_NAME +10. 【server】升级 h2、SpringBoot 版本 +11. 【server】使用系统git时,无法克隆tag问题优化 (感谢@唐明) +12. 【server】优化 SSH 和 代码仓库的密码从工作空间变量中读取 +13. 【server】优化 + 删除工作空间前预检查关联数据存在情况(感谢 [@陈旭](https://gitee.com/chenxu8989) [Gitee issues I7F0ZN](https://gitee.com/dromara/Jpom/issues/I7F0ZN) ) +14. 【server】优化 + 退出登录支持彻底退出、切换账号退出(感谢 [@wangfubiao](https://gitee.com/wangfubiao) [Gitee issues I7GA5Q](https://gitee.com/dromara/Jpom/issues/I7GA5Q) ) +15. 【server】优化 IP 白名单验证忽略 IPV6 情况 +16. 【server】优化 服务端缓存管理支持查看黑名单 IP 详细信息(感谢@酱总) +17. 【server】修复 SSH + 编辑输入框出现部分关键词时保持报错(感谢 [@一只羊](https://gitee.com/hjdyzy) [Gitee issues I7E3UG](https://gitee.com/dromara/Jpom/issues/I7E3UG) ) +18. 【server】优化 日志组件支持显示 \t 制表符、清空缓冲区滚动到顶部 +19. 【server】修复 彻底删除节点分发时未自动删除关联日志(感谢@ccx2480) +20. 【server】修复 + 节点管理中脚本模板翻页无效(感谢 [@wangfubiao](https://gitee.com/wangfubiao) [Gitee issues I7F0RS](https://gitee.com/dromara/Jpom/issues/I7F0RS) ) +21. 【server】优化 + 工作空间配置页面中新增节点分发白名单配置入口(感谢 [@陈旭](https://gitee.com/chenxu8989) [Gitee issues I7F0W0](https://gitee.com/dromara/Jpom/issues/I7F0W0) ) +22. 【server】优化 构建附加环境变量支持解析 URL 参数格式 + (感谢 [@爱琳琳真是太好了](https://gitee.com/qiqi513_admin) [Gitee issues I7FROG](https://gitee.com/dromara/Jpom/issues/I7FROG) ) +23. 【server】优化 构建支持单个配置保留天数和保留个数 + (感谢 [@阿超](https://gitee.com/VampireAchao) [Gitee issues I7FOG2](https://gitee.com/dromara/Jpom/issues/I7FOG2) ) + +------ + +## 2.10.41 (2023-06-16) + +### 🐣 新增功能 + +1. 【server】新增 SSH 列表支持显示 docker 版本信息 +2. 【server】优化 Docker 镜像增加批量删除(已经被容器使用的镜像不会删除) +3. 【server】优化 启用 Jpom 新版专属 logo +4. 【server】新增 工作空间新增分组字段(存在多个分组时页面切换将使用二级选择)(感谢@酱总) +5. 【server】新增 仓库支持导入导出 +6. 【server】新增 镜像创建容器支持配置 hostname、集群服务支持配置 hostname(感谢@心光) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 查看 docker 容器日志 web socket 线程被阻塞问题 +2. 【server】优化 日志组件显示高亮、滚动条样式优化 +3. 【server】优化 web socket 会话关闭显示分类 +4. 【server】优化 页面滚动条样式 +5. 【server】优化 编辑关联分发,选择项目下拉框不能显示项目全名称(tooltip)(感谢@LYY) +6. 【server】优化 监听页面关闭事件,主动关闭 websocket +7. 【server】修复 批量构建触发器无法正常使用(感谢 [@botboy](https://github.com/cheakin) [Github issues 48](https://github.com/dromara/Jpom/issues/48) ) +8. 【server】修复 页面关闭 docker 终端未主动关闭后台终端进程问题 +9. 【server】优化 docker 终端页面缓冲区大小自动适应 +10. 【server】优化 项目列表可以查看项目日志(避免控制台卡顿无法操作下载日志)(感谢@阿超) +11. 【server】优化 日志组件采用虚拟滚动渲染,避免日志过多浏览器卡死 +12. 【server】优化 资产管理支持管理共享仓库 +13. 【server】优化 增大验证码检测功能异常捕捉范围 +14. 【server】修复 令牌导入仓库令牌长度不足问题(感谢 [@Sherman Chu](https://github.com/yeliulee) [Github issues 45](https://github.com/dromara/Jpom/issues/45) ) +15. 【server】修复 分发列表配置功能无法使用(感谢 [@Free](https://gitee.com/fjlyy321) [Gitee issues I716UI](https://gitee.com/dromara/Jpom/issues/I716UI) ) +16. 【server】修复 构建卡片布局、构建详情中构建方式显示不正确(感谢@A) + +### ⚠️ 注意 + +1. 如果自定义过 SSH 监控脚本需要自行同步获取 docker 信息脚本 + +------ + +## 2.10.40 (2023-04-19) + +### 🐣 新增功能 + +1. 【server】新增 容器构建中对 gradle 插件的支持(感谢 [@xiaozhi](https://gitee.com/XiaoZhiGongChengShi) [Gitee pr 188](https://gitee.com/dromara/Jpom/pulls/188) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 日志搜索控制台无法正常使用(感谢@左手生活,右手浪漫) +2. 【server】修复 项目文件跟踪控制台无法正常使用(感谢@左手生活,右手浪漫) +3. 【server】修复 插件端日志无法正常差异 +4. 【server】修复 docker 拉取镜像不能识别私有仓库地址(@章强) +5. 【server】优化 编辑构建无法重置已经选择的事件脚本 (感谢@左手生活,右手浪漫) +6. 【server】优化 登录页面切换验证码自动清空验证码输入框(感谢@TrouBles) +7. 【server】修复 docker 集群日志查看后未自动关闭造成日志文件继续增长的问题(@无味。) +8. 【server】优化 服务端缓存项目信息的创建时间和修改时间同步为节点中的数据创建、修改时间 +9. 【server】优化 文件管理支持批量删除(感谢@左手生活,右手浪漫) +10. 【agent】优化 取消 hutool-cache 包依赖 +11. 【server】优化 JustAuth fastjson 依赖配置为 fastjson2 +12. 【agent】修复 获取项目状态部分情况出现 NPE (感谢@酱总) +13. 【server】修复 清空浏览器缓存未跳转到登录页面 +14. 【server】优化 构建拉取 git 仓库支持使用服务器中的 git 插件,实现配置克隆深度参数 +15. 【server】修复 删除节点脚本报错(感谢 [@xiaozhi](https://gitee.com/XiaoZhiGongChengShi) [Gitee issues I6USMY](https://gitee.com/dromara/Jpom/issues/I6USMY) ) +16. 【server】优化 构建 SSH 发布命令支持 `SSH_RELEASE_PATH` 环境变量(感谢@定格) +17. 【server】修复 全屏终端无法打开文件管理(感谢@Pluto) +18. 【server】优化 自动探测服务端登录验证码是否可用 +19. 【all】优化 文件编辑后缀识别支持配置文件名或者正则表达式(感谢@MichelleChung) +20. 【server】优化 支持自动执行触发器清理 +21. 【server】优化 重新登录未加载管理员菜单(@A) +22. 【server】修复 第三方登录跳转测试丢失 +23. 【server】修复 仓库编辑清除密码按钮弹窗层级问题(感谢 [@轩辕豆豆](https://gitee.com/xuanyuandoudou) [Gitee issues I6VSCR](https://gitee.com/dromara/Jpom/issues/I6VSCR) ) +24. 【server】修复 优化构建列表卡片布局存在未构建数据布局错乱问题(感谢 [@lin_yeqi](https://gitee.com/lin_yeqi) [Gitee issues I6VUB7](https://gitee.com/dromara/Jpom/issues/I6VUB7) ) + +------ + +## 2.10.39 (2023-04-04) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 资产管理机器管理单个分配工作空间无法正常使用(感谢@咻咻咻秀啊) +2. 【server】修复 资产管理相关权限、操作日志无法记录问题(感谢@咻咻咻秀啊) +3. 【server】修复 docker 控制台 、日志无法正常使用 +4. 【server】优化 docker 控制台页面布局优化,支持单独查看 docker-compose +5. 【server】优化 docker 实时查看日志支持配置是否显示时间戳 +6. 【server】修复 查看文件发布详情节点名称未显示 +7. 【server】优化 发布记录重建不能选中节点 +8. 【server】修复 构建同步到文件管理中心失败(感谢@破冰) +9. 【server】优化 登录成功主动刷新菜单缓存、切换账号登录工作空间无权限页面白屏(感谢@A、@零壹) +10. 【all】更名 变更包名为 `org.dromara.jpom` +11. 【server】修复 编辑 docker 导入证书弹窗无法正常显示问题(感谢@左手生活,右手浪漫) +12. 【server】修复 工作空间中资产管理相关页面搜索无数据时出现操作引导提示(感谢@酱总) + +------ + +## 2.10.38 (2023-03-31) + +### 🐣 新增功能 + +1. 【server】新增 证书管理全部迁移到服务端统一导入 (感谢@.) +2. 【server】新增 节点项目支持导入,导出(感谢@酱总) +3. 【server】新增 支持 oauth2 登录(maxkey、gitee、github) (感谢 [@MaxKeyTop](https://gitee.com/maxkeytop_admin) [Gitee pr 183](https://gitee.com/dromara/Jpom/pulls/183) 、@A) +4. 【all】新增 文件管理发布支持发布到节点指定目录 +5. 【server】新增 构建新增配置排除发布目录表达式(感谢@毛毛虫) +6. 【all】新增 节点脚本支持全局共享(感谢@奇奇) +7. 【server】新增 构建状态新增队列等待,用于标记当前构建存于线程排队中(感谢@酱总) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 清理单项构建历史保留个数只判断(构建结束、发布中、发布失败、发布失败)有效构建状态,避免无法保留有效构建历史(感谢@张飞鸿) +2. 【server】优化 节点监控超时时间调整为 30 秒(避免 windows 服务器频繁超时)(感谢@波比) +3. 【server】优化 打开节点管理页面不刷新节点列表 +4. 【agent】修复 未配置节点白名单时直接创建分发项目报错(感谢@奋起的大牛) +5. 【server】修复 SSH 关联工作空间的授权目录无法取消 +6. 【server】优化 查看分发项目状态取消折叠 table,调整为独立页面 +7. 【server】优化 逻辑节点没有显示快速安装按钮问题(感谢@酱总) +8. 【server】优化 docker TLS 证书全部迁移到证书管理,配置证书支持快捷选择 (感谢@.) +9. 【server】修复 仓库 ssh 协议配置超时时间无法正常拉取代码(感谢@毛毛虫) +10. 【server】优化 环境管理页面支持查看间隔任务统计信息 +11. 【server】优化 令牌导入仓库模块统一调整为模板配置(部分方式不支持搜索)(感谢@魏宏斌) +12. 【agent】优化 DSL 项目报警内容添加状态消息(感谢@核桃) +13. 【server】优化 服务端脚本支持配置全局共享(感谢@酱总) +14. 【server】优化 删除管理脚本中的 `-XX:+AggressiveOpts` 参数 + (感谢 [@牛孝祖](https://gitee.com/niuxiaozu) [Gitee issues I6PUNM](https://gitee.com/dromara/Jpom/issues/I6PUNM) ) +15. 【all】升级 springboot、hutool、fastjson2、svnkit 版本 +16. 【server】修复 资产管理 ssh 分组不生效问题(感谢@A) +17. 【server】优化 构建详情页面布局(构建触发器、查看构建历史) +18. 【server】优化 新增构建状态描述来记录构建异常信息 +19. 【server】优化 构建页面新增卡片布局方式 +20. 【server】修复 SSH 分组无法正常搜索、排序异常(感谢@A) +21. 【server】优化 构建命令支持引用脚本模板内容(便于复杂构建命令管理)(感谢@毛毛虫) +22. 【server】新增 构建状态新增`队列等待`,用于标记当前构建存于线程排队中(感谢@酱总) +23. 【server】修复 创建构建选择命令模板无法修改(感谢@定格) +24. 【server】优化 构建新增配置是否发布隐藏文件属性(感谢@简单) + +### ⚠️ 注意 + +1. 如果节点已经配置过项目文件下载远程地址白名单需要统一配置到服务端的工作空间的白名单。 +2. 已经配置节点项目远程下载白名单将保留只读,不做实际判断 +3. 构建触发器变动,发生异常时 type 为 error,并且新增:statusMsg 字段 + +### ❌ 不兼容功能 + +1. 【agent】取消 节点管理证书管理取消上传编辑功能(保留查询删除功能) +2. 【agent】取消 节点白名单配置取消 ssl 证书路径配置 +3. 【agent】取消 节点项目文件下载远程文件白名单统一调整到服务端白名单配置 + +------ + +## 2.10.37 (2023-03-21) + +### 🐣 新增功能 + +1. 【server】新增 文件中心添加别名码来为文件进行分类下载,构建添加别名码可以同步到文件中心 + (感谢 [@大灰灰大](https://gitee.com/linjianhui) [Gitee issues I6OUC8](https://gitee.com/dromara/Jpom/issues/I6OUC8) ) +2. 【server】新增 服务端在线升级支持配置 beta 计划(”妈妈“再也不用担心没有稳定版了)(感谢@罗俊) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 容器构建 maven 插件版本错误提示可用版本号,如果构建容器已经存在则忽略远程版本(感谢@大灰灰) +2. 【server】优化 脚本列表显示脚本 ID,方便快速查看复制 + (感谢 [@大灰灰大](https://gitee.com/linjianhui) [Gitee issues I6OUDT](https://gitee.com/dromara/Jpom/issues/I6OUDT) ) +3. 【server】优化 文件管理列表显示,小屏幕部分字段被隐藏(感谢@tinsang) +4. 【server】优化 docker 拉取镜像自动解析 tag,避免拉取所有镜像,如果没有配置 tag 默认使用 latest(感谢@Again... .) +5. 【server】修复 数据库迁移到 mysql 报错(字段不存在)(感谢@轩辕豆豆) +6. 【server】修复 节点统计页面错乱问题 + (感谢 [@轩辕豆豆](https://gitee.com/xuanyuandoudou) [Gitee issues I6OYSU](https://gitee.com/dromara/Jpom/issues/I6OYSU) ) + +------ + +## 2.10.36 (2023-03-20) + +### 🐞 解决BUG、优化功能 + +1. 【all】优化 缓存管理统一全局任务刷新 +2. 【server】优化 修复数据关联的工作空间ID sql(避免 '' 或者 'null' 无法修复) +3. 【server】优化 支持手动清理错误工作空间 ID 的数据 +4. 【server】修复 构建 git 仓库无法正常获取问题(感谢@小翼哥) + +------ + +## 2.10.35 (2023-03-20) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 mysql 数据库无法正常加载(感谢@酱总) + +------ + +## 2.10.34 (2023-03-20) + +### 🐣 新增功能 + +1. 【server】新增 资产管理 SSH 管理支持导入导出数据(感谢@吃葫芦娃的土拨鼠) +2. 【server】新增 文件管理中心(用于统一存储管理公共文件) +3. 【server】新增 仓库令牌导入支持 gogs (gogs 和 gitea 标准一致) + (感谢 [@爱琳琳真是太好了](https://gitee.com/qiqi513_admin) [Gitee issues I6CRPS](https://gitee.com/dromara/Jpom/issues/I6CRPS) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 构建 SSH 发布上传文件执行输出上传进度 +2. 【server】优化 在线构建产物支持同步到文件管理中心 +3. 【server】优化 节点分发、在线构建 webhook 添加 `triggerUser` 参数(感谢@酱总) +4. 【server】优化 SSH 文件夹管理支持重命名文件夹(感谢@零壹) +5. 【server】优化 机器名称和 hostname、SSH 机器名称和 hostname 限制字段长度 +6. 【server】优化 DSL 项目支持解析多 PID :`running:109,205:8080,8082`(感谢@酱总) +7. 【server】优化 缓存管理页面支持查看运行中的线程同步器、正在构建的ID +8. 【server】优化 SSH 脚本批量执行采用线程同步器执行(避免线程数大于 CPU 核心数) +9. 【server】优化 构建 SSH 发布命令响应方式调整为逐行(避免长时间没有任何信息输出) +10. 【server】优化 资产管理支持批量分配到工作空间 + +### ⚠️ 注意 + +1. 【server】节点管理和项目管理菜单合并到一个菜单 +2. 【server】节点统计页面合并到逻辑节点中不同视图模式查看 + +### ❌ 不兼容功能 + +1. 【server】取消 低版本(2.9.x 及其一下)的构建触发器 token 自动同步为新版本 + +------ + +## 2.10.33 (2023-03-16) + +### 🐣 新增功能 + +1. 【server】新增 ssh 基础信息监控(非报警监控) +2. 【agent】新增 DSL 项目支持解析端口号:`running:109:8080,8082` + (感谢 [@大灰灰大](https://gitee.com/linjianhui) [Gitee issues I6N35H](https://gitee.com/dromara/Jpom/issues/I6N35H) ) +3. 【server】新增 用户支持自定义工作空间名,排序 (感谢@酱总) +4. 【server】新增 节点分发项目支持排序,设置项目启用/禁用状态(感谢@酱总) +5. 【server】新增 节点分发支持手动释放删除指定项目 +6. 【server】新增 docker 镜像创建容器新增 runtime 参数 + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 插件端在线升级页面无法正常使用(调用到服务端在线升级接口) +2. 【server】优化 节点在线升级统一管理避免出现 null +3. 【server】修复 节点信息编码在部分接口出现 NPE (感谢@酱总) +4. 【server】优化 工作空间中不存在资产管理相关的数据添加默认缺省页(仅管理员显示) +5. 【server】优化 支持手动释放节点项目的分发属性 + +### ❌ 不兼容功能 + +1. 【agent】取消 节点进程列表显示 jpom 项目名 + +### ⚠️ 注意 + +1. 【server】优化 在线工具菜单更名为其他管理 + +------ + +## 2.10.32 (2023-03-14) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 mysql 数据库因为字段长度问题初始化失败(感谢@xuejun) + +------ + +## 2.10.31 (2023-03-14) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 未配置节点编码方式无法正常保存问题(感谢@初凡 ³) + +------ + +## 2.10.30 (2023-03-14) + +### 🐣 新增功能 + +1. 【all】新增 插件端支持配置发送请求消息编码方式(编码、混淆明文、规避防火墙) + (感谢 [@Mr_loyal](https://gitee.com/Mr_loyal) [Gitee pr 179](https://gitee.com/dromara/Jpom/pulls/179) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 导入 gitea 仓库搜索、分页无法正常使用问题 + (感谢 [@Smith](https://gitee.com/autools) [Gitee pr 175](https://gitee.com/dromara/Jpom/pulls/175) [Gitee pr 174](https://gitee.com/dromara/Jpom/pulls/174) ) +2. 【server】优化 镜像启动容器不填写运行命令行导致容器启动失败(部分低版本) + (感谢 [@失落的世界](https://gitee.com/marmotgo) [Gitee pr 176](https://gitee.com/dromara/Jpom/pulls/176) ) +3. 【server】修复 节点分发 webhook 输入框的错别字(感谢 @大灰灰 ) +4. 【server】修复 工作空间环境变量操作日志记录错误问题 +5. 【all】更新 fastjson2 版本 +6. 【all】优化 SSH 命令脚本、服务端脚本、插件端脚本执行参数优化 + (感谢 [@大灰灰大](https://gitee.com/linjianhui) [Gitee issues I6IPDY](https://gitee.com/dromara/Jpom/issues/I6IPDY) ) +7. 【server】优化 导入仓库页面提示信息错乱(感谢@零壹) +8. 【agent】修复 项目修改路径为子目录时 mv 文件触发死循环(感谢@D¹⁹⁹¹) +9. 【server】修复 查询构建日志可能出现 NPE 问题 + (感谢 [@Tom Xin](https://gitee.com/meiMingle) [Gitee issues I6MX9G](https://gitee.com/dromara/Jpom/issues/I6MX9G) ) +10. 【server】优化 系统缓存页面显示当前服务器时间、时区信息 +11. 【server】修复 还原数据后备份状态错误问题 + (感谢 [@lin_yeqi](https://gitee.com/lin_yeqi) [Gitee issues I6MVL7](https://gitee.com/dromara/Jpom/issues/I6MVL7) ) +12. 【agent】修复 DSL 项目状态不判断 jps 命令是否正常(感谢@大灰灰) +13. 【agent】修复 未配置节点白名单时直接创建分发项目报错(感谢@波比) + +### ❌ 不兼容功能 + +1. 【server】删除 COMMAND_INFO 表 type 字段 + +### ⚠️ 注意 + +SSH 命令脚本、服务端脚本、插件端脚本默认参数规则变化:参数描述将必填,默认参数在手动执行时无法删除并且可以查看对应参数描述 + +------ + +## 2.10.29 (2023-03-10) + +### 🐣 新增功能 + +1. 【server】新增 导入仓库支持 `gitea` 系统 + (感谢 [@Smith](https://gitee.com/autools) [Gitee pr 173](https://gitee.com/dromara/Jpom/pulls/173) ) +2. 【server】新增 用户登录日志(取消用户登录生成操作日志的执行日志) +3. 【server】新增 在线工具验证 cron 表达式 (感谢@奇奇) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 SSH 并发执行脚本引起脚本丢失错误(感谢@墨汁) +2. 【server】优化 docker 编辑无法连接提示异常详情信息(感谢@章强) +3. 【agent】优化 节点分发配置白名单到插件端需要验证合法性 +4. 【server】优化 docker 创建容器忽略未配置存储选项参数(感谢@D¹⁹⁹¹) +5. 【server】优化 docker 管理裁剪功能独立菜单 +6. 【server】修复 资产管理未记录操作日志的问题 +7. 【server】优化 操作日志存储用户名、工作空间名字段 +8. 【server】优化 容器构建查询可用标签容器相关提示 +9. 【server】优化 构建历史列表页面在小屏幕数据显示不全 + (感谢 [@一只羊](https://gitee.com/hjdyzy) [Gitee issues I6LLA0](https://gitee.com/dromara/Jpom/issues/I6LLA0) ) +10. 【server】修复 在线构建发布到集群无法正常选择集群服务(感谢@心光) + +------ + +## 2.10.28 (2023-03-08) + +### 🐣 新增功能 + +1. 【agent】新增 项目触发器新增 fileChange 事件(文件变动对应触发点:上传、删除、远程下载、编辑、新增目录或者文件、重命名) + (感谢 [@胡明](https://gitee.com/pig_home) [Gitee issues I6KKEK](https://gitee.com/dromara/Jpom/issues/I6KKEK) ) +2. 【server】新增 镜像创建容器支持配置存储选项(感谢@topsuder、@章强) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 新增 docker 无法使用在线构建功能 + (感谢 [@失落的世界](https://gitee.com/marmotgo) [Gitee issues I6KTLQ](https://gitee.com/dromara/Jpom/issues/I6KTLQ) ) +2. 【server】优化 项目文件列表支持前端排序(文件大小、修改时间) +3. 【server】优化 关闭程序时依次关闭线程池 +4. 【server】优化 工作空间环境变量开放给普通用户编辑 + +### ⚠️ 注意 + +插件端需要同步升级,否则项目文件列表排序无法正常使用 + +------ + +## 2.10.27 (2023-03-06) + +### 🐣 新增功能 + +1. 【server】新增 资产管理新增 docker 、集群管理 + +### 🐞 解决BUG、优化功能 + +1. 【all】升级 springboot 版本 +2. 【server】优化 系统自动同步 docker 已经安装的集群信息 +3. 【server】更新 mysql maven 坐标:`mysql-connector-j` +4. 【server】修复 构建产物模糊匹配二级剔除配置 `/` 无效 + +### ⚠️ 注意 + +新增 docker 资产管理,系统会自动将已经存在的 docker 信息根据 host 去重同步到资产管理中(如果 host +存在多个工作空间将根据最后更新时间排序使用最新的一条数据) + +更新后 docker、集群列表中状态如果出现:`信息丢失` 表示关联数据存在异常不能正常使用,需要删除对应数据重新关联 + +------ + +## 2.10.26 (2023-03-03) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 初始化数据库未删除完整问题(感谢@酱总) +2. 【server】优化 日志阅读选项卡 tab 名称添加项目名称(感谢@tinsang) + +------ + +## 2.10.25 (2023-03-03) + +### 🐣 新增功能 + +1. 【server】新增 构建历史新增产物文件大小 +2. 【all】新增 机器安装 ID 文件(请勿删除数据目录 `INSTALL.json` 文件) +3. 【agent】新增 插件端新增虚拟内存和交互内存监控趋势 + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 构建发布完成,自动删除压缩包文件(节省空间占用大小)(感谢@轩辕豆豆) +2. 【server】修复 更新构建历史环境变量失败 +3. 【server】取消 SSH 脚本命令参数描述(避免误导用户) + (感谢 [@大灰灰大](https://gitee.com/linjianhui) [Gitee issues I6IPDY](https://gitee.com/dromara/Jpom/issues/I6IPDY) ) +4. 【server】优化 编辑项目文件回显错乱问题 +5. 【server】优化 日志阅读菜单更名日志搜索 +6. 【server】优化 差异构建时,触发取消构建标记构建状态为`构建中断` (感谢@张飞鸿) +7. 【server】优化 部分窄下拉框新增 tooltip,避免内容过长无法查看 (感谢@墨汁) + +### ❌ 不兼容功能 + +1. 【server】删除 弃用表 NODE_STAT +2. 【server】删除 弃用表 SYSTEMMONITORLOG +3. 【server】删除 相关表中的 strike 字段 + +------ + +## 2.10.24 (2023-03-01) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 在线构建容器镜像构建参数和镜像标签支持解析环境变量 +2. 【server】优化 替换环境变量,支持 $xxx ${xxx} (感谢@大锅饭集团) +3. 【server】修复 配置节点分发白名单报错 (感谢@酱总) +4. 【server】优化 节点分发配置【配置管理-白名单配置】菜单移动到功能管理中【项目管理-分发白名单】 +5. 【server】修复 非管理员无法使用 SSH 终端问题 + (感谢 [@lilinLue](https://gitee.com/ljlToTlj) [Gitee issues I6IRJV](https://gitee.com/dromara/Jpom/issues/I6IRJV) ) + +### ⚠️ 注意 + +节点分发白名单可能失效,需要重新配置 + +------ + +## 2.10.23 (2023-03-01) + +### 🐣 新增功能 + +1. 【server】新增 控制台输出工作空间关联数据错误未关联的表和条数 +2. 【server】新增 资产管理-SSH管理 +3. 【server】新增 构建 SSH 发布支持配置发布前执行命令 (感谢@daniel) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 使用 ANT 产物目录会自动生成模糊匹配表达式文件夹(感谢@leonchen21) +2. 【server】修复 启动时候未自动触发修复数据逻辑 +3. 【server】修复 SSH 文件管理二级目录以下无法重命名 +4. 【server】优化 SSH 配置授权目录、允许编辑文件后缀、禁止命令移动到资产管理中 +5. 【all】优化 SSH文件、项目文件允许编辑文件的后缀支持配置 * (前提编辑格式统一) +6. 【server】优化 升级 docker-java 、svnkit 依赖版本 +7. 【server】优化 SSH 支持清空隐藏字段 + +### ⚠️ 注意 + +由于新增 SSH 资产管理,之前ssh 配置如果引用的工作空间变量的配置信息可能将失效(作用域不同). +如果仍需要变量信息还需要将对应的信息迁移到全局变量中才可以正常使用 + +------ + +## 2.10.22 (2023-02-24) + +### 🐣 新增功能 + +1. 【server】新增 仓库新增配置超时属性(避免仓库拉取代码超时)(感谢 [@阿超](https://gitee.com/VampireAchao) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 容器构建无法下载产物(感谢@张飞鸿) + +------ + +## 2.10.21 (2023-02-23) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 容器构建自动删除构建容器 +2. 【server】优化 系统管理菜单名:变更为`插件端配置`,`服务端配置` (感谢@ccx2480) +3. 【server】修复 机器管理节点配置同步获取信息错乱(使用到服务端配置)(感谢@ccx2480) + +------ + +## 2.10.20 (2023-02-23) + +### 🐞 解决BUG、优化功能 + +1. 【agent】修复 插件端验证项目白名单路径失败(感谢@ccblandy) + +------ + +## 2.10.19 (2023-02-22) + +### 🐣 新增功能 + +1. 【server】新增 容器构建缓存插件支持按照 `path` 全局缓存 `type: global` +2. 【server】新增 容器构建缓存插件支持缓存 node_modules `mode: copy` + (避免出现:[https://github.com/npm/cli/issues/3669](https://github.com/npm/cli/issues/3669)) +3. 【server】新增 构建列表新增批量构建 + (感谢 [@爱笑的眼睛](https://gitee.com/175cm75kg18cm) [Gitee issues I6GNV2](https://gitee.com/dromara/Jpom/issues/I6GNV2) ) +4. 【server】新增 机器管理新增查看关联节点功能 +5. 【server】新增 机器新增网络、硬件硬盘查看 +6. 【server】新增 机器管理列表新增表格视图 +7. 【server】新增 手动分发文件、构建分发弹窗新增筛选指定项目进行分发 + (感谢 [@Smith](https://gitee.com/autools) [Gitee issues I6GQNG](https://gitee.com/dromara/Jpom/issues/I6GQNG) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 构建读取附件环境变量时机调整到 pull 后 +2. 【agent】优化 白名单路径原样保存(避免部分安全组件拦截) +3. 【server】修复 编辑机器分组名失效问题 +4. 【server】优化 工作空间菜单配置由系统管理移动到工作空间列表管理中 +5. 【server】优化 节点白名单配置分发功能移动到机器管理表格视图中(模板节点) +6. 【server】优化 节点配置分发功能移动到机器管理表格视图中(模板节点) + +------ + +## 2.10.18 (2023-02-20) + +### 🐣 新增功能 + +1. 【server】新增 资产管理->机器管理 +2. 【server】新增 配置属性:jpom.node.stat-log-keep-days(节点统计日志保留天数) +3. 【all】新增 机器节点硬盘信息统计 +4. 【all】新增 机器节点网络流量信息统计 +5. 【server】新增 构建触发器新增获取构建日志接口 + (感谢 [@黑黑](https://gitee.com/c180) [Gitee issues I6G0AT](https://gitee.com/dromara/Jpom/issues/I6G0AT) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】更名 节点列表更名逻辑节点 +2. 【server】修复 节点分发编辑 webhook 字段回显(感谢@酱总) +3. 【server】优化 在线升级统一机器管理(无需切换工作空间) +4. 【server】优化 节点管理>在线升级菜单移动到机器管理中 + +### ❌ 不兼容功能 + +1. 【server】删除 node_info unLockType 字段 +2. 【server】取消 节点解绑功能 +3. 【server】停止 使用 NODE_STAT 表(暂时保留相关数据) +4. 【server】替代 MACHINE_NODE_STAT_LOG 表替代 SYSTEMMONITORLOG 表(并暂时保留 SYSTEMMONITORLOG 数据) + +### ⚠️ 注意 + +由于新增机器管理,程序将自动同步节点表中的所有数据`以节点地址去重`后保存到机器表中,如果同一个节点地址出现多条数据(节点存在不同的工作空间)将跟进节点更新时间最新的为准 + +插件端需要同步更新,否则节点状态、机器状态为:`状态码错误` + +如果更新当前版本后出现节点授权码错误:可能原因是之前同一个机器添加多个节点到不同的工作空间并且最后更新的节点中保存的授权信息是错误,导致数据自动同步后仍然是错误的授权信息 + +------ + +## 2.10.17 (2023-02-16) + +### 🐣 新增功能 + +1. 【server】新增 构建配置新增严格执行命令模式(判断命令执行状态码是否为0) + (感谢@阿克苏市姑墨信息科技有限公司) [Gitee pr 169](https://gitee.com/dromara/Jpom/pulls/169) ) +2. 【server】新增 节点分发新增 webhook 配置属性(感谢@酱总) + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 构建产物配置单属性时,二次匹配不能匹配到文件问题 + (感谢 [@伤感的风铃草](https://gitee.com/bwy-flc) [Gitee issues I6FETS](https://gitee.com/dromara/Jpom/issues/I6FETS) ) +2. 【server】优化 构建历史回滚输出相关操作日志(感谢@酱总) +3. 【server】修复 windows 容器构建无法上传文件到容器问题 + +------ + +## 2.10.16 (2023-02-14) + +### 🐣 新增功能 + +1. 【server】新增 docker 列表支持跨工作空间同步 + (感谢 @[清风柳絮II号](https://gitee.com/zhangfeihong_597) [Gitee issues I6EOIR](https://gitee.com/dromara/Jpom/issues/I6EOIR) ) +2. 【server】新增 构建历史保存构建环境变量(为回滚流程使用) + +### 🐞 解决BUG、优化功能 + +1. 【all】优化 解压工具支持多种编码格式(GBK、UTF8)(感谢@Again... . ) +2. 【server】优化 在线构建新增配置文件环境变量测试(`BUILD_CONFIG_BRANCH_NAME`)(感谢@阿克苏市姑墨信息科技有限公司) +3. 【server】修复 节点分发回滚 NPE (感谢@酱总) +4. 【server】优化 构建弹窗部分下拉支持手动刷新数据(感谢@张飞鸿) + +------ + +## 2.10.15 (2023-02-13) + +### 🐣 新增功能 + +1. 【server】新增 构建 pull 流程之后新增 `BUILD_COMMIT_ID` 变量 +2. 【server】新增 执行脚本输出可用环境变量(服务端脚本、节点脚本、SSH 脚本、在线构建 pull 成功之后、构建事件脚本) +3. 【server】新增 构建确认弹窗新增配置构建环境变量 + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 节点分发二级路径不能删除问题(感谢@张飞鸿) +2. 【agent】优化 服务端环境隐私变量字段传递到插件端(已经存在的插件端环境变量默认为隐私变量) +3. 【agent】修复 DSL 项目模式 status 事件写入日志编码格式跟随系统配置,避免编码格式不正确(已经存在的日志文件可能会乱码,可以删除文件解决) +4. 【server】优化 提前构建加载附加环境变量(startReady 事件) +5. 【agent】优化 节点进程列表、内存、cpu、硬盘加载方式采用 oshi +6. 【server】优化 在线升级页面新版本检测支持本地网络检测 + +### ⚠️ 注意 + +插件端需要同步更新,否则节点首页进程列表数据将不能正常显示 + +------ + +## 2.10.14 (2023-02-10) + +### 🐣 新增功能 + +1. 【server】新增 构建状态新增`构建中断`(执行事件脚本返回中断构建) +2. 【server】新增 构建事件脚本支持返回指定关键词中断构建(需要执行事件脚本输出的最后一行,`interrupt $type`) +3. 【server】新增 构建触发器将请求参数传入构建环境变量(`triggerContentType`、`triggerBodyData`) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 构建产物为文件夹打包位置优化(避免存放位置错乱) +2. 【server】修复 构建触发修改构建产物路径未验证 slip 问题 +3. 【server】优化 本地构建产物模糊匹配(ant path)支持配置截取路径、合并文件 +4. 【server】优化 构建日志输出信息(部分调整为中文、消息标签和级别) +5. 【server】优化 切换工作空间刷新菜单(感谢@ccx2480) +6. 【server】优化 用户密码提示改为弹窗并且可以快捷复制 +7. 【agent】修复 保存 DSL 项目判断是否存在 status 节点,避免无法删除情况(感谢@张飞鸿) +8. 【agent】修复 节点项目修改路径移动文件不生效问题 +9. 【agent】取消 编辑项目校验目录存在情况 +10. 【server】优化 项目ID、节点分发ID 支持前端快捷生成 +11. 【server】优化 构建执行事件脚本描述匹配支持 all 关键词 (匹配所有事件) +12. 【server】修复 执行脚本文件的换行符合跟随系统,避免 windows 中出现异常 +13. 【server】优化 解绑操作提示弹窗更明确(减少误操作)(感谢@酱总) + +### ⚠️ 注意 + +如果使用到产物模糊匹配的请关注是否需要重新调整匹配符。 + +新版本匹配符支持配置三个属性: + +属性1:属性2[可选]:属性3[可选] + +**属性1**:为模糊匹配的表达式 ( `Ant-style` ) + +**属性2**:匹配到的文件保留方式,可用值:`KEEP_DIR`、`SAME_DIR`。(大小写均兼容、配置错误默认为 KEEP_DIR) + +KEEP_DIR: 保留匹配到的文件的文件层级 + +SAME_DIR: 将匹配到的文件均保留到同一个层级(合并到一个文件夹下)。慎用该方式,如果多目录存在相同的文件名会出现合并后只保留匹配到的最后一个文件 + +**属性3**: 需要剔除匹配到多级文件夹的指定目录,(可以配置为空)。建议配合属性2的`KEEP_DIR`使用。剔除目录可以理解为二次过滤前缀匹配文件 + +#### 🌰 举个栗子 + +##### 栗子1: `/web*/**/*.html:KEEP_DIR:/web2/` + +表示匹配执行构建后,对应目录下的:已 web 开头的目录下面的所有 html 文件,并且保留文件夹层级关系,最后发布时候需要剔除 /web2/ + +假设:目录下有如下文件 + +```log +/vue/vue.html +/web/web1.html +/a/b/t.html +/web2/a.html +/web2/b/a.html +/web1/aa/t.html +``` + +执行匹配后的文件 + +```log +a.html +/b/a.html +``` + +##### 栗子2: `/web*/**/*.html:SAME_DIR:` + +表示匹配执行构建后,对应目录下的:已 web 开头的目录下面的所有 html 文件,并且合并文件到同一个目录,最后发布时候需要剔除 +/web2/ + +假设:目录下有如下文件 + +```log +/vue/vue.html +/web/web1.html +/a/b/t.html +/web2/a.html +/web2/b/a.html +/web1/aa/t.html +``` + +执行匹配后的文件 + +```log +web1.html +a.html +t.html +``` + +##### 栗子3: `/web*/**/*.html:KEEP_DIR:` + +表示匹配执行构建后,对应目录下的:已 web 开头的目录下面的所有 html 文件,并且保留文件夹层级关系,最后发布时候按照原目录结构发布 + +假设:目录下有如下文件 + +```log +/vue/vue.html +/web/web1.html +/a/b/t.html +/web2/a.html +/web2/b/a.html +/web1/aa/t.html +``` + +执行匹配后的文件 + +```log +/web/web1.html +/web2/a.html +/web2/b/a.html +/web1/aa/t.html +``` + +------ + +## 2.10.13 (2023-02-08) + +### 🐣 新增功能 + +1. 【server】新增 项目支持配置分组属性,方便项目列表筛选 + (感谢 @[hjk2008](https://gitee.com/hjk2008) [Gitee issues I63PEN](https://gitee.com/dromara/Jpom/issues/I63PEN) ) +2. 【server】新增 节点分发支持配置分组属性,方便列表筛选 +3. 【agent】新增 DSL 项目支持配置自定义备份路径 + (感谢 @[陈旭](https://gitee.com/chenxu8989) [Gitee issues I57ZKJ](https://gitee.com/dromara/Jpom/issues/I57ZKJ) ) + +### 🐞 解决BUG、优化功能 + +1. 【all】修复 linux 无法正常安装 service (感谢@山上雪) +2. 【server】优化 构建的节点分发模式增加二级目录 + (感谢 [@爱琳琳真是太好了](https://gitee.com/qiqi513_admin) [Gitee issues I6DNMX](https://gitee.com/dromara/Jpom/issues/I6DNMX) ) +3. 【server】优化 构建不保留产物时自动删除产物为目录时的压缩包文件 +4. 【server】优化 构建状态等待`节点分发`完成(阻塞执行节点分发) +5. 【server】修复 构建选择`节点分发`并关闭`保留产物`,会导致分发失败。 + (感谢 [@爱琳琳真是太好了](https://gitee.com/qiqi513_admin) [Gitee issues I6DII6](https://gitee.com/dromara/Jpom/issues/I6DII6) ) +6. 【server】修复 构建分发为`节点分发`,产物为文件时导致的不能回滚 + (感谢 [@Smith](https://gitee.com/mrsmith) [Gitee issues I6DNSM](https://gitee.com/dromara/Jpom/issues/I6DNSM) ) +7. 【server】优化 定时构建支持配置禁用表达式,方便临时关闭定时执行 + (感谢 [@阿超](https://gitee.com/VampireAchao) [Gitee issues I6DNBW](https://gitee.com/dromara/Jpom/issues/I6DNBW) ) +8. 【server】修复 DSL 项目配置文件备份数量不生效问题 + +### ⚠️ 注意 + +Linux 环境 已经安装的需要手动更新一下服务管理脚本 + +**服务端**:(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Service.sh https://gitee.com/dromara/Jpom/raw/master/modules/server/src/main/bin/Service.sh +``` + +**插件端** :(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Service.sh https://gitee.com/dromara/Jpom/raw/master/modules/agent/src/main/bin/Service.sh +``` + +------ + +## 2.10.12 (2023-01-29) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 在线终端断开连接时提醒并支持重连 +2. 【server】修复 线程同步器,避免任务过多造成线程数不可控(节点分发相关功能) +3. 【server】优化 前端打包取消 .map 文件,缩少发布包大小 + (感谢 [@金技](https://gitee.com/jinjiG) [Gitee issues I6AK0N](https://gitee.com/dromara/Jpom/issues/I6AK0N) ) +4. 【all】优化 分片上传文件名采用分片序号(伪装文件后缀)(感谢@冷月) +5. 【all】优化 分片上传文件签名由 sha1 改为 md5 提升效率 +6. 【server】优化 构建历史页面鼠标移到名称下拉项显示文字 + (感谢 [@伤感的风铃草](https://gitee.com/bwy-flc) [Gitee pr 167](https://gitee.com/dromara/Jpom/pulls/167) ) +7. 【all】修复 日志监听器 catch 异常日志造成会话未自动删除问题 + (感谢 [@金技](https://gitee.com/jinjiG) [Gitee issues I6A5QW](https://gitee.com/dromara/Jpom/issues/I6A5QW) ) +8. 【server】修复 仓库地址 https 证书验证问题(自动忽略验证) + (感谢 [@arstercz](https://github.com/arstercz) [Github issues 32](https://github.com/dromara/Jpom/issues/32) ) + +### ⚠️ 注意 + +1. 插件端需要同步升级,否则不能正常使用节点上传文件相关功能 + +------ + +## 2.10.11 (2023-01-10) + +### 🐣 新增功能 + +1. 【server】新增 系统缓存新增分片操作数查看 +2. 【server】新增 节点分片上传支持配置并发数:`jpom.node.upload-file-concurrent` + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 迁移数据添加更多日志输出 +2. 【server】优化 分片上传解析文件数据采用分片形式,避免大文件造成浏览器奔溃 +3. 【server】优化 插件端在线升级管理页面错误信息提示由弹窗改到对应节点 +4. 【server】修复 迁移数据出现监控报警记录表字段不全问题 (感谢@loyal) +5. 【server】修复 迁移系统参数表中的 sync_trigger_token 数据重复问题(感谢@loyal) +6. 【server】优化 取消迁移数据忽略处理(避免默认工作空间名称不迁移)(感谢@loyal) +7. 【server】优化 获取项目运行状态失败弹窗提醒改为单条数据异常提醒 +8. 【server】优化 服务端项目管理项目列表获取运行状态改为并发执行,缩短加载时间 +9. 【server】优化 分片上传文件中文件选择器禁用 + +### ❌ 不兼容功能 + +1. 【server】取消 监控记录实体中的 logId 字段 (感谢@loyal) +2. 【all】取消 启动时候判断重复启动 + +------ + +## 2.10.10 (2023-01-09) + +### 🐣 新增功能 + +1. 【all】新增 在线升级是否允许降级操作配置属性`jpom.system.allowed-downgrade` +2. 【server】新增 分发整体状态新增`分发失败` +3. 【server】新增 构建日志显示进度折叠率配置:`jpom.build.log-reduce-progress-ratio` + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 mysql 环境非`allowMultiQueries`初始化表结构失败(感谢@丿幼儿园逃犯) +2. 【server】修复 部分表字段缺失问题(strike) +3. 【server】优化 迁移数据到 mysql 字段大小写跟随实体(感谢@丿幼儿园逃犯) +4. 【server】修复 导入数据库备份文件目录不存在时报错(感谢@丿幼儿园逃犯) +5. 【all】优化 节点上传项目文件采用分片上传、并且支持进度显示 +6. 【all】优化 在线升级上传项目包采用分片上传、并且支持进度显示 +7. 【all】优化 在线升级,默认禁止降级操作 +8. 【server】优化 节点分发上传文件采用分片上传、并且支持进度显示 +9. 【server】优化 分发单项的状态信息存储于日志记录中(取消 json 字段存储) +10. 【server】优化 节点分发子项展示逻辑(同步改异步加载,避免长时间加载) +11. 【server】优化 构建日志输出各个流程耗时 +12. 【server】优化 构建发布项目文件采用分片上传、并且支持进度显示 +13. 【agent】优化 配置文件中上传文件大小限制由 1G 改为 10MB 节省插件端占用内存大小(采用分片代替) +14. 【server】优化 手动上传的节点分发文件将自动删除,节省存储空间 +15. 【server】优化 节点分发日志支持显示进度信息 + +### ⚠️ 注意 + +1. 插件端需要同步升级,否则节点分发项目无法显示项目名称 +2. 插件端需要同步升级,否则会出现部分接口 404 或者参数不正确的情况 +3. 建议升级验证上传项目文件无问题后,将插件端上传文件大小限制配置属性大改小 + 1. spring.servlet.multipart.max-file-size=5MB + 2. spring.servlet.multipart.max-request-size=20MB + +**如果需要使用 mysql 存储,则需要修改配置** + +1. 修改 `jpom.db.mode` 为 `MYSQL` +2. 修改 `jpom.db.url` 为您 mysql 的 jdbc 地址( jdbc:mysql://127.0.0.1: + 3306/jpom?useUnicode=true&characterEncoding=UTF-8&useSSL=false) +3. 修改 `jpom.db.user-name` 为对应 mysql 账户 +4. 修改 `jpom.db.user-pwd` 为对应 mysql 密码 + +如果您需要迁移之前 h2 数据库中的数据到 mysql(需要先将 mysql 的连接信息配置好后才能迁移) + +```shell +bash ./bin/Server.sh restart -15 --h2-migrate-mysql --h2-user=jpom --h2-pass=jpom + +``` + +------ + +## 2.10.9 (2023-01-06) + +### 🐣 新增功能 + +1. 【server】新增 服务端数据存储支持 mysql + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 在线编辑配置文件报错并修改数据库密码问题 +2. 【server】~~三次修复~~ 在线终端输入部分字符后自动断开连接问题 +3. 【server】升级 svnkit 依赖版本 +4. 【server】优化 docker 标签查询精准查询 +5. 【server】更名 阅读文件更名为跟踪文件 + +### ❌ 不兼容功能 + +1. 【server】删除 数据库中多个数据表中弃用字段 + +### ⚠️ 注意 + +如果需要使用 mysql 存储,则需要修改配置: + +1. 修改 `jpom.db.mode` 为 `MYSQL` +2. 修改 `jpom.db.url` 为您 mysql 的 jdbc 地址( jdbc:mysql://127.0.0.1: + 3306/jpom?useUnicode=true&characterEncoding=UTF-8&useSSL=false) +3. 修改 `jpom.db.user-name` 为对应 mysql 账户 +4. 修改 `jpom.db.user-pwd` 为对应 mysql 密码 + +如果您需要迁移之前 h2 数据库中的数据到 mysql(需要先将 mysql 的连接信息配置好后才能迁移) + +```shell +bash ./bin/Server.sh restart -15 --h2-migrate-mysql --h2-user=jpom --h2-pass=jpom + +``` + +------ + +## 2.10.8 (2023-01-05) + +### 🐞 解决BUG、优化功能 + +1. 【all】优化 程序运行的 tmp 文件夹(`java.io.tmpdir`)跟随项目目录 +2. 【all】优化 判断目录越级 `checkSlip` 目录转义至 tmpdir,避免在用户目录生成空白文件夹 + +### ❌ 不兼容功能 + +1. 【all】取消 程序启动写入全局临时信息 +2. 【server】取消 服务端没有节点自动探测本地节点功能 + +### ⚠️ 注意 + +Linux、Windows 环境 已经安装 2.10.0 ~ 2.10.7 的需要手动更新一下管理脚本 + +> 建议先更新脚本再升级插件端或者服务端 > -> > 1. 在 Server 端找到 Server.sh 文件,执行命令 `./Server.sh create`,会在当前目录下生成 jpom-server 文件,这个文件就是 Server 端的自启动的文件 -> > 2. 在 Agent 端找到 Agent.sh 文件,执行命令 `./Agent.sh create`,会在当前目录下生成 jpom-agent 文件,这个文件就是 Agent 端的自启动的文件 -> > 3. 把刚刚生成的自启动文件移动到 /etc/init.d/ 目录 -> > 4. 到 /etc/init.d/ 目录让自启动文件拥有执行权限,执行命令 `chmod +x jpom-server` 或者 `chmod +x jpom-agent` -> > 5. 注册到 chkconfig 列表里面,就可以实现开机自启,执行命令 `chkconfig --add jpom-server` 或者 `chkconfig --add jpom-agent` -> > 6. 执行完第 4 步就可以通过 `service jpom-xxx {status | start | stop}` 来管理 Jpom 服务 -> > 7. 目前仅通过 Cent OS 服务器测试,其他服务器可能会无效 +> Windows 用户需要自行下载脚本替换 ------------------------------------------------------------ +**服务端**:(需要到安装目录的 bin 下执行) -# 2.5.0 +```shell +curl -LfsSo Server.sh https://gitee.com/dromara/Jpom/raw/master/modules/server/src/main/bin/Server.sh +``` -### 新增功能 +**插件端** :(需要到安装目录的 bin 下执行) -1. 【server】接入全局 loading 控件 -2. 【server】默认进入新版UI +```shell +curl -LfsSo Agent.sh https://gitee.com/dromara/Jpom/raw/master/modules/agent/src/main/bin/Agent.sh +``` + +------ -### 解决BUG、优化功能 +## 2.10.7 (2023-01-04) -1. 【Server】fix bug: ssh 列表页面编辑弹窗无法加载(当没有设置文件目录时) -2. 【Server】fix bug: 分发列表,项目运行状态显示错误 -3. 【Server】fix bug:第一次安装未能正常打开初始化账号密码页面 -4. 【server】fix bug: 独立分发项目编辑时,jvm args 等参数不会回显 -5. 【server】fix: 点击构建自动打开构建日志、构建日志弹窗自动滚动到底部 -6. 【server】add: index.html 添加打包时间 -7. 【server】fix bug:添加、编辑用户原始密码进行了sha1 -8. 【server】add: 添加构建历史回滚操作(感谢@李道甫) -9. 【server】add: 添加项目文件管理页面上传压缩文件(感谢@李道甫) -10. 【server】fix bug: 文件上传时显示上传进度(感谢@李道甫) -11. 【server】fix bug: 项目文件管理的侧边文件树优化(感谢@李道甫) -12. 【server】fix: 控制台日志弹窗自动滚动到底部(感谢@南有乔木) -13. 【server】add: File方式创建项目 项目控制台互调(感谢@李道甫 贡献) -13. 【server】add: 分发提示修改 分发项目显示 (感谢@李道甫 贡献) +### 🐣 新增功能 -> 注意:目前新版本登录状态采用固定 token 模式,登录后将一直保持在线状态,如需要退出或者离线需要进行退出登录操作。(该问题将于后面版本进行优化调整) +1. 【server】新增 配置管理新增配置目录在线编辑功能 +2. 【server】新增 容器构建新增 `ubuntu-git` 镜像 ------------------------------------------------------------ +### 🐞 解决BUG、优化功能 -# 2.4.0 ~ 2.4.9 版本日志 +1. 【server】修复 在线终端输入部分字符后自动断开连接问题(感谢 @Again.... ) +2. 【server】修复 执行 SSH 脚本未正常加载环境变量问题 +3. 【server】修复 快速安装(绑定)插件端的命令特殊字符转义问题 (感谢@张飞鸿) +4. 【server】优化 节点在线升级确认操作提醒要升级的目标版本号(感谢@木迷榖) +5. 【server】优化 modal 弹窗新增 destroyOnClose , 优化页面卡顿和组件样式冲突 +6. 【server】修复 windows nginx 配置文件编辑白名单路径非绝对路径时出现名称错误 -[https://gitee.com/dromara/Jpom/blob/master/docs/changelog/2.4.x.md](https://gitee.com/dromara/Jpom/blob/master/docs/changelog/2.4.x.md) +### ❌ 不兼容功能 + +1. 【server】下架 构建配置管理功能(请使用配置目录管理功能代替) + +------ ------------------------------------------------------------ +## 2.10.6 (2022-12-29) -# 2.3.1 ~ 2.3.2 版本日志 +### 🐣 新增功能 -[https://gitee.com/dromara/Jpom/blob/master/docs/changelog/2.3.x.md](https://gitee.com/dromara/Jpom/blob/master/docs/changelog/2.3.x.md) +1. 【agent】新增 上传项目文件,下载远程文件 压缩包支持自动剔除文件夹 +2. 【server】新增 节点分发新增手动取消分发任务功能 + (感谢 [@gxw](https://gitee.com/yinxianer) [Gitee issues I61SBB](https://gitee.com/dromara/Jpom/issues/I61SBB) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 SSH 终端 JSCH 新增日志实现,方便排查问题 +2. 【agent】优化 部分下载接口取消返回值,避免控制台出现错误日志 +3. 【server】优化 服务端代理插件端的 websocket 超时问题 +4. 【server】修复 在线终端输入部分字符后自动断开连接问题(感谢 @Again.... ) +5. 【server】修复 部分下拉框无法正常搜索文件(感谢 @Again.... ) +6. 【agent】优化 同时上传相同的文件名时可能异常 +7. 【server】优化 节点分发状态新增(等待分发、手动取消状态) +8. 【server】修复 状态为未分发时分发失败引起的状态错误 + +------ + +## 2.10.5 (2022-12-27) + +### 🐣 新增功能 + +1. 【server】新增 操作日志新增数据名称字段 + +### 🐞 解决BUG、优化功能 + +1. 【agent】修复 项目文件夹不存在时不能下载远程文件 +2. 【all】升级 fastjson 升级为 fastjson2 +3. 【all】升级 SpringBoot 2.7.7 、commons-compress +4. 【server】移除 空闲依赖 jaxb-api +5. 【all】优化 启动加载流程,保存顺序加载 +6. 【all】修复 启动成功写入全局信息由于没有权限造成的异常 + (感谢 [@LeonChen21](https://gitee.com/leonchen21) [Gitee issues I67C3C](https://gitee.com/dromara/Jpom/issues/I67C3C) ) +7. 【server】优化 websocket 控制台操作日志记录 +8. 【server】修复 超级管理的 websocket 操作日志记录工作空间不正确 +9. 【agent】优化 插件端删除 spring-boot-starter-websocket 依赖 +10. 【server】优化 服务端删除 Java-WebSocket 依赖(采用统一模块管理) +11. 【server】修复 更新构建状态互斥,避免状态被异步更新冲突 +12. 【server】优化 下载文件采用标签页面形式取消 blob + +### ❌ 不兼容功能 + +1. 【server】取消 兼容低版本插件端的 websocket 授权信息传输方式(低版本插件端请同步升级到最新) +2. 【server】取消 服务端取消向插件端传递操作人的用户名 +3. 【server】取消 服务端数据库用户操作日志表对 REQID 字段兼容(2.9.1 以下) + +------ ------------------------------------------------------------ +## 2.10.4 (2022-12-23) -# 2.0 ~ 2.2 版本日志 +### 🐞 解决BUG、优化功能 + +1. 【all】修复 linux 管理脚本中的 pid 文件内容与真实进程不一致问题 +2. 【all】恢复 linux 管理脚本支持创建服务管理 + +### ⚠️ 注意 + +Linux 环境 已经安装 2.10.3 ~ 2.10.0 的需要手动更新一下管理脚本 + +> 需要`创建服务来管理`的需要更新后才能正常使用在线升级和保存配置并重启 + +> 建议先更新脚本再升级插件端或者服务端 + +**服务端**:(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Server.sh https://gitee.com/dromara/Jpom/raw/master/modules/server/src/main/bin/Server.sh +``` + +```shell +curl -LfsSo Service.sh https://gitee.com/dromara/Jpom/raw/master/modules/server/src/main/bin/Service.sh +``` + +**插件端** :(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Agent.sh https://gitee.com/dromara/Jpom/raw/master/modules/agent/src/main/bin/Agent.sh +``` + +```shell +curl -LfsSo Service.sh https://gitee.com/dromara/Jpom/raw/master/modules/agent/src/main/bin/Service.sh +``` + +------ + +## 2.10.3 (2022-12-22) + +### 🐣 新增功能 + +1. 【server】新增 在线构建新增 `packageFile` 流程 编译 webhook 或者事件脚本调用 + +### 🐞 解决BUG、优化功能 + +1. 【server】修复 快速导入节点工作空间id `undefined` +2. 【server】修复 本地运行脚本默认找不到的情况 +3. 【agent】优化 项目控制台日志文件默认编码格式判断系统 windows 默认 GBK,其他默认 UTF-8 + (感谢 [@gf_666](https://gitee.com/gf_666) [Gitee issues I66ZZZ](https://gitee.com/dromara/Jpom/issues/I66ZZZ) ) +4. 【server】优化 在线构建 ssh 清空产物异常不标记发布异常 + +### ⚠️ 注意 + +Linux 环境 已经安装 2.10.2 ~ 2.10.0 的需要手动更新一下管理脚本,之前管理脚本存在部分场景日志输出错乱的问题 + +> 建议先更新脚本再升级插件端或者服务端 + +**服务端**:(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Server.sh https://gitee.com/dromara/Jpom/raw/master/modules/server/src/main/bin/Server.sh +``` + +**插件端** :(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Agent.sh https://gitee.com/dromara/Jpom/raw/master/modules/agent/src/main/bin/Agent.sh +``` + +------ + +## 2.10.2 (2022-12-21) + +### 🐞 解决BUG、优化功能 + +1. 【server】节点快速安装命令示例提供默认安装命令 +2. 【server】修复 docker 插件未正常加载问题(感谢@顺子) +3. 【server】优化 本地构建命令执行方式由逐行改为脚本执行 +4. 【server】修复 构建未配置 webhook 控制台报错 +5. 【server】修复 构建未配置 webhook 不触发事件脚本 + +### ❌ 不兼容功能 + +1. 【server】下架 SSH 上传文件安装插件端方式,采用快速安装命令代替 +2. 【server】取消 构建命令和本地命令发布 不支持 #{} 变量替换 +3. 【server】取消 SSH 命令模板 不支持 #{} 变量替换(仅支持 ${} 替换) + +------ + +## 2.10.1 (2022-12-20) + +### 🐣 新增功能 + +1. 【server】新增 节点项目支持快速复制操作 + (感谢[@mt-mored](https://gitee.com/mt-mored) [Gitee issues I653O3](https://gitee.com/dromara/Jpom/issues/I653O3) ) +2. 【all】新增 节点项目、独立节点分发支持彻底删除 +3. 【agent】新增 DSL 项目模式执行脚本支持节点环境变量 + (感谢[@苏生不语](https://gitee.com/sushengbuyu) [Gitee issues I66MNP](https://gitee.com/dromara/Jpom/issues/I66MNP) ) +4. 【all】新增 构建项目发布、节点分发支持配置发布前先停止(避免 windows 环境文件被占用) + (感谢 [@yiziyu](https://gitee.com/yiziyu) [Gitee issues I65MS1](https://gitee.com/dromara/Jpom/issues/I65MS1)、[@all-around-badass](https://gitee.com/all-around-badass) [Gitee issues I66PYU](https://gitee.com/dromara/Jpom/issues/I66PYU) ) + +### 🐞 解决BUG、优化功能 + +1. 【server】优化 节点分发菜单更名为项目管理 +2. 【server】优化 节点分发添加项目限制数量由 2 调整为 1 + (感谢[@苏生不语](https://gitee.com/sushengbuyu) [Gitee issues I66R73](https://gitee.com/dromara/Jpom/issues/I66R73) ) +3. 【server】修复 节点分发手动上传文件二级目录出现 `undefined` +4. 【agent】修复 默认项目模式执行命令存在 `null` 字符串 +5. 【server】修复 初次安装服务端初始化数据库失败问题 (感谢@lg) +6. 【server】优化 日志显示组件(取消正则搜索),日志删除 `ansi` 颜色 + (感谢[@苏生不语](https://gitee.com/sushengbuyu) [Gitee issues I657JR](https://gitee.com/dromara/Jpom/issues/I657JR) ) +7. 【server】优化 编辑组件可能出现行错和内容错乱问题 +8. 【server】优化 查看系统日志的多次切换内容返回错乱问题 + +### ❌ 不兼容功能 + +1. 【agent】取消 DSL 项目脚本的 #{} 替换变量 + +### ⚠️ 注意 + +Linux 环境 已经安装 2.10.0 的需要手动更新一下管理脚本,2.10.0 管理脚本存在在线升级和在线重启日志输出重复问题 + +> 建议先更新脚本再升级插件端或者服务端 + +**服务端**:(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Server.sh https://gitee.com/dromara/Jpom/raw/master/modules/server/src/main/bin/Server.sh +``` + +**插件端** :(需要到安装目录的 bin 下执行) + +```shell +curl -LfsSo Agent.sh https://gitee.com/dromara/Jpom/raw/master/modules/agent/src/main/bin/Agent.sh +``` + +------ + +## 2.10.0 (2022-12-19) + +### 🐣 新增功能 + +1. 【all】外置 `logback` 配置文件 +2. 【server】服务端管理相关功能独立页面菜单 +3. 【server】新增项目触发器用于管理项目状态 +4. 【all】新增 构建项目发布支持配置发布到二级目录 +5. 【server】新增 节点分发发布支持配置发布到二级目录 + +### 🐞 解决BUG、优化功能 + +1. 【all】启动相关信息由控制台输出改为 `logback` +2. 【all】节点管理中 `其他功能` 菜单更名为 `脚本管理` +3. 【all】优化版本升级修改管理脚本里变量,采用文件记录方式 +4. 【server】优化容器启动脚本,支持监听进程已经终端重启操作 +5. 【server】修复 自动刷新页面已经关闭的标签页,后台仍然在发送请求 + (感谢[@苏生不语](https://gitee.com/sushengbuyu) [Gitee issues I664OP](https://gitee.com/dromara/Jpom/issues/I664OP) ) +6. 【server】修正触发器说明错别字 + +### ❌ 不兼容功能 + +1. 【server】取消支持 2.8.0 以下 json 文件转存数据库 +2. 【all】下架 JDK 管理模块(请使用 DSL 项目模式代替) +3. 【all】下架 TOMCAT 管理模块(请使用 DSL 项目模式代替) +4. 【all】删除 项目内存监控页面 +5. 【all】配置文件名称由 `extConfig.yml` 变更为 `application.yml` +6. 【all】调整项目打包目录结构 +7. 【all】取消兼容低版本数据目录文件迁移(调试运行) +8. 【all】取消自动识别文件编码格式模块 `auto-charset-jchardet` +9. 【all】更新管理脚本,进程标识更新(已经存在的需要手动停止) +10. 【all】取消插件端配置化向服务端注册功能(采用快速导入方式替代) +11. 【server】取消服务端授权 token 配置 +12. 【all】下架 节点脚本导入功能 +13. 【server】取消限制创建用户最大数配置:`user.maxCount` +14. 【server】删除 node_info 表 cycle 字段 +15. 【agent】删除项目回收记录功能 + +### ❌ 不兼容的属性配置变更 + +> 属性配置支持驼峰和下划线 + +1. 【agent】`whitelistDirectory.checkStartsWith` -> `jpom.whitelist-directory.check-starts-with` +2. 【agent】`project.stopWaitTime` -> `jpom.project.statusWaitTime` +3. 【agent】`project.*` -> `jpom.project.*` +4. 【agent】修正拼写错误 `log.*back*` -> `jpom.project.log.*backup*` +5. 【agent】`log.*` -> `jpom.project.log.*` +6. 【agent】`log.intiReadLine` -> `jpom.init-read-line` +7. 【agent】 `log.autoBackConsoleCron` 不支持配置 none (none 使用 `jpom.project.log.autoBackupToFile` 代替) +8. 【all】删除 `consoleLog.reqXss` 、`consoleLog.reqResponse` +9. 【all】`consoleLog.charset` -> `jpom.system.console-charset` +10. 【server】`node.uploadFileTimeOut` -> `jpom.node.uploadFileTimeout` +11. 【server】`system.nodeHeartSecond` -> `jpom.node.heartSecond` +12. 【server】`user.*` -> `jpom.user.*` +13. 【server】`jpom.authorize.expired` -> `jpom.user.tokenExpired` +14. 【server】`jpom.authorize.renewal` -> `jpom.user.tokenRenewal` +15. 【server】`jpom.authorize.key` -> `jpom.user.tokenJwtKey` +16. 【server】`jpom.webApiTimeout` -> `jpom.web.api-timeout` +17. 【server】删除 `ssh.initEnv` +18. 【server】批量修正前端相关配置属性均修改到 `jpom.web.*` +19. 【server】`db.*` -> `jpom.db.*` +20. 【server】`build.*` -> `jpom.build.*` + +### ⚠️ 注意 + +> 此版本为不兼容升级,需要手动升级修改相关配置才能正常使用 + +#### 简洁的升级流程 + +1. 停止正在运行的程序插件端或者服务端 +2. 备份已经存在的插件端或者服务端的数据目录 +3. 手动安装新版本 `2.10.0+` +4. 还原数据:将备份的数据目录迁移到新安装的数据目录(需要再未运行的状态下操作) +5. 重启程序 + +详细的升级文档:[https://jpom.top/pages/upgrade/2.9.x-to-2.10.x/](https://jpom.top/pages/upgrade/2.9.x-to-2.10.x/) + +------ -[https://gitee.com/dromara/Jpom/blob/master/docs/changelog/2.x.md](https://gitee.com/dromara/Jpom/blob/master/docs/changelog/2.x.md) \ No newline at end of file diff --git a/CLA.md b/CLA.md new file mode 100644 index 0000000000..e2ef18753d --- /dev/null +++ b/CLA.md @@ -0,0 +1,86 @@ +# Jpom 贡献者许可协议 + +感谢您对 Jpom 项目(“社区”或者“我们”)拥有及/或管理的开源项目(“项目”)的关注,请完整、仔细、充分阅读本《贡献者许可协议》(“协议”)后,在对协议项下条款均无异议的前提下,签署本协议。 + +**提交贡献即认为签署了本协议,若对本协议有异议,请勿提交贡献。** + +为确保社区及项目合法合规,同时也避免侵犯任何人士的合法权益,我们希望确保: +(1)您对您所提交的贡献具有完整充分的合法权利; +(2)您的提交行为合法且不侵权。 + +您在提交贡献后,仍然对贡献享有应有的合法权利。您不会因为提交贡献而承担任何未约定的义务。 + +## 一、定义 + +为本协议之目的,除非上下文另有说明,本协议中用语分别具有本条所指含义: +1.1 “中国”指中华人民共和国。 + +1.2 “法律”包括但不限于任何适用的法典、法律、法规、规章、司法解释及规范性文件,包括但不限于《中华人民共和国著作权法》(“著作权法”)、《中华人民共和国计算机软件保护条例》、《中华人民共和国专利法》(“专利法”)。如无特别说明或者其他约定,本协议项下争议优先适用中国法律。 + +1.3 “社区”或“我们”指 Jpom 项目。 + +1.4 “项目”指社区拥有及/或管理的项目,包括但不限于软件、软件所包含的程序、代码、文字、图片、图形、文档或者其他信息。于本协议签署之日,项目托管于Gitee [https://gitee.com/dromara/Jpom](https://gitee.com/dromara/Jpom),托管地址今后可能随实际情况发生变化,具体访问地址以社区实时公布为准。 + +1.5 “贡献者”指签署本协议并向社区提交贡献的任何自然人、法人、其他组织以及法律规定的其他主体。 + +1.6 “贡献”指由贡献者根据本协议向社区提交的任何程序、代码、文字、图片、图形、文档或者其他作品。 + +1.7 “提交”指将“贡献”通过电子邮件或者其他形式发送给“项目”或者“社区”,其他形式包括但不限于讨论、在“项目”相关的电子邮件列表上的交流、在“项目”相关的源代码修订控制、问题跟踪等系统中的操作,但不包括以书面方式明确标记为“非贡献”的交流。 + +1.8 “著作权”指《著作权法》规定的各项权利;在任何不适用中国法律的争议中,应指包括《保护文学艺术作品伯尔尼公约》、《世界知识产权组织版权公约》、《与贸易有关的知识产权协定》等国际公约及争议准据法项下规定在内的全部可适用权利。 + +1.9 “专利权”指《专利法》规定的各项权利;在任何不适用中国法律的争议中,应指包括《保护工业产权巴黎公约》、《与贸易有关的知识产权协定》等国际公约及争议准据法项下规定在内的全部可适用权利。 + +1.10 “合法权利”包括但不限于著作权、版权、专利权、商标权、隐私权以及法律规定的其他权利。 + +1.11 “作品”指《著作权法》规定的在文学、艺术和科学领域内具有独创性并能以一定形式表现的智力成果以及《专利法》规定的产品、方法或者其改进所提出的新的技术方案、对产品的形状、构造或者其结合所提出的适于实用的新的技术方案、对产品的整体或者局部的形状、图案或者其结合以及色彩与形状、图案的结合所作出的富有美感并适于工业应用的新设计。在任何不适用中国法律的争议中,应指与著作权及专利权相对应的全部可适用标的。 + +1.12 “原创作品”指贡献者作为作者创作、开发的作品或者发明创造。 + +1.13 “职务作品”指贡献者作为作者创作、开发但依法属于《著作权法》规定的职务作品的作品或者《专利法》规定的职务发明创造。 + +1.14 “合作作品”指贡献者作为作者之一创作、开发但依法属于《著作权法》规定的合作作品的作品或者《专利法》规定的合作完成的发明创造。 + +1.15 “委托作品”指贡献者作为作者创作、开发但依法属于《著作权法》规定的委托作品的作品或者《专利法》规定的委托完成的发明创造。 + +1.16 “非原创作品”指贡献者以外的人创作、开发的作品。为免歧义,贡献者作为作者之一参与的合作作品属于原创作品,但作为贡献提交时,贡献者应取得合作作者的合法授权。 + +## 二、著作权许可授权 + +2.1 贡献一经提交,即视为贡献者免费且不可撤销地授予社区、项目及其全部或者部分的接收者永久、非排他、全球性的著作权许可,修改、复制、发行、传播、改编、注释、整理、汇编、开发、展示、分发、再许可或以其他合法方式使用贡献及使用贡献产生的成果。 + +## 三、专利权许可授权 + +3.1 贡献一经提交,即视为贡献者免费且不可撤销地授予社区、项目及其全部或者部分的接收者永久、非排他、全球性的专利权许可,制造、委托制造、销售、许诺销售、进口或以其他合法方式使用贡献及使用贡献产生的成果。 + +## 四、原创及不侵权承诺 + +4.1 除严格按第4.4条要求明确标记为非原创的情形之外,贡献者承诺所提交的贡献完全为原创作品,不侵害他人的合法权利,且无以下任何一种情况: + +  4.1.1 属于职务作品,单位未签署本协议及/或未同意贡献者提交; + +  4.1.2 属于合作作品,合作作者未签署本协议及/或未同意贡献者提交; + +  4.1.3 属于委托作品或者著作权、专利权已经对外转让,著作权、专利权的实际持有人未签署本协议及/或未同意贡献者提交。 + +4.2 如存在第4.1条任何一种情形的,贡献者应先取得相应授权后再进行提交: + +  4.2.1 如有第4.1.1条的情形,贡献者应事先取得单位的授权; + +  4.2.2 如有第4.1.2条的情形,贡献者应事先取得合作作者的授权; + +  4.2.3 如有第4.1条的情形,贡献者应事先取得著作权、专利权实际持有人的授权。 + +4.3 在遵守本第五条约定的基础上,单位作为贡献者签署本协议的,可以授权指定人士以单位名义、单位员工名义或者个人名义提交贡献。 + +4.4 如贡献者提交的贡献为非原创作品,则贡献者应当明确标记出非原创部分,并完整列出原创者及/或著作权、专利权持有者的名称。 + +4.5 除其他条款约定外,如贡献者提交的贡献在使用时可能涉及任何第三方权利,则贡献者还应当在提交时予以说明,并披露该等限制的完整详细信息。 + +4.6 请留意:社区希望保护贡献者的合法权益,免受任何人士的侵犯;社区也倡导尊重原创、保护知识产权,不允许侵犯他人的合法权益。如贡献者提交贡献时,冒名、盗用或者擅自提交他人享有著作权或其他合法权利的作品或者以其他方式侵犯他人合法权益,导致社区承担责任和损失的,社区保留追究责任的权利。 + +## 五、贡献者权利保护 + +5.1 贡献者签署本协议不应视为放弃原创者身份或署名权。本协议不影响贡献者将其贡献合法用于其他任何目的的权利,社区或者任何其他人士亦不应凭借本协议要求贡献者放弃其他任何合法权利。 + +5.2 贡献者签署本协议不应视为承诺对其提交的贡献承担后续支持、维保、适用性、品质保证等义务。社区或者任何其他人士亦不应凭借本协议要求贡献者承担任何未约定的义务。 \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..0e502fd208 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,116 @@ +# Jpom 贡献说明 + +## 目录说明 + +``` +. +├── .gitee => gitee 配置 +├── docs => 一键安装的命令脚本以及版本号文件 +├── modules => java 后端目录(agent、server) +   ├── agent => 插件端代码 +   ├── common => 这个项目的公共模块(插件端、服务端都依赖该模块) +   ├── server => 服务端代码 +   ├── sub-plugin => 插件模块 +├── script => 一些通用脚本 +├── web-vue => 前端 vue 目录 +   ├── .editorconfig => 前端(vue)代码格式配置 +├── .editorconfig => 全局代码格式配置 +├── .gitattributes => 文件编码格式配置 +└── .... => 仓库一些默认配置 +``` + +## 一些规范说明 + +1. 写完代码后在保证不影响其他的人的代码情况下尽量统一格式化一下代码 + 1. 采用 4 个空格缩进,禁止使用 tab 字符 + 2. 如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时, + 请勿勾选 Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs +2. Java 代码需要保证新增方法都有充足、标准的 JavaDoc 注释 +3. 在修改 Bug、新增功能尽量保证最小提交的方式提交代码,减少多个功能一个 commit +4. 所有接口 url 都需要遵循下划线模式 +5. Java 代码、方法需要遵循小驼峰法 +6. Java 类名需要遵循大驼峰法 +7. 前端项目统一采用 `prettier` 方式来格式化(需要安装插件) +8. 所有 controller 层的接口都需要添加文档注释(至少包含接口的作用说明、参数说明、返回值说明及添加 apiDoc 文档注释) + +> 注:由于旧代码存在很多不规范问题,会逐步调整为新规范。在新写的代码都需要需要遵循上面说明 +> +> +### 类的文档注释规范(Javadoc) + +``` +/** + * xxxxxxxx + * @author xxxx + * @since ${DATE} + */ +``` + +> 这里采用 `@since` 声明创建日期是因为 `Javadoc` 规范里面并没有 `@date` 标记所以采用 `@since` 代替 + +### Java 代码规范 + +> 推荐安装 `Alibaba Java Coding Guidelines`(`p3c`) 插件 + +##### 代码级别的多行注释 + +[https://www.e-learn.cn/topic/3680721](https://www.e-learn.cn/topic/3680721) + +## changelog 更新规范 + +> 在新加功能、修复bug、优化功能在完成时候都需要在 [CHANGELOG.md](./CHANGELOG.md) 记录 + +1. 如果是使用者反馈的bug,在修复后需要备注反馈人的昵称 +2. 如果是 issue 需要备注 issue 地址以及平台(Gitee、GitHub) +3. 如果是 pr 需要备注 pr 地址以及平台(Gitee、GitHub) +4. 根据变动情况确定影响范围:如果影响 只:`agent`、`server` 其中一个,就使用【agent】、【server】开头,如果都影响就不用 +5. 可以视情况添加其他说明:如提交记录 +6. emoji 表情参考:[https://emojixd.com/](https://emojixd.com/) + +## apiDoc 文档注释规范 +### 【强制】所有需要包含在 apiDoc 文档中的接口,都必须有 `@api` 文档标记 +说明:如果没有 `@api` 文档标记,则定义的文档不会出现在生成后的 apiDoc 文档中。 + +### 【强制】所有 apiDoc 的文档标记必须定义在 javaDoc 标记的后面 +说明:如果先定义 javaDoc 文档标记,再定义 apiDoc 的文档标记,则 javaDoc 的标记可能会包含在 apiDoc 的标记属性中,这并不是我们想要的结果。 + +正例: +``` +/** +* @author hjk +* @api {method} path title +* @apiParam {Number} id Users unique ID. +*/ +``` + +反例: + +说明:参数 id 的说明应该是 Users unique ID. 如果这样定义则变成了 Users unique ID.@author hjk +``` +/** +* @api {method} path title +* @apiParam {Number} id Users unique ID. +* @author hjk +*/ +``` + +### 【强制】定义通用文档块 + +说明:使用 `@apiDefine` 定义通用的文档块,然后使用 `@apiUse` 来引用,增强文档块的复用性。 + +所有的文档块统一定义在 `server` 模块下的 `org.dromara.jpom.ApiDoc` + + +## 分支说明 + +1. 新功能都提交到 dev 分支, 不能提交到 master 分支 +2. PR 提交到 dev 分支 +3. 一般功能开发可以直接提交到 dev 分支,较大功能开发需要新建分支提交 + +## 需要的小组 + +1. 后端小组 (主要任务:根据需求开发对应的接口) +2. 前端小组 (主要任务:优化前端 UI 交互和对接部分接口) +3. 文档小组 (主要任务:完善、补充 Jpom 使用文档) +4. 视频小组 (主要任务:录制 Jpom 相关的使用视频) +5. 测试小组 (主要任务:参与 Jpom 新版内测、日常开发测试相关任务) \ No newline at end of file diff --git a/HEADER.txt b/HEADER.txt new file mode 100644 index 0000000000..017f46077c --- /dev/null +++ b/HEADER.txt @@ -0,0 +1,7 @@ +Copyright (c) 2019 Of Him Code Technology Studio +Jpom is licensed under Mulan PSL v2. +You can use this software according to the terms and conditions of the Mulan PSL v2. +You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +See the Mulan PSL v2 for more details. diff --git a/LICENSE b/LICENSE index bcd8fb2e0a..84931bc19e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,127 @@ -The MIT License (MIT) - -Copyright (c) 2019 码之科技工作室 - -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. + 木兰宽松许可证, 第2版 + + 木兰宽松许可证, 第2版 + 2020年1月 http://license.coscl.org.cn/MulanPSL2 + + + 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: + + 0. 定义 + + “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 + + “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 + + “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + + “法人实体”是指提交贡献的机构及其“关联实体”。 + + “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + + 1. 授予版权许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 + + 2. 授予专利许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 + + 3. 无商标许可 + + “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + + 4. 分发限制 + + 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 + + 5. 免责声明与责任限制 + + “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + + 6. 语言 + “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 + + 条款结束 + + 如何将木兰宽松许可证,第2版,应用到您的软件 + + 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + + 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + + 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + + 3, 请将如下声明文本放入每个源文件的头部注释中。 + + Copyright (c) 2019 Of Him Code Technology Studio + Jpom is licensed under Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. + + + Mulan Permissive Software License,Version 2 + + Mulan Permissive Software License,Version 2 (Mulan PSL v2) + January 2020 http://license.coscl.org.cn/MulanPSL2 + + Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: + + 0. Definition + + Software means the program and related documents which are licensed under this License and comprise all Contribution(s). + + Contribution means the copyrightable work licensed by a particular Contributor under this License. + + Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. + + Legal Entity means the entity making a Contribution and all its Affiliates. + + Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. + + 1. Grant of Copyright License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. + + 2. Grant of Patent License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. + + 3. No Trademark License + + No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. + + 4. Distribution Restriction + + You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. + + 5. Disclaimer of Warranty and Limitation of Liability + + THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 6. Language + + THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. + + END OF THE TERMS AND CONDITIONS + + How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software + + To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: + + i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; + + ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; + + iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. + + + Copyright (c) 2019 Of Him Code Technology Studio + Jpom is licensed under Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000..51889e2dfa --- /dev/null +++ b/NOTICE @@ -0,0 +1,12 @@ +Jpom-An open-source, Project operation and maintenance system. +Jpom-项目运维系统。 + +Jpom Declaration / Jpom 声明 + +It can be used directly without authorization. All copyrights, patents, trademarks and ownership statements in the product and source code must be retained. +可直接免费商用,但必须保留本作品及源码中的所有版权、专利、商标和归属声明。 + +The attribution statement and copyright notice in the footer cannot be ignored, otherwise it will be deemed as infringement. +页面等页脚中归属声明及版权声明不可忽略,否则视为侵权。 + +Copyright https://www.jpom.top All Rights Reserved. diff --git a/PLANS.md b/PLANS.md index 45b9c34c7a..4b481fd1c1 100644 --- a/PLANS.md +++ b/PLANS.md @@ -1,14 +1,106 @@ -### 开发计划 - -# 2.7.x - -1. 自定义检查项目状态 -2. 自定义启动项目脚本 -3. 重构用户角色(计划引入工作空间) -4. ssh 批量执行命令 -5. 数据导入导出 - -# 2.6.0 +# 开发计划 + +## 2.11.x + +1. **构建流水线** +2. **netty-agent** +3. 凭证管理 +4. 升级 JDK 11 或者 17 +5. 端口监控、监控报警、机器监控、ssh 监控报警 +6. 资产监控 +7. nginx 流量切换(nginx 功能可能下线) +8. acme.sh ui +9. 执行审计 +10. 执行部分命令耗时和直接执行相差太大 +11. **非 root 用户提升权限写入 root 用户文件** +12. 部分数据迁移工作空间(~~项目~~,构建,仓库、节点分发) +13. 前端表格用户自定义列显示 +14. 节点取消,白名单配置和下载白名单(统一到服务端工作空间配置) +15. 隧道节点 +16. docker-compose sh +17. 监控通知模块优化支持更多(飞书) zx +18. 数据库支持 mariadb + +## 2.10.x + +1. ~~前端升级 vue3~~ +2. `导入云效仓库 (zx) 依赖太重,非单接口实现(需要标准验证流程)` +3. ~~仓库~~、构建、分发、~~项目~~导入导出 +4. ~~docker 容器编辑重建(zx)~~ +5. ~~前端主题切换~~ +6. ~~仓库密码、ssh 密码引用环境变量支持使用下拉框 sh~~ +7. ~~SSH 修改文件权限 zx~~ +8. ~~vue3 资产管理 zs~~ +9. ~~vue3 用户管理 zs~~ + +### DONE + +1. ~~SSH 连接 docker (sh)~~ +2. ~~批量删除镜像 (sh)~~ +3. **资产管理** + 1. ~~机器管理~~ + 2. ~~机器监控~~ + 3. ~~SSH 管理~~ + 4. ~~dokcer 管理~~ + 5. ~~docker 集群~~ + 6. ~~ssh 监控~~ + 7. ~~仓库管理(待定,可以和凭证管理一起考虑)~~ +4. ~~使用服务端的 git 插件~~ +5. ~~日志组件卡顿~~ +6. ~~清理触发器表~~ +7. ~~scp 发布实践案例~~ +8. ~~SSH 上传文件进度(前端分片+进度)~~ +9. ~~**用户体系支持接入第三方系统**~~ +10. ~~传输信息加密(混淆,避免 http 明文传输)~~ +11. ~~插件端证书验证迁移到服务端~~ +12. ~~稳定版/体验版~~ +13. ~~插件端自定义发布文件~~ +14. ~~容器构建基础镜像的管理~~ +15. ~~tomcat 实践案例~~ +16. ~~**分片上传文件**~~ +17. ~~**支持 mysql 数据库**~~ +18. ~~配置文件优化~~ +19. ~~项目触发器~~ +20. ~~节点转发模块优化~~ +21. ~~构建事件触发新增更多(前后)~~ +22. ~~复制项目~~ +23. ~~测命令行参数~~ +24. ~~标签页缓存问题(定时器未清空)~~ +25. ~~发布到指定目录~~ + +## 2.8.x + +1. ~~h2 数据库升级 2.0~~ +2. ~~文件管理支持备份~~ +3. ~~节点工作空间变量~~ +4. ~~操作监控优化~~ +5. ~~节点日志文件搜索~~ +6. ~~监控 webhook~~ +7. ~~构建判断仓库代码是否变动~~ +8. ~~批量执行节点脚本~~ + +## 2.8.x +-- + +1. ~~ssh 批量执行命令~~ +2. ~~容器编排~~ +3. ~~自定义检查项目状态~~ + 1. `for /f "tokens=1 delims= " %i in ('jps -l ^| findstr "JpomServer"') do @echo %i` +4. ~~自定义启动项目脚本~~ +5. ~~快速安装导入插件端~~ +6. ~~容器构建~~ +7. ~~docker ui~~ +8. ~~节点大屏~~ +9. ~~实时阅读日志文件~~ +10. ~~配置分发~~ +11. ~~修改数据库密码~~ + +## 2.7.x + +1. ~~重构用户角色(计划引入工作空间)~~ +2. ~~数据导入导出~~ + +## 2.6.0 1. ~~取消thymeleaf~~ 2. ~~git 证书拉取代码~~ @@ -18,12 +110,9 @@ 6. ~~节点分发功能增加SSH部署-按指定地址下载应用~~ 7. ~~项目文件管理支持远程下载~~ 8. ~~jwt 更换~~ -9. 压缩工具类优化 -10. 文件管理支持备份 -11. 文件管理备份 - +9. ~~压缩工具类优化~~ -# 2.5.1 +## 2.5.1 1. ~~token 机制问题(@ArnoHand 4月4日前)~~ 2. ~~控制台文件太大问题(@蒋小小 4月4日前)~~ @@ -32,11 +121,11 @@ ====================== -# 2.5.2 +## 2.5.2 1. ~~agent统一升级管理(@蓝枫 4月17日前)~~ 2. ~~git 证书拉取代码~~ -3. 项目列表新增运行方式筛选 +3. ~~项目列表新增运行方式筛选~~ 4. ~~及时刷新菜单~~ 5. ~~远程升级(jpom 官方服务)~~ @@ -45,29 +134,29 @@ * 主要管理页面兼容移动端 * ssl 到期提醒、快捷续签 * Tomcat管理优化 - * 优化启动(期望能使用到tomcat原生配置文件中的参数) - * 在线修改配置 -* 支持docker容器部署 + * 优化启动(期望能使用到tomcat原生配置文件中的参数) + * 在线修改配置 +* ~~支持docker容器部署~~ * ~~jdk选择~~ * ~~开机自动启动~~ * ~~IP白名单~~ -* socket 日志 +* ~~socket 日志~~ * ~~单节点支持项目副本~~ * 构建发布到同一个节点多个项目 -* 集群环境自动注册节点 -* 文件管理优化 +* ~~集群环境自动注册节点~~ +* ~~文件管理优化~~ * ~~agent统一升级管理~~ * ~~ssh文件管理(还原,之前有pr被合并错了)~~ -* 用户权限 -* 数据存储 -* 账号密码安全性 +* ~~用户权限~~ +* ~~数据存储~~ +* ~~账号密码安全性~~ -------------------- * Jpom 接口文档整理 -* 部分服务器ssh不能退格 +* ~~部分服务器ssh不能退格~~ * ~~Token 机制问题~~ * ~~控制台日志文件过大~~ diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000000..2fc60682ab --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,54 @@ +# Jpom项目运维本地部署版隐私协议 + +**一、引言** + +**1.1** 我们诚挚欢迎并感谢您选用开源软件——Jpom项目运维的本地部署版本(以下简称“本软件”)。本隐私协议旨在全面介绍和阐明我们在您本地部署和使用本软件时,关于数据处理的各种活动规范,覆盖但不限于数据的搜集、运用、存储和防护等各个阶段。敬请您在安装并启用本软件之前,务必详尽阅读并深刻理解本协议各项条款。 + +**二、数据处理与本地化规范** + +**2.1** **本地数据操作** +本软件所有数据处理活动严格限制在您指定和管控的本地服务器或设备上执行,我们不会直接获取或远程访问这些数据资源。为保证软件的正常运行,可能需要处理如配置信息、错误日志、临时文件等本地生成的各类数据。 + +**2.2** **必要数据操作原则** +为确保软件基础功能稳定、性能卓越、技术问题有效解决以及优化用户使用体验,本软件可能会对上述部分或全部数据进行必要的处理。请注意,此类数据操作完全局限于本地环境,并严格遵循软件设计的规范与标准。 + +**三、开源与透明度** + +**3.1** 本软件基于 MulanPSL2 开源许可协议发布,其源代码面向公众开放,意味着您有权审查软件源码以验证其数据处理行为符合您的隐私期待。我们积极倡议用户共同参与和监督,共同营造一个高度尊重数据隐私的应用生态体系。 + +**四、数据主权与控制** + +**4.1** 用户对其在本地部署环境中生成的所有数据享有完整的控制权和所有权,这涵盖了从数据的创建、修改、删除到数据传输方式及存储地点的决定权。 + +**五、数据安全保护** + +**5.1** 尽管本软件自带一定的内在安全机制,但用户仍需自行负责在本地部署环境中实施高效的数据安全措施,包括但不限于数据加密、访问控制、安全审计及数据备份策略等。对于因用户本地部署环境引发的数据安全事件,我们不承担直接法律责任。 + +**六、数据交互与隐私承诺** + +本软件坚守最高的数据隐私保护原则,明确规定未经用户明确授权,本软件决不会主动对外向任何第三方透露您系统内的任何敏感信息。 + +我们尤为重视并全力支持用户对数据安全性的深度审视诉求,确保每位用户有权详尽审查软件源代码,以亲手验证我们在数据处理与安全防护措施层面的严密性和合规性。 +我们通过这种开诚布公、负责任的方式,共同致力于最大程度地保障用户数据安全与隐私不受侵犯。 + +**6.1** 个人信息收集政策 + +我们严格遵循不收集原则,除非必要,否则不涉及收集用户的个人身份识别信息,例如姓名、电子邮件地址、电话号码、身份证件号码等。 + +**6.2** 可能收集的数据类别 + +在您使用我们产品和服务的过程中,为确保正常运行和提供更好的服务体验,我们有可能会收集与产品使用紧密相关的一些必要信息,包括但不限于IP地址、Cookies、页面路由、地理位置信息、设备制造商、设备型号、设备唯一标识符以及设备相关数据等。 + +**6.3** 数据信息处理方式 + +在必须收集相关信息的情况下,我们会在遵循相关法律法规及本隐私协议的基础上,将这些数据传输至我们的官方服务器(https://jpom.top),并在严格的规则约束下进行管理和使用。 + +**七、法律合规与用户责任** + +**7.1** 在本软件的研发和运营过程中,我们严格遵守中国各地关于数据保护和隐私权的各项法律法规。 + +**7.2** 用户在部署和使用本软件时,同样需遵循相关法律法规,承诺在使用过程中秉持合法、合理原则,不得利用本软件从事非法行为、侵犯他人合法权益,也不得在违反我国法律法规的项目或平台上部署和使用本软件。 + +**八、隐私政策更新** + +**8.1** 我们保留在必要时适时更新和完善本隐私协议的权利,并将通过官方网站、软件更新公告或其他醒目位置公布修订内容。对于重要的政策变更,我们将提前通知用户,用户在变更后继续使用本软件即视作已同意并接受新的隐私协议。 \ No newline at end of file diff --git a/README-en.md b/README-en.md new file mode 100644 index 0000000000..2e8cc8ffc9 --- /dev/null +++ b/README-en.md @@ -0,0 +1,527 @@ +

+ + logo + +

+

+ 🚀Simple and lightweight low-invasive online build, automated deployment, daily operations, and project monitoring software. +

+

+ 【It is also a native ops software / 中文】 +

+ +

+ + gitee star + + + github star + + + license + + + jdk + +

+ +

+ + codacy + + + docker pull + + + docker pull + +

+ +

+ 👉 https://jpom.top/ 👈 +

+ +## 😭 Do you experience these pain points in your daily development? + +- **No dedicated operations team, so developers have to handle operations tasks**, including manual project building and deployment. +- Different projects require different build and deployment commands. +- Need to package for various environments like development, testing, and production. +- Need to monitor the status of multiple projects simultaneously. +- Need to download SSH tools to remotely connect to servers. +- *Need to download FTP tools* to transfer files to servers. +- Syncing account passwords across multiple servers and computers is inconvenient. +- Want to use automation tools, but they are high-demanding on server performance and too complicated to set up. +- **Have specific needs for automation tools and want to modify the project**, but existing tools are too complex. + +> For distributed projects, these steps are even more cumbersome. +> +> Let Jpom help you solve these pain points! And these are just the basic features that Jpom offers. + +### 😁 After using [Jpom](https://gitee.com/dromara/Jpom) + +- Convenient User Management + 1. User activity monitoring, with email notifications for specific user actions + 2. Multi-user management with independent project permissions (control over upload and delete rights), comprehensive operation logs, and workspace-based permission isolation + 3. Accounts can enable **MFA (Multi-Factor Authentication)** for security +- Real-time interface to view project status, console logs, and manage project files + 1. Edit project text files online +- Docker container management and Docker Swarm cluster management(**Docker UI**) +- **Online SSH Terminal**, allowing easy server management without using PuTTY, Xshell, FinalShell, etc. + 1. No need to know server passwords after logging into the Jpom system + 2. Specify forbidden SSH commands to prevent high-risk operations and automatically log command execution + 3. Set file directories to manage project files and configuration files online + 4. Execute SSH command templates and schedule scripts online + 5. Edit text files online + 6. **Lightweight implementation of basic"bastion host"functionality** +- One-click cluster project deployment using project distribution +- Online build process eliminates the need for manual project updates and upgrades + 1. Supports pulling from GIT and SVN repositories + 2. **Supports container builds (docker)** + 3. Supports SSH-based deployment + 4. Supports scheduled builds + 5. Supports WebHook-triggered builds +- Supports online editing of nginx configuration files and automatic reload operations + 1. Manage nginx status and SSL certificates +- Automatic alerts and restart attempts for abnormal project status + 1. Supports notifications via email, DingTalk groups, and WeChat groups, actively monitoring project status +- Node script templates with scheduling or triggers for expanded functionality +- Configurable authorization for important paths to prevent user errors with system files + +### 🔔️ Special Reminders + +> 1. On Windows servers, some features may have compatibility issues due to system characteristics. It is recommended to thoroughly test in practical use. Linux currently has good compatibility. +> 2. Install the server and plugin components in different directories; do not install them in the same directory. +> 3. To uninstall Jpom plugin or server components, first stop the corresponding service, then delete the related program files, log folders, and data directories. +> 4. Local builds depend on the system environment. If build commands require Maven or Node.js, + ensure the corresponding environment is installed on the build server. If the environment is installed after the server is started, restart the server via the command line for the environment to take effect. +> 5. On Ubuntu/Debian servers, the plugin component may fail to add. Please create a .bash_profile file in the root directory of the current user. +> 6. After upgrading to version 2.7.x, downgrading is not recommended due to potential data incompatibility issues. +> 7. Currently, version 2.x.x uses HTTP for communication between the plugin and server components, so ensure network connectivity between them during use. +> 8. Jpom version 3.0 is already being planned. Stay tuned for the new release! + +### 🗒️ [Changelog](https://gitee.com/dromara/Jpom/blob/master/CHANGELOG.md) + +Must-read before upgrading: [CHANGELOG.md](https://gitee.com/dromara/Jpom/blob/master/CHANGELOG.md) + +## 📥 Installing Jpom + +Jpom supports various installation methods to meet different user needs. Just choose one method to install. + +### Method 1: 🚀(Recommended) One-click Installation (Linux) + +#### One-click Server Installation + +> **Note: The installation directory is the current directory where the command is executed!** +> +> ⚠️ Special Reminder: When using the one-click installation, ensure the command is executed in different directories. The Server and Agent cannot be installed in the same directory! +> +> To change the data and log storage paths of the server, +> modify the `jpom.path` configuration property in the file +> [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/application.yml) + +```shell +# Default one-click installation +curl -fsSL https://jpom.top/docs/install.sh | bash -s Server jdk+default +# Default one-click installation and automatic startup service configuration +curl -fsSL https://jpom.top/docs/install.sh | bash -s Server jdk+default+service + +# Install server and jdk environment +yum install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Server jdk + +# Install server and jdk, maven environment +yum install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Server jdk+mvn + +# ubuntu +apt-get install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Server jdk +``` + +After a successful startup, the server port is `2122`. You can access the management page via `http://127.0.0.1:2122/` +(if not accessing from the local machine, replace 127.0.0.1 with the IP address of the installed server). + +> If you cannot access the management system, run the command `systemctl status firewalld` to check if the firewall is enabled. +> If you see `Active: active (running)` in green in the status bar, you need to allow port `2122`. +> +>```bash +># Allow port 2122 for the management system +>firewall-cmd --add-port=2122/tcp --permanent +># Reload the firewall to take effect +>firewall-cmd --reload +>``` +> +>If you have allowed the port in the operating system but still cannot access it, and you are using a cloud server, check the security group rules in the cloud server's control panel to see if port 2122 is allowed. +> +>⚠️ Note: There are multiple firewalls in Linux systems: Firewall, Iptables, SELinux, etc. When checking firewall configurations, make sure to check all of them. + +#### One-Click Agent Installation + +> If the server where the server side is installed also needs to be managed, you need to install the agent on the server side as well (both the server and agent can be installed on the same server but in different directories). +> +> ⚠️ Special reminder: Do not execute the one-click installation command in the same directory for both the Server and Agent! +> +> If you need to modify the agent data and log storage paths, update the `jpom.path` configuration property in the file +> [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/config_default/application.yml) + +```shell +# Default one-click installation +curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default +# Default one-click installation and auto-configure startup service +curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+service + +# Install agent and JDK environment +yum install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Agent jdk + +# ubuntu +apt-get install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Agent jdk +``` + +After a successful startup, the agent port is `2123`, which is used by the server. + +### Method 2: 📦 Container Installation + +> ⚠️ Note: Container installation requires Docker to be installed first. [Click here for Docker installation documentation](https://jpom.top/pages/b63dc5/) + +#### One-Command Installation + +```shell +docker run -p 2122:2122 --name jpom-server jpomdocker/jpom +``` + +#### Using Mount to Store Data (may have compatibility issues in some environments) + +```shell +docker pull jpomdocker/jpom +mkdir -p /home/jpom-server/logs +mkdir -p /home/jpom-server/data +mkdir -p /home/jpom-server/conf +docker run -d -p 2122:2122 \ + --name jpom-server \ + -v /home/jpom-server/logs:/usr/local/jpom-server/logs \ + -v /home/jpom-server/data:/usr/local/jpom-server/data \ + -v /home/jpom-server/conf:/usr/local/jpom-server/conf \ + jpomdocker/jpom +``` + +#### Using Docker Volumes to Store Data + +```shell +docker pull jpomdocker/jpom +docker volume create jpom-server-data +docker volume create jpom-server-logs +docker volume create jpom-server-conf +docker run -d -p 2122:2122 \ + --name jpom-server \ + -v jpom-server-data:/usr/local/jpom-server/data \ + -v jpom-server-logs:/usr/local/jpom-server/logs \ + -v jpom-server-conf:/usr/local/jpom-server/conf \ + jpomdocker/jpom +``` + +> Container installation only provides the server version. Due to isolation between the container and the host environment, many functionalities of the agent cannot be used properly. Therefore, containerization of the agent is not very meaningful. +> +> For more information on installing Docker, configuring images, auto-start, and locating the installation directory, refer to the documentation +> [https://jpom.top/pages/b63dc5/](https://jpom.top/pages/b63dc5/) +> +> In lower versions of Docker, you may encounter the error `ls: cannot access'/usr/local/jpom-server/lib/': Operation not permitted` +> In this case, add the `--privileged` parameter +> Example: `docker run -p 2122:2122 --name jpom-server jpomdocker/jpom --privileged` + +### Method 3: 💾 Download and Install + +1. Download the installation package from [https://jpom.top/pages/all-downloads/](https://jpom.top/pages/all-downloads/) +2. Extract the files +3. Install the agent: + 1. The `agent-x.x.x-release` directory contains all the installation files for the agent + 2. Upload the entire directory to the corresponding server + 3. Start the agent. Use the bat script on Windows and the sh script on Linux (if there are garbled characters or execution issues, check the encoding format and line endings) + 4. The default running port for the agent is `2123` +4. Install the server: + 1. The `server-x.x.x-release` directory contains all the installation files for the server + 2. Upload the entire directory to the corresponding server + 3. Start the server. Use the bat script on Windows and the sh script on Linux (if there are garbled characters or execution issues, check the encoding format and line endings) + 4. The default running port for the server is `2122`. Access the management page at `http://127.0.0.1:2122/` (if not accessed locally, replace `127.0.0.1` with your server's IP address) + +### Method 4: ⌨️ Compile and Install + +1. Visit the [Jpom](https://gitee.com/dromara/Jpom) Gitee page and pull the latest complete code (recommended to use the master branch) +2. Switch to the `web-vue` directory and run `npm install` (you need to have the Vue environment set up in advance; refer to the README.md in the web-vue directory for details) +3. Run `npm run build` to package the Vue project +4. Switch to the project root directory and run: `mvn clean package` +5. Install the agent: + 1. Check the agent installation package in `modules/agent/target/agent-x.x.x-release` + 2. Upload the entire directory to the server + 3. Start the agent. Use the bat script on Windows and the sh script on Linux (if there are garbled characters or execution issues, check the encoding format and line endings) + 4. The default running port for the agent is `2123` +6. Install the server: + 1. Check the server installation package in `modules/server/target/server-x.x.x-release` + 2. Upload the entire directory to the server + 3. Start the server. Use the bat script on Windows and the sh script on Linux (if there are garbled characters or execution issues, check the encoding format and line endings) + 4. The default running port for the server is `2122`. Access the management page at `http://127.0.0.1:2122/` (if not accessed locally, replace `127.0.0.1` with your server's IP address) + +> You can also use `script/release.bat` or `script/release.sh` for quick packaging. + +### Method 5: 📦 One-Click Start with Docker-Compose + +- No environment installation required; automatically compiles and builds + +> Note: Remember to modify the token value in the `.env` file + +```shell +yum install -y git +git clone https://gitee.com/dromara/Jpom.git +cd Jpom +docker-compose -f docker-compose.yml up +# docker-compose -f docker-compose.yml up --build +# docker-compose -f docker-compose.yml build --no-cache +# docker-compose -f docker-compose-local.yml up +# docker-compose -f docker-compose-local.yml build --build-arg TEMP_VERSION=.0 +# docker-compose -f docker-compose-cluster.yml up --build +``` + +### Method 6: 💻 Compile and Run + +1. Visit the [Jpom](https://gitee.com/dromara/Jpom) Gitee page and pull the latest complete code (it's recommended to use the master branch, but if you want to experience new features, you can use the + dev branch) +2. Run the agent: + 1. Run `org.dromara.jpom.JpomAgentApplication` + 2. Note the default username and password information printed in the console. + 3. The agent's default running port: `2123` +3. Run the server: + 1. Run `org.dromara.jpom.JpomServerApplication` + 2. The server's default running port: `2122` +4. Build the Vue page, switch to the `web-vue` directory (make sure you have node and npm environments set up locally). +5. Install the Vue project dependencies by executing `npm install` in the console. +6. Start development mode by executing `npm run dev` in the console. +7. Access the frontend page using the address output in the console: `http://127.0.0.1:3000/` (if not accessing from the local machine, replace `127.0.0.1` with your server's IP address). + +## Managing Jpom Commands + +1. Using BAT Script Files on Windows + +```bash +# Server management scripts (command line) +./bin/Server.bat start # Start the Jpom server +./bin/Server.bat stop # Stop the Jpom server +./bin/Server.bat restart # Restart the Jpom server +./bin/Server.bat status # Check the Jpom server status +# Server management script (control panel), follow the panel prompts for operations +./bin/Server.bat + +# Agent management scripts +./bin/Agent.bat start # Start the Jpom agent +./bin/Agent.bat stop # Stop the Jpom agent +./bin/Agent.bat restart # Restart the Jpom agent +./bin/Agent.bat status # Check the Jpom agent status +# Agent management script (control panel), follow the panel prompts for operations +./bin/Agent.bat + +``` + +> After executing the startup script on Windows, follow the logs to check the startup status. If you encounter garbled text, check or modify the encoding format. It is recommended to use +> `GB2312` for BAT script encoding on Windows. + +2. Using SH Script Files on Linux + +```bash +# Server management scripts +./bin/Server.sh start # Start the Jpom server +./bin/Server.sh stop # Stop the Jpom server +./bin/Server.sh restart # Restart the Jpom server +./bin/Server.sh status # Check the Jpom server status +./bin/Service.sh install # Create a service for the Jpom server (jpom-server) + +# Agent management scripts +./bin/Agent.sh start # Start the Jpom agent +./bin/Agent.sh stop # Stop the Jpom agent +./bin/Agent.sh restart # Restart the Jpom agent +./bin/Agent.sh status # Check the Jpom agent status +./bin/Service.sh install # Create a service for the Jpom agent (jpom-agent) +``` + +## Linux Service Management + +> The following service installation instructions are for reference only; customize configurations as needed. +> +> After successfully using `./bin/Service.sh install`: +> +> systemctl {status | start | stop | restart} jpom-server +> +> systemctl {status | start | stop | restart} jpom-agent + +## ⚙️ Jpom Configuration Parameters + +Located in the project's root path: + +### Application Configuration `./conf/application.yml` + +1. Agent example: + [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/config_default/application.yml) +2. Server example: + [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/application.yml) + +### Project Logs `./conf/logback.xml` + +1. Agent example: + [`logback.xml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/config_default/logback.xml) +2. Server example: + [`logback.xml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/logback.xml) + +## 📝 Frequently Asked Questions and User Guide + +- [Home Page](https://jpom.top/) +- [FQA](https://jpom.top/pages/FQA/) +- [Glossary](https://jpom.top/pages/FQA/proper-noun/) + +### Practical Examples + +> Some images may load slowly. + +1. [Local Build + SSH Deployment for Java Projects](https://jpom.top/pages/practice/build-java-ssh-release/) +2. [Local Build + Project Deployment for Node Projects](https://jpom.top/pages/practice/build-node-release/) +3. [Local Build + SSH Deployment for Node Projects](https://jpom.top/pages/practice/build-node-ssh-release/) +4. [Local Build + Custom Management for Python Projects](https://jpom.top/pages/practice/project-dsl-python/) +5. [Custom Management for Java Projects](https://jpom.top/pages/practice/project-dsl-java/) +6. [Managing Compiled and Installed Nginx](https://jpom.top/pages/practice/node-nginx/) +7. [Managing Docker](https://jpom.top/pages/practice/docker-cli/) +8. [Container Build + Project Deployment for Java Projects](https://jpom.top/pages/practice/build-docker-java-node-release/) +9. [More Practical Examples>>](https://jpom.top/pages/practice/) + +## Example Code Repositories + +1. [Jboot Example Code](https://gitee.com/keepbx/Jpom-demo-case/tree/master/jboot-test) +2. [SpringBoot Example Code (ClassPath)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/springboot-test) +3. [SpringBoot Example Code (Jar)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/springboot-test-jar) +4. [Node Vue Example Code (antdv)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/antdv) +5. [Python Example Code](https://gitee.com/keepbx/Jpom-demo-case/tree/master/python) + +> Node.js compile specific directory: + +```bash +yarn --cwd xxxx/ install +yarn --cwd xxxx/ build +``` + +> Maven compile specific directory: + +```bash +mvn -f xxxx/pom.xml clean package +``` + +## 🛠️ Overall Architecture + +![jpom-func-arch](https://jpom.top/images/jpom-func-arch.png) + + +## 🐞 Community Discussion, Bug Reports, Suggestions, etc. + +1. Scan the QR code below on the left to join our WeChat group! (Add the assistant and mention Jpom to join the group) +2. Open source projects rely on community support. If Jpom has helped you, consider buying us a coffee. + You can scan the [WeChat and Alipay QR codes](https://jpom.top/images/qrcode/praise-all.png) + or support us through [Gitee sponsorship](https://gitee.com/dromara/Jpom) + (click the donate button at the bottom of the project homepage, supports WeChat and Alipay). [Sponsorship records](https://jpom.top/pages/praise/publicity/) +3. Purchase open source merchandise: [Merchandise Introduction](https://jpom.top/pages/shop/) +4. For enterprise technical services, please contact us directly to discuss service plans. +5. For bug reports and suggestions, feel free to create [issues](https://gitee.com/dromara/Jpom/issues); developers will respond periodically. +6. To contribute, please see the [Contribution Guide](#Contribution Guide)。 + +Thank you to all sponsors and contributors; your support is our motivation for continuous updates and improvements! +

+praise img +

+ +## 💖 Merchandise + +To better support the open-source project, we have decided to launch merchandise. + +By purchasing, you get a small item, and we receive the profit from your purchase (the prices of the merchandise may be slightly higher than market prices; please be aware before ordering). + +

+shop home +

+ +## 🔨Contribution Guide + +> By contributing, you agree to the terms of the [CLA](https://gitee.com/dromara/Jpom/blob/master/CLA.md) agreement + +### Contribution Guidelines + +As an open-source project, Jpom relies on community support and welcomes contributions from everyone. Whether big or small, your contributions will help thousands of users and developers. Your contributions will also be permanently recorded in the list of contributors, which is the essence of open-source projects! + +To ensure code quality and standards, and to help you quickly understand the project structure, please read the following before contributing: + +- [Jpom Contribution Guide](https://gitee.com/dromara/Jpom/blob/master/CODE_OF_CONDUCT.md) +- [Typography Specifications (Chinese and English)](https://gitee.com/dromara/Jpom/blob/dev/typography-specification.md) + +### Contribution Steps + +1. Fork this repository. + +2. Clone your forked repository to your local machine. + + Replace `branch-name` and `username` with the appropriate values. + + Use `dev` for code contributions and `docs` for documentation contributions. + + ```bash + git clone -b branch-name https://gitee.com/username/Jpom.git + ``` + +3. Modify the code/documentation and commit your changes. + + ```bash + # Add your changes to the staging area + git add . + # Commit your changes with a descriptive message + git commit -m 'Describe your changes' + # Push to your remote repository, replacing branch-name with dev or docs + git push origin branch-name + ``` + +4. Create a Pull Request (PR). + + Go to your repository on Gitee, create a PR request, and wait for the administrators to merge your changes. + +### Branch Explanation + +| Branch | Description | +|--------|------------------------------------------------------| +| master | Main branch, protected. Does not accept PRs. Merges from the beta branch after testing. | +| beta | Beta version branch, protected. Does not accept PRs. Merges from the dev branch after testing. | +| dev | Development branch, accepts PRs. Please submit PRs to the dev branch. | +| docs | Documentation branch, accepts PRs. Used for project documentation, feature introductions, and FAQ summaries. | + +> Primarily use the dev and docs branches for PR submissions. Other branches are for archiving and can be ignored by contributors. + +## 🌍 Knowledge Planet + +

+Scan to join Knowledge Planet and learn more +

+ +## 🔔 Recommended Projects + +| Project Name | Project Link | Description | +|---------------|----------------------------------------------------------------------------|-----------------------------------------------| +| SpringBoot_v2 | [https://gitee.com/bdj/SpringBoot_v2](https://gitee.com/bdj/SpringBoot_v2) | A pure SpringBoot scaffold | +| TLog GVP Project | [https://gitee.com/dromara/TLog](https://gitee.com/dromara/TLog) | A lightweight distributed log tagging and tracking tool for microservices | +| Sa-Token | [https://gitee.com/dromara/sa-token](https://gitee.com/dromara/sa-token) | Possibly the most feature-rich Java authentication framework | +| Erupt | [https://gitee.com/erupt/erupt](https://gitee.com/erupt/erupt) | Zero frontend code, pure annotation-based admin backend development | +| hippo4j | [https://gitee.com/magegoofy/hippo4j](https://gitee.com/magegoofy/hippo4j) | Powerful dynamic thread pool framework with monitoring and alert features | +| HertzBeat | [https://gitee.com/dromara/hertzbeat](https://gitee.com/dromara/hertzbeat) | Easy-to-use cloud monitoring system with strong custom monitoring capability | + +## 🤝 Acknowledgements + +- Special thanks to JetBrains for providing a free open-source license: + +

+JetBrains +

diff --git a/README.md b/README.md index de5b847295..f56d806486 100644 --- a/README.md +++ b/README.md @@ -1,307 +1,534 @@

- - logo + + logo

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

+

+ 【更是一款原生 ops 软件 / English

- - license + + gitee star + + github star + + + license + jdk - - travis - +

+ +

codacy - jpom66 请备注jpom - - gitee star + + docker pull - - github + + docker pull -

+

- https://jpom.io/ | https://jpom-site.keepbx.cn/ | https://jpom.keepbx.cn/ + 👉 https://jpom.top/ 👈

-#### 你为什么需要 [Jpom](https://gitee.com/dromara/Jpom) - -> Java 项目在实际部署运维,通用的方法是登录服务器上传新的项目包,执行相应命令管理,如果管理多个项目则重复操作上述步骤 - -> 此方法不足的是: -> 1. 需要每次登录服务器(专业软件) -> 2. 多个项目有多个管理命令(不易记、易混淆) -> 3. 查看项目运行状态需要再次使用命令 -> 4. 同时面对多个运维都需要知道服务器密码(安全性低) -> 5. 集群项目需要挨个操作项目步骤 - -> 在使用Jpom后: -> 1. 使用浏览器登录方便快捷管理项目 -> 2. 界面形式实时查看项目运行状态以及控制台日志 -> 3. 运维有对应的账号密码不需要知道服务器密码(并且有操作日志) -> 4. 集群项目使用项目分发一键搞定多机部署 -> 5. 项目状态监控异常自动报警 -> 6. 在线构建不用手动上传项目包 - -#### 项目主要功能及特点 - -1. 创建、修改、删除项目、Jar包管理 -2. 实时查看控制台日志、备份日志、删除日志、导出日志 -3. 在线构建项目发布项目一键搞定 -4. 多节点管理、多节点自动分发 -5. 在线 SSH 终端,并且有终端日志和禁用命令 -6. 实时监控项目状态异常自动报警 -7. ~~cpu、ram 监控、(已经取消该功能)~~ 导出堆栈信息、查看项目进程端口、服务器状态监控 -8. 多用户管理,用户项目权限独立(上传、删除权限可控制),完善的操作日志 -9. 系统路径白名单模式,杜绝用户误操作系统文件 -10. 在线管理 Nginx 配置文件、ssl 证书文件 -11. ~~Tomcat状态、文件、war包在线实时管理 (不再长期维护)~~ - -> 特别提醒: -> 1. 在 Windows 服务器中可能有部分功能因为系统特性造成兼容性问题,建议在实际使用中充分测试。Linux 目前兼容良好 +## 😭 日常开发中,您是否有以下痛点? + +- **团队中没有专业的运维,开发还要做运维的活**,需要自己手动构建、部署项目。 +- 不同的项目有不同的构建、部署命令。 +- 有开发、测试、生产等多环境打包的需求。 +- 需要同时监控多个项目的运行状态。 +- 需要下载 SSH 工具远程连接服务器。 +- *需要下载 FTP 工具*传输文件到服务器。 +- 多台服务器时,在不同电脑之间账号密码同步不方便。 +- 想使用一些自动化工具,但是对服务器性能太高,搭建太麻烦。 +- **对自动化工具有个性化的需求,想自己修改项目**,但是市面上的工具太复杂了。 + +> 如果是分布式的项目,以上步骤则更加繁琐。 +> +> 让 Jpom 来帮您解决这些痛点吧!然而,这些只是 Jpom 解决的最基础的功能。 + +### 😁 使用 [Jpom](https://gitee.com/dromara/Jpom) 后 + +- 方便的用户管理 + 1. 用户操作监控,监控指定用户指定操作以邮件形式通知 + 2. 多用户管理,用户项目权限独立(上传、删除权限可控制),完善的操作日志,使用工作空间隔离权限 + 3. 账号可以开启 **MFA 两步验证**提高账号安全性 +- 界面形式实时查看项目运行状态、控制台日志、管理项目文件 + 1. 在线修改项目文本文件 +- Docker 容器管理、Docker Swarm 集群管理(**Docker UI**) +- **在线 SSH 终端**,让您在没有 PuTTY、Xshell、FinalShell 等软件也能轻松管理服务器 + 1. 登录 Jpom 系统后不需要知道服务器密码 + 2. 能指定 SSH 禁止执行的命令,避免执行高风险命令,并且能自动执行命令日志 + 3. 设置文件目录,在线查看管理对应项目文件及配置文件 + 4. SSH 命令模版在线执行脚本还能定时执行 + 5. 在线修改文本文件 + 6. **轻量的实现了简单的"堡垒机"功能** +- 使用项目分发一键搞定集群项目多机部署 +- 在线构建不用手动更新升级项目 + 1. 支持拉取 GIT、SVN 仓库 + 2. **支持容器构建(docker)** + 3. 支持 SSH 方式发布 + 4. 支持定时构建 + 5. 支持 WebHook 形式触发构建 +- 支持在线编辑 nginx 配置文件并自动 reload 等操作 + 1. 管理 nginx 状态,管理 SSL 证书 +- 项目状态监控异常自动报警、自动尝试重启 + 1. 支持邮件 + 钉钉群 + 微信群通知,主动感知项目运行状况 +- 节点脚本模版+定时执行或者触发器,拓展更多功能 +- 重要路径授权配置,杜绝用户误操作系统文件 + +### 🔔️ 特别提醒 + +> 1. 在 Windows 服务器中可能有部分功能因为系统特性造成兼容性问题,建议在实际使用中充分测试。Linux 目前兼容性良好 > 2. 服务端和插件端请安装到不同目录中,切勿安装到同一目录中 -> 3. 卸载 Jpom 插件端或者服务端,先停止对应服务,删除对应的程序文件、日志文件夹、数据目录文件夹即可 -> 4. 构建依赖的是系统环境,如果需要 maven 或者 node 需要服务端所在的服务器中有对应插件,如果已经启动服务端再安装的对应环境需要通过命令行重启服务端后才生效。 +> 3. 卸载 Jpom 插件端或者服务端,先停止对应服务,然后删除对应的程序文件、日志文件夹、数据目录文件夹即可 +> 4. 本地构建依赖的是系统环境,如果构建命令需要使用 maven 或者 node + 需要在构建项目的服务器安装好对应的环境。如果已经启动服务端再安装的对应环境需要通过命令行重启服务端后环境才会生效。 +> 5. 在 Ubuntu/Debian 服务器作为插件端可能会添加失败,请在当前用户的根目录创建 .bash_profile 文件 +> 6. 升级 2.7.x 后不建议降级操作,会涉及到数据不兼容的情况 +> 7. 由于目前 2.x.x 版本插件端和服务端主要采用 http 协议通讯,插件端和服务端网络要求互通,在使用的时候请注意。 +> 8. Jpom 3.0 版本已经开始规划更新了,尽请期待新版本的诞生吧 + +### 🗒️ [版本更新日志](https://gitee.com/dromara/Jpom/blob/master/CHANGELOG.md) + +升级前必看:[CHANGELOG.md](https://gitee.com/dromara/Jpom/blob/master/CHANGELOG.md) + +## 📥 安装 Jpom + +Jpom 支持多种安装方式,满足不同用户的个性化需求,您只需要选择一种方式安装即可。 + +### 方式一:🚀(推荐) 一键安装(Linux) + +#### 一键安装服务端 + +> **注意:安装的目录位于执行命令的目录!** +> +> ⚠️ 特别提醒:一键安装的时候注意执行命令不可在同一目录下,即 Server 端和 Agent 端不可安装在同一目录下! +> +> 如果需要修改服务端数据、日志存储的路径请修改 +> [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/application.yml) +> 文件中 `jpom.path` 配置属性。 + +```shell +# 一键默认安装 +curl -fsSL https://jpom.top/docs/install.sh | bash -s Server jdk+default +# 一键默认安装 + 自动配置开机自启服务 +curl -fsSL https://jpom.top/docs/install.sh | bash -s Server jdk+default+service + +# 安装服务端和 jdk 环境 +yum install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Server jdk + +# 安装服务端和 jdk、maven 环境 +yum install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Server jdk+mvn + +# ubuntu +apt-get install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Server jdk +``` -> 升级 2.7.x 后不建议降级操作,会涉及到数据不兼容到情况 -> -> [2.6.x "稳定版" 分支](https://gitee.com/dromara/Jpom/tree/2.6.x/) +启动成功后,服务端的端口为 `2122`,可通过 `http://127.0.0.1:2122/` +访问管理页面(如果不是本机访问,需要把 127.0.0.1 换成您安装的服务器 IP 地址)。 + +> 如无法访问管理系统,执行命令 `systemctl status firewalld` 检查下是否开启了防火墙 +> ,如状态栏看到绿色显示 `Active: active (running)` 需要放行 `2122` 端口。 +> +>```bash +># 放行管理系统的 2122 端口 +>firewall-cmd --add-port=2122/tcp --permanent +># 重启防火墙才会生效 +>firewall-cmd --reload +>``` +> +>如果在操作系统上放行了端口仍无法访问,并且您使用的是云服务器,请到云服务器后台中检查安全组规则是否放行 2122 端口。 +> +>⚠️ 注意: Linux 系统中有多种防火墙:Firewall、Iptables、SELinux 等,再检查防火墙配置时候需要都检查一下。 + +#### 一键安装插件端 + +> 如果安装服务端的服务器也需要被管理,在服务端上也需要安装插件端(同一个服务器中可以同时安装服务端和插件端) +> +> ⚠️ 特别提醒:一键安装的时候注意执行命令不可在同一目录下,即 Server 端和 Agent 端不可安装在同一目录下! +> +> 如果需要修改插件端数据、日志存储的路径请修改 +> [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/config_default/application.yml) +> 文件中 `jpom.path` 配置属性。 + +```shell +# 一键默认安装 +curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default +# 一键默认安装 + 自动配置开机自启服务 +curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+service + +# 安装插件端和 jdk 环境 +yum install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Agent jdk + +# ubuntu +apt-get install -y wget && \ +wget -O install.sh https://jpom.top/docs/install.sh && \ +bash install.sh Agent jdk +``` -#### 版本更新日志 +启动成功后,插件端的端口为 `2123`,插件端提供给服务端使用。 -[CHANGELOG.md](https://gitee.com/dromara/Jpom/blob/master/CHANGELOG.md) +### 方式二:📦 容器化安装 -### 一键安装(Linux)(推荐) +> ⚠️ 注意:容器化安装方式需要先安装 docker,[点击跳转docker安装文档](https://jpom.top/pages/b63dc5/) -#### 插件端 -> 如果服务端也需要被管理,在服务端上也需要安装插件端 -> -> 安装的路径位于执行命令目录(数据、日志存放目录默认位于安装路径,如需要修改参考配置文件:[`extConfig.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/bin/extConfig.yml) ) +#### 一条命令安装 +```shell +docker run -p 2122:2122 --name jpom-server jpomdocker/jpom ``` -yum install -y wget && wget -O install.sh https://dromara.gitee.io/jpom/docs/install.sh && bash install.sh Agent -备用地址 +#### 使用挂载方式存储相关数据(在部分环境可能出现兼容性问题) + +```shell +docker pull jpomdocker/jpom +mkdir -p /home/jpom-server/logs +mkdir -p /home/jpom-server/data +mkdir -p /home/jpom-server/conf +docker run -d -p 2122:2122 \ + --name jpom-server \ + -v /home/jpom-server/logs:/usr/local/jpom-server/logs \ + -v /home/jpom-server/data:/usr/local/jpom-server/data \ + -v /home/jpom-server/conf:/usr/local/jpom-server/conf \ + jpomdocker/jpom +``` -yum install -y wget && wget -O install.sh https://cdn.jsdelivr.net/gh/dromara/Jpom/docs/install.sh && bash install.sh Agent +#### 使用容器卷方式存储相关数据 + +```shell +docker pull jpomdocker/jpom +docker volume create jpom-server-data +docker volume create jpom-server-logs +docker volume create jpom-server-conf +docker run -d -p 2122:2122 \ + --name jpom-server \ + -v jpom-server-data:/usr/local/jpom-server/data \ + -v jpom-server-logs:/usr/local/jpom-server/logs \ + -v jpom-server-conf:/usr/local/jpom-server/conf \ + jpomdocker/jpom +``` -支持自动安装jdk环境 +> 容器化安装仅提供服务端版。由于容器和宿主机环境隔离,而导致插件端的很多功能无法正常使用,因此对插件端容器化意义不大。 +> +> 安装docker、配置镜像、自动启动、查找安装后所在目录等可参考文档 +> [https://jpom.top/pages/b63dc5/](https://jpom.top/pages/b63dc5/) +> +> 在低版本 docker 中运行可能出现 `ls: cannot access'/usr/local/jpom-server/lib/': Operation not permitted` +> 错误,此时需要添加 `--privileged` 参数 +> 如:`docker run -p 2122:2122 --name jpom-server jpomdocker/jpom --privileged` -yum install -y wget && wget -O install.sh https://dromara.gitee.io/jpom/docs/install.sh && bash install.sh Agent jdk +### 方式三:💾 下载安装 +1. 下载安装包 [https://jpom.top/pages/all-downloads/](https://jpom.top/pages/all-downloads/) +2. 解压文件 +3. 安装插件端 + 1. `agent-x.x.x-release` 目录为插件端的全部安装文件 + 2. 上传到对应服务器(整个目录) + 3. 启动插件端,Windows 环境用 bat 脚本,Linux 环境用 sh 脚本。(如果出现乱码或者无法正常执行,请检查编码格式、换行符是否匹配。) + 4. 插件端默认运行端口:`2123` +4. 安装服务端 + 1. `server-x.x.x-release` 目录为服务端的全部安装文件 + 2. 上传到对应服务器(整个目录) + 3. 启动服务端,Windows 环境用 bat 脚本,Linux 环境用 sh 脚本。(如果出现乱码或者无法正常执行,请检查编码格式、换行符是否匹配。) + 4. 服务端默认运行端口:`2122`,访问管理页面:`http://127.0.0.1:2122/`(非本机访问把 `127.0.0.1` 换成您的服务器 IP 地址) + +### 方式四:⌨️ 编译安装 + +1. 访问 [Jpom](https://gitee.com/dromara/Jpom) 的码云主页,拉取最新完整代码(建议使用 master 分支) +2. 切换到 `web-vue` 目录,执行 `npm install`(vue 环境需要提前搭建和安装依赖包详情可以查看 web-vue 目录下 README.md) +3. 执行 `npm run build` 进行 vue 项目打包 +4. 切换到项目根目录执行:`mvn clean package` +5. 安装插件端 + 1. 查看插件端安装包 `modules/agent/target/agent-x.x.x-release` + 2. 打包上传服务器运行(整个目录) + 3. 启动插件端,Windows 环境用 bat 脚本,Linux 环境用 sh 脚本。(如果出现乱码或者无法正常执行,请检查编码格式、换行符是否匹配。) + 4. 默认运行端口:`2123` +6. 安装服务端 + 1. 查看插件端安装包 `modules/server/target/server-x.x.x-release` + 2. 打包上传服务器运行(整个目录) + 3. 启动服务端,Windows 环境用 bat 脚本,Linux 环境用 sh 脚本。(如果出现乱码或者无法正常执行,请检查编码格式、换行符是否匹配。) + 4. 服务端默认运行端口:`2122`,访问管理页面:`http://127.0.0.1:2122/`(非本机访问把 `127.0.0.1` 换成您的服务器 IP 地址) + +> 也可以使用 `script/release.bat` 或 `script/release.sh` 快速打包。 + +### 方式五:📦 一键启动 docker-compose + +- 无需安装任何环境,自动编译构建 + +> 需要注意修改 `.env` 文件中的 token 值 + +```shell +yum install -y git +git clone https://gitee.com/dromara/Jpom.git +cd Jpom +docker-compose -f docker-compose.yml up +# docker-compose -f docker-compose.yml up --build +# docker-compose -f docker-compose.yml build --no-cache +# docker-compose -f docker-compose-local.yml up +# docker-compose -f docker-compose-local.yml build --build-arg TEMP_VERSION=.0 +# docker-compose -f docker-compose-cluster.yml up --build ``` -启动成功后,插件端的端口为 `2123` - -#### 服务端 +### 方式六:💻 编译运行 -> 安装的路径位于执行命令目录(数据、日志存放目录默认位于安装路径,如需要修改参考配置文件:[`extConfig.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/bin/extConfig.yml) ) -> -> 如果需要修改数据、日志存储路径请参照 `extConfig.yml` 文件中 `jpom.path` 配置属性 +1. 访问 [Jpom](https://gitee.com/dromara/Jpom) 的码云主页 拉取最新完整代码 (建议使用 master 分支,如果想体验新功能可以使用 + dev 分支) +2. 运行插件端 + 1. 运行 `org.dromara.jpom.JpomAgentApplication` + 2. 留意控制台打印的默认账号密码信息 + 3. 插件端默认运行端口:`2123` +3. 运行服务端 + 1. 运行 `org.dromara.jpom.JpomServerApplication` + 2. 服务端默认运行端口:`2122` +4. 构建 vue 页面,切换到 `web-vue` 目录(前提需要本地开发环境有 node、npm 环境) +5. 安装项目 vue 依赖,控制台执行 `npm install` +6. 启动开发模式,控制台执行 `npm run dev` +7. 根据控制台输出的地址访问前端页面:`http://127.0.0.1:3000/`(非本机访问把 `127.0.0.1` 换成您的服务器 IP 地址) + +## 管理 Jpom 命令 + +1. Windows 系统使用 bat 脚本文件。 + +```bash +# 服务端管理脚本 (命令行) +./bin/Server.bat start # 启动Jpom服务端 +./bin/Server.bat stop # 停止Jpom服务端 +./bin/Server.bat restart # 重启Jpom服务端 +./bin/Server.bat status # 查看Jpom服务端运行状态 +# 服务端管理脚本 (控制面板),按照面板提示输入操作 +./bin/Server.bat + +# 插件端管理脚本 +./bin/Agent.bat start # 启动Jpom插件端 +./bin/Agent.bat stop # 停止Jpom插件端 +./bin/Agent.bat restart # 重启Jpom插件端 +./bin/Agent.bat status # 查看Jpom插件端运行状态 +# 插件端管理脚本(控制面板),按照面板提示输入操作 +./bin/Agent.bat ``` -yum install -y wget && wget -O install.sh https://dromara.gitee.io/jpom/docs/install.sh && bash install.sh Server -备用地址 +> Windows 系统中执行启动后需要根据日志去跟进启动的状态,如果出现乱码请检查或者修改编码格式,Windows 系统中 bat +> 编码格式推荐为 `GB2312` -yum install -y wget && wget -O install.sh https://cdn.jsdelivr.net/gh/dromara/Jpom/docs/install.sh && bash install.sh Server +2. Linux 系统中使用 sh 脚本文件。 +```bash +# 服务端 +./bin/Server.sh start # 启动Jpom服务端 +./bin/Server.sh stop # 停止Jpom服务端 +./bin/Server.sh restart # 重启Jpom服务端 +./bin/Server.sh status # 查看Jpom服务端运行状态 +./bin/Service.sh install # 创建Jpom服务端的应用服务(jpom-server) -支持自动安装jdk环境 +# 插件端 +./bin/Agent.sh start # 启动Jpom插件端 +./bin/Agent.sh stop # 停止Jpom插件端 +./bin/Agent.sh restart # 重启Jpom插件端 +./bin/Agent.sh status # 查看Jpom插件端运行状态 +./bin/Service.sh install # 创建Jpom插件端的应用服务(jpom-agent) +``` -yum install -y wget && wget -O install.sh https://dromara.gitee.io/jpom/docs/install.sh && bash install.sh Server jdk +## Linux 服务方式管理 -支持自动安装jdk和maven环境 +> 这里安装服务仅供参考,实际中可以根据需求自定义配置 +> +> 在使用 `./bin/Service.sh install` 成功后 +> +> systemctl {status | start | stop | restart} jpom-server +> +> systemctl {status | start | stop | restart} jpom-agent -yum install -y wget && wget -O install.sh https://dromara.gitee.io/jpom/docs/install.sh && bash install.sh Server jdk+mvn +## ⚙️ Jpom 的参数配置 -``` +在项目运行的根路径下的 : -启动成功后,服务端的端口为 `2122` 访问管理页面 例如`http://127.0.0.1:2122/` +### 程序配置 `./conf/application.yml` -> 特别提醒:一键安装的时候注意执行命令不可在同一目录下,即Server端和Agent端不可安装在同一目录下 -> -> 如无法访问,检查下是否开启了防火墙`systemctl status firewalld`,如状态显示为绿色`Active: active (running)`可临时关闭防火墙`systemctl stop firewalld`,然后重启防火墙`firewall-cmd --reload`(建议仅测试环境下使用,生产环境下慎用) -> 如关闭防火墙后仍无法访问,并且使用的是云服务器,还需要到云服务器管理后台中关闭防火墙 +1. 插件端示例: + [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/config_default/application.yml) +2. 服务端示例: + [`application.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/application.yml) -### 容器化安装 +### 项目日志 `./conf/logback.xml` -#### 插件端 +1. 插件端示例: + [`logback.xml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/config_default/logback.xml) +2. 服务端示例: + [`logback.xml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/logback.xml) -``` -docker pull samho2008/jpom-agent -docker volume create jpom-agent-vol -docker run -d -p 2123:2123 --name jpom-agent -v /etc/localtime:/etc/localtime:ro -v jpom-agent-vol:/usr/local/jpom-agent samho2008/jpom-agent -``` +## 📝 常见问题、操作说明 -#### 服务端 +- [文档主页](https://jpom.top/) +- [FQA](https://jpom.top/pages/FQA/) +- [名词解释](https://jpom.top/pages/FQA/proper-noun/) -``` -docker pull samho2008/jpom-server -docker volume create jpom-server-vol -docker run -d -p 2122:2122 --name jpom-server -v /etc/localtime:/etc/localtime:ro -v jpom-server-vol:/usr/local/jpom-server samho2008/jpom-server -``` +### 实践案例 -### 下载安装 +> 里面有部分图片加载可能比较慢 -> [帮助文档](https://jpom-site.keepbx.cn/docs/#/安装使用/开始安装) +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. 下载安装包 [https://gitee.com/dromara/Jpom/attach_files](https://gitee.com/dromara/Jpom/attach_files) -2. 解压文件 -3. 安装插件端( [流程说明](https://jpom-site.keepbx.cn/docs/#/安装使用/开始安装?id=安装插件端) ) - 1. agent-x.x.x-release 目录为插件端的全部安装文件 - 2. 上传到对应服务器 - 3. 命令运行(Agent.sh、Agent.bat) - 4. 默认运行端口:`2123` -4. 安装服务端( [流程说明](https://jpom-site.keepbx.cn/docs/#/安装使用/开始安装?id=安装服务端) ) - 1. server-x.x.x-release 目录为服务端的全部安装文件 - 2. 上传到对应服务器 - 3. 命令运行(Server.sh、Server.bat) - 4. 默认运行端口:`2122` 访问管理页面 例如`http://127.0.0.1:2122/` - -### 编译安装 - -> [帮助文档](https://jpom-site.keepbx.cn/docs/#/安装使用/开始安装) - -1. 访问 [Jpom](https://gitee.com/dromara/Jpom) 的码云主页,拉取最新完整代码(建议使用master分支) -2. 切换到`web-vue`目录 执行`npm install` (vue环境需要提前搭建和安装依赖包详情可以查看web-vue目录下README.md) -3. 执行`npm build`进行vue项目打包(vue环境需要提前搭建和安装依赖包详情可以查看web-vue目录下README.md) -4. 切换到项目根目录执行:`mvn clean package` -5. 安装插件端( [流程说明](https://jpom-site.keepbx.cn/docs/#/安装使用/开始安装?id=安装插件端) ) - 1. 查看插件端安装包 modules/agent/target/agent-x.x.x-release - 2. 打包上传服务器运行 - 3. 命令运行(Agent.sh、Agent.bat) - 4. 默认运行端口:`2123` -6. 安装服务端( [流程说明](https://jpom-site.keepbx.cn/docs/#/安装使用/开始安装?id=安装服务端) ) - 1. 查看插件端安装包 modules/server/target/server-x.x.x-release - 2. 打包上传服务器运行 - 3. 命令运行(Server.sh、Server.bat) - 4. 默认运行端口:`2122` 访问管理页面 例如`http://127.0.0.1:2122/` - -> 也可以使用 `script/release.bat` `script/release.sh` 快速打包 - -### 编译运行 - -1. 访问 [Jpom](https://gitee.com/dromara/Jpom) 的码云主页,拉取最新完整代码(建议使用master分支、如果想体验新功能请使用dev分支) -2. 运行插件端 - 1. 运行`io.jpom.JpomAgentApplication` - 2. 注意控制台打印的默认账号密码信息 - 3. 默认运行端口:`2123` -3. 运行服务端 - 1. 运行`io.jpom.JpomServerApplication` - 2. 默认运行端口:`2122` -4. 构建vue页面 切换到`web-vue`目录(前提需要本地开发环境有node、npm环境) -5. 安装项目vue依赖 控制台执行 `npm install` -6. 启动开发模式 控制台执行 `npm serve` -7. 根据控制台输出的地址访问前端页面 例如`http://127.0.0.1:3000/` +## 构建案例仓库代码 -### 管理命令 +1. [Jboot 案例代码](https://gitee.com/keepbx/Jpom-demo-case/tree/master/jboot-test) +2. [SpringBoot 案例代码(ClassPath)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/springboot-test) +3. [SpringBoot 案例代码(Jar)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/springboot-test-jar) +4. [node vue 案例代码(antdv)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/antdv) +5. [python 案例代码](https://gitee.com/keepbx/Jpom-demo-case/tree/master/python) -1. windows 中 Agent.bat 、Server.bat +> Node.js 编译指定目录: +```bash +yarn --cwd xxxx/ install +yarn --cwd xxxx/ build ``` -# 服务端 -Server.bat 启动管理面板(按照面板提示输入操作) -# 插件端 -Agent.bat 启动管理面板(按照面板提示输入操作) +> Maven 编译指定目录: + +```bash +mvn -f xxxx/pom.xml clean package ``` -> windows 中执行启动后需要根据日志取跟进启动的状态 +## 🛠️ 整体架构 -2. linux 中 Agent.sh 、Server.sh +![jpom-func-arch](https://jpom.top/images/jpom-func-arch.png) -``` -# 服务端 -Server.sh start 启动Jpom服务端 -Server.sh stop 停止Jpom服务端 -Server.sh restart 重启Jpom服务端 -Server.sh status 查看Jpom服务端运行状态 -Server.sh create 创建Jpom服务端的应用服务(jpom-server) -# 插件端 -Agent.sh start 启动Jpom插件端 -Agent.sh stop 停止Jpom插件端 -Agent.sh restart 重启Jpom插件端 -Agent.sh status 查看Jpom插件端运行状态 -Agent.sh create 创建Jpom插件端的应用服务(jpom-agent) -``` +## 🐞 交流讨论 、反馈 BUG、提出建议等 -### linux 服务方式管理 +1. 快扫描下方左侧微信群二维码和我们一起交流讨论吧!(添加小助手:备注 Jpom 进群) +2. 开源项目离不开社区的支持,如果项目帮助到了您,并且想给我们加个餐。 + 欢迎扫描下方[微信、支付宝收款码赞赏](https://jpom.top/images/qrcode/praise-all.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. 参与贡献,请查看[贡献指南](#贡献指南)。 -> 在使用 `Server.sh create`/`Agent.sh create` 成功后 -> -> service jpom-server {status | start | stop} -> -> service jpom-agent {status | start | stop} +感谢所有赞赏以及参与贡献的小伙伴,您们的支持是我们不断更新前进的动力! +

+praise img +

-### Jpom 的参数配置 +## 💖 周边商品 -在项目运行的根路径下的`extConfig.yml`文件 +为了更好地维持开源项目,我们决定推出周边商品。 -1. 插件端示例:[`extConfig.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/agent/src/main/resources/bin/extConfig.yml) -2. 服务端示例:[`extConfig.yml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/bin/extConfig.yml) +购买支持我们这样您既获得了一份小商品我们也获得了您购买商品的利润(周边商品的价格会比市场价稍高,介意请勿下单) -### 演示项目 +

+shop home +

-[https://jpom.keepbx.cn](https://jpom.keepbx.cn) +## 🔨贡献指南 -``` -账号:demo -密码:demo123 -``` +> 提交贡献即认为签署了 [CLA](https://gitee.com/dromara/Jpom/blob/master/CLA.md) 协议 -> 演示系统有部分功能做了限制,完整功能请自行部署体验 +### 贡献须知 -> 如果出现登录不上,请联系我们,联系方式在最底部 +Jpom 作为开源项目,离不开社区的支持,欢迎任何人修改和提出建议。贡献无论大小,您的贡献会帮助背后成千上万的使用者以及开发者,您做出的贡献也会永远的保留在项目的贡献者名单中,这也是开源项目的意义所在! -1. [Jboot案例代码](https://gitee.com/keepbx/Jpom-demo-case/tree/master/jboot-test) -2. [SpringBoot案例代码(ClassPath)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/springboot-test) -3. [SpringBoot案例代码(Jar)](https://gitee.com/keepbx/Jpom-demo-case/tree/master/springboot-test-jar) +为了保证项目代码的质量与规范,以及帮助您更快的了解项目的结构,请在贡献之前阅读: -### 常见问题、操作说明 +- [Jpom 贡献说明](https://gitee.com/dromara/Jpom/blob/master/CODE_OF_CONDUCT.md) +- [中英文排版规范](https://gitee.com/dromara/Jpom/blob/dev/typography-specification.md) -[https://jpom-site.keepbx.cn/docs/](https://jpom-site.keepbx.cn/docs/) +### 贡献步骤 -[https://jpom-site.keepbx.cn/docs/#/FQA/FQA](https://jpom-site.keepbx.cn/docs/#/FQA/FQA) +1. Fork 本仓库。 -[Jpom 插件开发](https://gitee.com/keepbx/Jpom-Plugin) +2. Fork 后会在您的账号下多了一个和本仓库一模一样的仓库,把您账号的仓库 clone 到本地。 -### 交流讨论 、提供bug反馈或建议 + 注意替换掉链接中的`分支名`和`用户名`。 -1. 微信群二维码(添加小助手:备注Jpom 进群) + 如果是贡献代码,分支名填 `dev`;如果是贡献文档,分支名填 `docs` -![Alt text](https://cdn.jsdelivr.net/gh/jiangzeyin/Jpom-site/images/wx_qrcode.jpg) + ```bash + git clone -b 分支名 https://gitee.com/用户名/Jpom.git + ``` -2. 微信公众号:[CodeGzh](https://cdn.jsdelivr.net/gh/jiangzeyin/Jpom-site/docs/images/CodeGzh-QrCode.jpg) 查看一些基础教程 +3. 修改代码/文档,修改后提交上来。 -3. 码云: [issues](https://gitee.com/dromara/Jpom/issues) + ```bash + # 把修改的文件添加到暂存区 + git add . + # 提交到本地仓库,说明您具体做了什么修改 + git commit -m '填写您做了什么修改' + # 推送到远程仓库,分支名替换成 dev 或者 docs + git push origin 分支名 + ``` -4. [码云赞赏: 在码云仓库项目首页下方捐赠、打赏](https://gitee.com/dromara/Jpom) +4. 登录您的仓库,然后会看到一条 PR 请求,点击请求合并,等待管理员把您的代码合并进来。 -5. 微信赞赏 [赞赏记录](./docs/praise/praise.md) +### 项目分支说明 -![微信赞赏](https://cdn.jsdelivr.net/gh/jiangzeyin/Jpom-site/images/wx_praise_small.png) +| 分支 | 说明 | +|--------|------------------------------------------------------| +| master | 主分支,受保护分支,此分支不接受 PR。在 beta 分支后经过测试没问题后会合并到此分支。 | +| beta | beta 版本 分支,受保护分支,此分支不接受 PR。在 dev 分支后经过测试没问题后会合并到此分支。 | +| dev | 开发分支,接受 PR,PR 请提交到 dev 分支。 | +| docs | 项目文档分支,接受 PR,介绍项目功能、汇总常见问题等。 | +> 目前用到的主要是 dev 和 docs 分支,接受 PR 修改,其他的分支为归档分支,贡献者可以不用管。 -### 精品项目推荐 +### 贡献者 -|项目名称 | 项目地址 | 项目介绍 | -|---|---|---| -| SpringBoot_v2 | [https://gitee.com/bdj/SpringBoot_v2](https://gitee.com/bdj/SpringBoot_v2) | 基于springboot的一款纯净脚手架| -| TLog GVP 项目 | [https://gitee.com/dromara/TLog](https://gitee.com/dromara/TLog) | 一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪 | -| Sa-Token | [https://gitee.com/dromara/sa-token](https://gitee.com/dromara/sa-token) | 这可能是史上功能最全的 Java 权限认证框架! | -| Erupt | [https://gitee.com/erupt/erupt](https://gitee.com/erupt/erupt) | 零前端代码,纯注解开发 admin 管理后台 | + + contributors + -### giteye +Made with [contrib.rocks](https://contrib.rocks). -[![Giteye chart](https://chart.giteye.net/gitee/dromara/Jpom/N4VGB7ZB.png)](https://giteye.net/chart/N4VGB7ZB) \ No newline at end of file +## 🌍 知识星球 + +

+扫码加入知识星球,了解学习更多知识 +

+ +## 🔔 精品项目推荐 + +| 项目名称 | 项目地址 | 项目介绍 | +|---------------|----------------------------------------------------------------------------|-----------------------------------------------| +| SpringBoot_v2 | [https://gitee.com/bdj/SpringBoot_v2](https://gitee.com/bdj/SpringBoot_v2) | 基于springboot的一款纯净脚手架 | +| TLog GVP 项目 | [https://gitee.com/dromara/TLog](https://gitee.com/dromara/TLog) | 一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪 | +| Sa-Token | [https://gitee.com/dromara/sa-token](https://gitee.com/dromara/sa-token) | 这可能是史上功能最全的 Java 权限认证框架! | +| Erupt | [https://gitee.com/erupt/erupt](https://gitee.com/erupt/erupt) | 零前端代码,纯注解开发 admin 管理后台 | +| 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: + +

+JetBrains +

diff --git a/README_CONTRIBUTE.md b/README_CONTRIBUTE.md deleted file mode 100644 index f12f797b89..0000000000 --- a/README_CONTRIBUTE.md +++ /dev/null @@ -1,71 +0,0 @@ -# Jpom 贡献说明 - -## 目录说明 - -``` -. -├── .gitee => gitee 配置 -├── docs => 一键安装的命令脚本以及版本号文件 -├── modules => java 后端目录(agent、server) -   ├── agent => 插件端代码 -   ├── commone => 这个项目的公共模块(插件端、服务端都依赖该模块) -   ├── server => 服务端代码 -├── script => 一些通用脚本 -├── web-vue => 前端 vue 目录 -   ├── .editorconfig => 前端(vue)代码格式配置 -├── .editorconfig => 全局代码格式配置 -├── .gitattributes => 文件编码格式配置 -└── .... => 仓库一些默认配置 -``` - -## 一些规范说明 - -1. 写完代码后在保证不影响其他的人的代码情况下尽量统一格式化一下代码 -2. Java 代码需要保证新增方法都有充足、标准的 JavaDoc 注释 -3. 在修改 Bug、新增功能尽量保证最小提交的方式提交代码,减少多个功能一个 commit -4. 所有接口 url 都需要遵循下划线模式 -5. Java 代码、方法需要遵循小驼峰法 -6. Java 类名需要遵循大驼峰法 -7. 前端项目统一采用 `prettier` 方式来格式化(需要安装插件) - -> 注:由于旧代码存在很多不规范问题,会逐步调整为新规范。在新写的代码都需要需要遵循上面说明 -> -> -### 类的文档注释规范(Javadoc) - -``` -/** - * xxxxxxxx - * @author xxxx - * @since ${DATE} - */ -``` - -> 这里采用 `@since` 声明创建日期是因为 `Javadoc` 规范里面并没有 `@date` 标记所以采用 `@since` 代替 - -### Java 代码规范 - -> 推荐安装 `Alibaba Java Coding Guidelines`(`p3c`) 插件 - -##### 代码级别的多行注释 - -[https://www.e-learn.cn/topic/3680721](https://www.e-learn.cn/topic/3680721) - -## changelog 更新规范 - -> 在新加功能、修护bug、优化功能在完成时候都需要在 [CHANGELOG.md](./CHANGELOG.md) 记录 - -1. 如果是使用者反馈的bug,在修护后需要备注反馈人的昵称 -2. 如果是 issue 需要备注 issue 地址以及平台(Gitee、GitHub) -3. 如果是 pr 需要备注 pr 地址以及平台(Gitee、GitHub) -4. 根据变动情况确定影响范围:如果影响 只:`agent`、`server` 其中一个,就使用【agent】、【server】开头,如果都影响就不用 -5. 可以视情况添加其他说明:如提交记录 - - -## 需要的小组 - -1. 后端小组 (主要任务:根据需求开发对应的接口) -2. 前端小组 (主要任务:优化前端 UI 交互和对接部分接口) -3. 文档小组 (主要任务:完善、补充 Jpom 使用文档) -4. 视频小组 (主要任务:录制 Jpom 相关的使用视频) -5. 测试小组 (主要任务:参与 Jpom 新版内测、日常开发测试相关任务) \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..a2210d5740 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +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: | +| 2.8.1-2.8.6 | :x: | +| < 2.7 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/best_practices.md b/best_practices.md deleted file mode 100644 index d7f0a7430f..0000000000 --- a/best_practices.md +++ /dev/null @@ -1,30 +0,0 @@ -# Jpom最佳实践征文 - -## 活动介绍 - -在我们使用Jpom部署运维过程中,经常会get到一些最佳的解决方法,或者在使用过程中遇到不太明白的问题,再使用比较早或者大佬已经知道怎么更好的使用Jpom实现需要的效果, -为了更好地帮忙大家使用Jpom,在此举办最佳实践征文,为后面使用Jpom的同学提供参考, -快来秀一秀你使用Jpom部署运维的的神操作,顺便拿个大奖~ - -## 奖品 - -1. 一篇有效博文链接10-50元现金红包 -2. 一篇搜索引擎收录博文链接20-50元现金红包 - -## 参与方式 - -1. oschina(开源中国) -2. csdn -3. 简书 -4. 博客园 - -## 推荐博文主题 - -1. 如何部署Jpom -2. 集群环境使用Jpom -3. Jpom 节点管理 -4. Jpom的基础配置 - -## 领奖方式 - -1. 加入微信群:添加微信 `jpom66` 根据提示进入微信群,发博文链接,管理确认后将进行现金私发红包 diff --git a/docker-compose-cluster.yml b/docker-compose-cluster.yml new file mode 100644 index 0000000000..7ff60f23d3 --- /dev/null +++ b/docker-compose-cluster.yml @@ -0,0 +1,95 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +version: '3.8' +services: + server: + env_file: + - env-beta.env + image: jpomdocker/jpom:server-${JPOM_VERSION} + build: + dockerfile: ./modules/server/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + environment: + "JPOM_SERVER_TEMP_TOKEN": ${SERVER_TOKEN} + "jpom.cluster.id": server00 + volumes: + - jpom-server-data:/usr/local/jpom-server/data + - jpom-server-logs:/usr/local/jpom-server/logs + - jpom-server-conf:/usr/local/jpom-server/conf + ports: + - "2122:2122" + hostname: server + server01: + env_file: + - env-beta.env + image: jpomdocker/jpom:server-${JPOM_VERSION} + build: + dockerfile: ./modules/server/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + environment: + "JPOM_SERVER_TEMP_TOKEN": ${SERVER_TOKEN} + "jpom.cluster.id": server01 + volumes: + - jpom-server01-data:/usr/local/jpom-server/data + - jpom-server01-logs:/usr/local/jpom-server/logs + - jpom-server01-conf:/usr/local/jpom-server/conf + ports: + - "2120:2122" + hostname: server01 + agent01: + env_file: + - env-beta.env + image: jpomdocker/jpom:agent-${JPOM_VERSION} + build: + dockerfile: ./modules/agent/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + RUN_ARG: --auto-push-to-server 'http://server:2122/api/node/receive_push?token=${SERVER_TOKEN}&workspaceId=DEFAULT' + + volumes: + - jpom-agent01:/usr/local/jpom-agent + ports: + - "2123:2123" + depends_on: + - server + hostname: agent01 + agent02: + env_file: + - env-beta.env + image: jpomdocker/jpom:agent-${JPOM_VERSION} + build: + dockerfile: ./modules/agent/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + RUN_ARG: --auto-push-to-server 'http://server01:2122/api/node/receive_push?token=${SERVER_TOKEN}&workspaceId=DEFAULT' + + volumes: + - jpom-agent02:/usr/local/jpom-agent + ports: + - "2124:2123" + depends_on: + - server01 + hostname: agent02 +volumes: + jpom-agent01: + jpom-agent02: + jpom-server-data: + jpom-server-logs: + jpom-server-conf: + jpom-server01-data: + jpom-server01-logs: + jpom-server01-conf: diff --git a/docker-compose-local.yml b/docker-compose-local.yml new file mode 100644 index 0000000000..1c8be68bc0 --- /dev/null +++ b/docker-compose-local.yml @@ -0,0 +1,72 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +version: '3.8' +services: + server: + env_file: + - env-release.env + image: jpomdocker/jpom:server-${JPOM_VERSION} + build: + dockerfile: ./modules/server/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + environment: + "JPOM_SERVER_TEMP_TOKEN": ${SERVER_TOKEN} + volumes: + - jpom-server-data:/usr/local/jpom-server/data + - jpom-server-logs:/usr/local/jpom-server/logs + - jpom-server-conf:/usr/local/jpom-server/conf + ports: + - "2122:2122" + hostname: server + agent01: + env_file: + - env-release.env + image: jpomdocker/jpom:agent-${JPOM_VERSION} + build: + dockerfile: ./modules/agent/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + RUN_ARG: --auto-push-to-server 'http://server:2122/api/node/receive_push?token=${SERVER_TOKEN}&workspaceId=DEFAULT' + + volumes: + - jpom-agent01:/usr/local/jpom-agent + ports: + - "2123:2123" + depends_on: + - server + hostname: agent01 + agent02: + env_file: + - env-release.env + image: jpomdocker/jpom:agent-${JPOM_VERSION} + build: + dockerfile: ./modules/agent/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + RUN_ARG: --auto-push-to-server 'http://server:2122/api/node/receive_push?token=${SERVER_TOKEN}&workspaceId=DEFAULT' + + volumes: + - jpom-agent02:/usr/local/jpom-agent + ports: + - "2124:2123" + depends_on: + - server + hostname: agent02 +volumes: + jpom-agent01: + jpom-agent02: + jpom-server-data: + jpom-server-logs: + jpom-server-conf: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..4425bfb519 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +version: '3.8' +services: + server: + env_file: + - env-release.env + image: jpomdocker/jpom:server-${JPOM_VERSION} + build: + dockerfile: ./modules/server/Dockerfile + context: . + args: + JPOM_VERSION: ${JPOM_VERSION} + environment: + "jpom.authorize.token": ${SERVER_TOKEN} + volumes: + - jpom-server-data:/usr/local/jpom-server/data + - jpom-server-logs:/usr/local/jpom-server/logs + - jpom-server-conf:/usr/local/jpom-server/conf + ports: + - "2122:2122" +volumes: + jpom-server-data: + jpom-server-logs: + jpom-server-conf: diff --git a/docs/apidoc.json b/docs/apidoc.json new file mode 100644 index 0000000000..f884ff94d3 --- /dev/null +++ b/docs/apidoc.json @@ -0,0 +1,8 @@ +{ + "name": "Jpom 服务端接口文档", + "version": "1.0.0", + "description": "Jpom 服务端接口文档", + "title": "Jpom 服务端接口文档", + "url": "http://127.0.0.1:2122/", + "sampleUrl": "http://127.0.0.1:2122/" +} \ No newline at end of file diff --git a/docs/changelog/2.3.x.md b/docs/changelog/2.3.x.md deleted file mode 100644 index b5a10b9180..0000000000 --- a/docs/changelog/2.3.x.md +++ /dev/null @@ -1,61 +0,0 @@ -# 2.3.1 ~ 2.3.2 版本日志 - -# 2.3.2 - -### 新增功能 - -1. 控制台日志支持配置保留天数 -2. 项目列表状态跟随控制台刷新 -3. 项目配置页面优化交互流程 -4. 项目列表显示正在运行的项目的端口号(感谢@洋芋) -5. 新版的Windows管理命令(感谢@洋芋) -6. 支持类似于Nginx二级代理配置(感谢@№譜樋) -7. 记录启动、重启、停止项目的操作人 -8. Jpom 数据路径默认为程序运行的路径(感谢@〓下页) -9. 首页进程监听表格显示端口号(感谢@洋芋) -10. 保存时检查Oss信息是否正确 -11. Jpom管理命令新增判断`JAVA_HOME`环境变量 -12. 修改用户信息,在线用户需要重新登录 - -### 解决BUG、优化功能 - -1. 修改WebHooks 不生效 -2. 初始化系统白名单初始化失败(感谢@洋芋) -3. 指定Cookie名称防止名称相同被踢下线(感谢@洋芋) -4. 优化未加载到tools.jar的提示(感谢@№譜樋) -5. 构建按钮移动到文件管理页面中 -6. 优化nginx列表显示数据、取消nginx快捷配置 -7. 证书管理页面交互优化 -8. 取消安全模式功能(有更完善的权限代替) -9. 管理员不能修改自己的信息 - ------------------------------------------------------------ - -# 2.3.1 - -#### 新增功能 - -1. 添加创建项目判断项目id是否被占用 -2. 项目列表中添加悬停突出显示效果 -3. 生产环境中检查Jpom 运行标识和项目id是否冲突 -4. windows 管理命令支持停止Jpom -5. 防止暴力登录新增限制ip登录失败次数 -6. 用户前台输入密码传输加密(感谢@JAVA jesion) -7. 首页页面自动刷新按钮状态记忆功能(感谢@Mark) -8. Jpom启动成功会自动在数据目录中创建进程id信息文件如`pid.27936` -9. 证书管理支持导出、查看代码模板功能 - -#### 解决BUG - -1. 解决配置JVM、ARGS时,不能获取到程序运行信息bug(感谢@Agoni 、) -2. 减少登录图形验证码干扰线(感谢@Mark) -3. 项目编辑页面JVM、ARGS调整为多行文本(感谢@JAVA jesion) -4. jar模式MainClass非必填 -4. 优化JDK32位和64位冲突时自动跳过(感谢@13145597) -5. 用户授权项目权限不足问题 - -#### 升级注意事项 - -1. 由2.2及以下升级到 2.3.x 需要手动删除Jpom数据目录中的`data/user.json` 文件、所有用户账户信息将失效需要重新添加 - ------------------------------------------------------------ \ No newline at end of file diff --git a/docs/changelog/2.4.x.md b/docs/changelog/2.4.x.md deleted file mode 100644 index e4e42f9c13..0000000000 --- a/docs/changelog/2.4.x.md +++ /dev/null @@ -1,303 +0,0 @@ -# 2.4.0 ~ 2.4.9 版本日志 - - -# 2.4.9 - 3.0.0(beta) - -> 当前版本为重构页面后的预览版本 - -### 新增功能 - -1. 【Server】新增监控用户操作记录 -2. 【Agent】新增配置是否禁用根据jmx获取项目状态(默认启用) -3. 项目文件管理支持在线修改文件(感谢@Chen 贡献) -4. 3.0.0bata版本的页面重构[采用vue项目编写](感谢@Hotstrip) -5. 新增项目启动banner输出(感谢@Hotstrip) - -### 解决BUG、优化功能 - -1. 【Server】 优化判断构建命令中的删除命令关键词 -2. 【Server】 优化删除构建历史、构建代码(避免不能删除情况) -3. 【Agent】 调整项目的jvm 和 args参数支持url编码。避免xss后冲突 -4. 优化获取项目当前运行路径问题 -5. 【Server】开始构建时输出代码目录 -6. 【Server】编辑构建类型为SVN没有分组bug(感谢@JAVA-落泪归枫) -7. 更新文档Jpom 的JDK要为1.8.0_40+(感谢@JAVA 企鹅) -8. 【Server】数据库初始化时间前置,打印成功日志,未初始化结束数据库相关操作都忽略 -9. 【Server】修复报警恢复后,报警列表中的报警状态显示报警中的错误(感谢@南有乔木) -10. 更新hutool 版本至5.4.x (能避免系统缓存页面里面获取文件大小卡死) -11. 调整Jpom启动输出日志,启动消息采用控制台输出不再打印error级别的启动消息 - -> 特别感谢:@Hotstrip 对Jpom的前端页面采用vue重构编写 -> -> 当前版本为3.x版本前的过渡版本 - ------------------------------------------------------------ - -# 2.4.8 - -### 新增功能 - -1. 【Agent】读取进程新增 `ps -ef | grep xxx` 方式(感谢@JAVA-落泪归枫) - -### 解决BUG、优化功能 - -1. 【Server】构建历史中记录字段不全问题(感谢@£天空之城~龙) -2. 【Server】Java-WebSocket 模块漏洞版本更新 来源 [Github GHSA-gw55-jm4h-x339](https://github.com/advisories/GHSA-gw55-jm4h-x339) -3. 【Server】节点分发列表点击控制台、文件管理404 -4. 【Server】节点分发顺序重启休眠时间取构建名称最后的时间(测试构建:10 则睡眠时间为10秒) -5. 【Agent】启动完成打印授权信息日志级别调至error -6. CommandUtil.asyncExeLocalCommand 方法格式化命令中的换行 -7. 优化启动读取进程文件目录避免包含node_modules 目录卡死 -8. 【Server】修复构建命令中判断是否包含【rm、del、rd】bug (感谢@落泪归枫) -9. 【Server】修改删除节点会修改掉非管理员的账号密码bug -10. 【Server】 构建历史根据权限查询 - ------------------------------------------------------------ - -# 2.4.7 - -### 新增功能 - -1. [支持maven快速编辑节点项目](https://gitee.com/keepbx/Jpom-Plugin/tree/master/jpom-maven-plugin) (配合`jpom-maven-plugin`使用)( - 感谢@夜空中最亮的星) -2. 【Agent】 新增jdk 管理,不同项目选择不同的jdk (GITEE@IV8ZZ) -3. 【Server】构建新增分组属性,方便快速选择 -4. 【Agent】 新增[JavaExtDirsCp] 运行模式 (感谢@TXpcmgr(Geiger)) -5. 【Server】 ssh 连接方式新增私钥证书连接 -6. 【Server】 ssh文件管理新增解压操作(感谢@TXpcmgr(Geiger)贡献) -7. 【Agent】 项目新建副本集,方便单机快速运行多个副本 -8. 【Server】构建发布后操作支持副本集相关操作 - -### 解决BUG、优化功能 - -1. 完善使用nginx之类代理二级目录,指定端口路径跳转问题(感谢@😯😨😰😱 ) -2. 解决菜单路径不正确问题(GITEE@I15O46) -3. 【Agent】 windows中Agent关闭,Agent中所有项目跟随关闭(感谢@java gods) -4. 【Server】构建命令包含删除命令误判断(感谢@Sawyer) -5. 【Server】构建历史支持配置单个构建最多保存多少个历史 -6. 【Server】解决节点分组筛选bug(感谢gitee@I17XEH) -7. 【Server】角色权限动态数据,单个节点异常不影响所有节点配置(感谢@£天空之城~龙) -8. 【Server】关联节点分发项目支持修改发布后操作 -9. 补充说明文档:[详情](https://jpom-site.keepbx.cn/docs/index.html#/) (感谢@TXpcmgr(Geiger)) -10. 更新部分插件依赖版本【hutool、fast-boot、fastjson】 - -> 注意:如果在2.4.7以下项目运行方式中使用过【War】模式的由于【War】更名为【JarWar】 所有在升级后请重新修改运行方式后再运行对应项目 - ------------------------------------------------------------ - -# 2.4.6 - -### 新增功能 - -1. 【Agent】 nginx管理支持自定义编译运行,管理方式变更 -2. 【Server】 监控通知新增企业微信(感谢@TinyBao。) -3. 管理脚本支持自动识别环境变量和java路径(linux环境) -4. 项目类型新增File(快速管理纯静态文件) - -### 解决BUG、优化功能 - -1. 【Server】解决分发列表项目状态显示不正确(感谢@群友) -2. 【Server】修复权限选择错乱和无法正确过滤问题【注意此版本的角色动态权限不兼容旧数据,需要重新授权动态数据权限】(感谢@Java-OutMan) -3. 调整项目日志输出 -4. 更新【commons-compress】依赖版本[漏洞升级] -5. 【Server】构建弹窗条件构建名称(感谢@Sawyer) -6. json文件读取异常提示(感谢@Taller) -9. 【Server】 优化ssh上传文件、删除文件 -10. InternalError 异常捕捉 -11. 【Server】优化Nginx 非80、443端口 二级路径代理重定向问题(感谢@😯😨😰😱 ) - -### 升级注意 - -1. 此版本更新控制台日志级别有调整,如果使用管理命令方式运行日志级别将不再打印info级别,如果需要打印info级别的请调整管理命令中的`--spring.profiles.active=pro` - 为 `--spring.profiles.active=dev` -2. 使用Nginx 二级路径代理请一定使用Jpom 推荐nginx配置[查看配置](https://jpom-site.keepbx.cn/docs/index.html#/辅助配置/nginx-config) - ------------------------------------------------------------ - -# 2.4.5 - -### 新增功能 - -1. 【Server】节点列表支持筛选(感谢@£天空之城~龙) -2. 【Server】新增构建触发器(感谢@java 麦田英雄) -3. 【Server】新增自动清理过量的构建历史记录和文件(感谢@Sawyer、@Jvmlz) -4. 【Server】构建支持ssh发布(感谢@£天空之城~龙) -5. 【Server】节点新增分组属性,方便多节点快速筛选(感谢@£天空之城~龙) -6. 新增windows快速升级 -7. 【Server】layui升级到最新版,文件上传支持进度条 -8. 新增节点内存、cpu、硬盘使用情况采集报表(感谢@£天空之城~龙) -9. 节点首页新增快速结束进程方式 - -### 解决BUG、优化功能 - -1. 【Server】节点分发需要节点数大于二(感谢@Sawyer) -2. 修复未加载到tools.jar判断(感谢@java-磊) -3. 【Server】控制台新增自动清屏开关(感谢@Jvmlz) -4. 上传文件大小限制,配置化 -5. 【Server】构建文件copy忽略隐藏文件 -6. 【Server】不能清除错误进程缓存(感谢@java 李道甫) -7. 【Agent】长时间运行jpom无法监控到项目运行状态(感谢@java 李道甫、@洋芋) -8. 【Server】节点分发编辑支持修改分发后的操作 -9. 【Agent】脚本模板跟随系统编码 -10. 【Server】tomcat控制台删除日志文件错误(感谢@Java-iwen) -11. 【Agent】自动备份控制台日志表达式为none,不生成日志备份 -12. 【Server】角色授权编辑权限不能创建数据(感谢@Lostshadow) -13. 【Server】tomcat动态权限配置不正确(感谢@Lostshadow) - ------------------------------------------------------------ - -# 2.4.4 - -### 新增功能 - -1. 【Agent】添加对SpringBoot war包支持 - -### 解决BUG、优化功能 - -1. 【Server】新项目打开项目控制台页面报错(感谢@黄战虎) -2. 【Server】修改邮箱不及时生效问题(感谢@WeChat) -3. 【Server】修复发布构建产物路径bug(感谢@Sawyer) -4. 优化执行命令方式 -5. 脚本模板在linux 不添加权限(采用sh 方式执行) -6. 【Server】修复添加节点分发项目报错的数据异常(感谢@WeChat) - ------------------------------------------------------------ - -# 2.4.3 - -### 新增功能 - -1. SpringBoot 升级到2.1.x -2. 【Server】velocity模板引擎升级为thymeleaf -3. 【Server】构建支持svn类型仓库(感谢@群友 .) -4. 插件端自动注册到服务端(感谢@群友 .) -5. 新增在线修改配置并可及时重启 -6. 新增WebSSH 管理功能 -7. 【Server】用户新增邮箱和钉钉群webhook 属性 -8. 【Server】监控报警通知改为联系人 -9. 【Server】引人netty插件(感谢@夜空中最亮的星) -10. 支持docker 容器运行(感谢@24k) -11. 【Server】 新增清空构建代码(解决代码冲突)(感谢@xieyue200810) -12. 搭建插件化基础架构 -13. 用户权限重构,使用角色支持更细粒的权限控制 -14. 新增ssh快速部署插件端 -15. 新增一键安装脚本[详情](https://gitee.com/dromara/Jpom/#%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85) - -### 解决BUG、优化功能 - -1. 【Server】未登录重定向带入参数 -2. 【Server】页面登录方法调整支持自定义事件登录 -3. 【Server】删除节点、分发验证是否存在关联数据,分发释放分发关系 -4. 项目白名单目录调整为属性 -5. 【Server】编辑用户回显节点选中错乱问题 -6. 调整linux管理命令脚本防止在线升级产生tail 进程 -7. 【Agent】插件端的脚本模板路径切换到数据目录下 -8. 【Agent】Windows异步执行命令调整不使用[INHERIT](防止插件端进程阻塞) -9. 【Server】分页查询会存在字段not found -10. 【Server】构建命令不能包含删除命令(del,rd,rm) -11. 支持配置初始读取日志文件最后多少行【log.intiReadLine】(感谢@夜空中最亮的星) -12. 优化节点首页饼状图统计 -13. 取消用户输入脚本模板id -14. 重定向支持自动识别 Proto(解决http-> https iframe报错) -15. 构建执行命令存在错误只是提示,不取消执行(感谢@Sawyer) -16. 构建打包目录没有文件名异常(感谢@Sawyer) -17. 修改为专属包名【io.jpom】 - -### 升级注意事项 - -1. 由于修改包名引起:如果在旧版本中使用过在线升级,本次升级需要手动上传jar到到服务器中执行命令升级,并且删除旧包并且覆盖管理命令文件 - ------------------------------------------------------------ - -# 2.4.2 - -### 新增功能 - -1. 新增实时查看tomcat日志 -2. 【Server】分发包支持更多压缩格式 -3. 页面菜单采用json文件配置(支持二级菜单) -4. 【Server】分发包支持更多类型的压缩格式 -5. 【Server】节点支持配置请求超时时间 -6. 支持配置是否记录请求、响应日志【consoleLog.reqXss、consoleLog.reqResponse】 -7. 新增日志记录最大记录条数【默认100000】 -8. 【Server】layui 升级到2.5.4 -9. 【Server】新增项目监控功能 -10. 【Server】新增在线构建项目功能 -11. 【Server】新增查看项目实际执行的命令行 -12. 【Server】新增分发日志 -13. 新增清空文件缓存、临时数据缓存 -14. 在线查看、下载Jpom运行日志(windows不能实时查看) -15. 新增linux在线升级 - -### 解决BUG、优化功能 - -1. 【Agent】logBack页面最后修改时间不能正确显示(感谢@JAVA jesion) -2. 【Agent】nginx修改内容截断,不正确情况(感谢@JAVA jesion) -3. 【Agent】nginx、脚本模板保存内容xss标签还原 -4. 【Server】节点分发页面的交互方式 -5. 【Server】页面菜单分类整理 -6. 【Agent】修复SpringBoot相对文件夹下无法读取配置问题 -7. 【Agent】缓存异常的jvm进程,避免卡死状态(感谢@java 李道甫) -8. 【Server】节点分发状态更新到所有节点状态 -9. 【Server】节点分发白名单独立页面配置 -10. 【Server】项目控制台未运行能查看已经存在的最后的日志 -11. 【Agent】删除阿里云oss构建,已经有在线构建功能代替 -12. 【Server】修改证书名称和导出证书问题 -13. 打包方式改为一个可执行的jar -14. 【Server】解决编辑用户页面json转换异常(感谢@JAVA jesion) -15. 分发项目新增清空发布防止新旧jar冲突 -16. 【Server】优化节点列表页面加载速度[不显示运行的项目数](感谢@java 李道甫) -17. 【Agent】调整启动,关闭进程命令执行方式[解决重启不能监控项目状态](感谢@java 李道甫) -18. 【Agent】调整进程标识传入参数到JVM参数中,避免和部分框架冲突(感谢@java-杨侨) - -### 升级注意事项 - -1. 需要删除旧lib目录所有文件 -2. 覆盖旧版管理命令文件 - ------------------------------------------------------------ - -# 2.4.1 - -### 新增功能 - -1. 【Agent】新增线程列表监控(感谢@其锋) -2. 【Agent】新增节点脚本模板(感谢@其锋) -3. 【Server】新增所有页面添加公共Html代码 -4. 新增Tomcat管理 -5. 【Agent】导入证书文件新增对cer、crt文件支持 -6. 【Agent】导入项目包时指出多压缩包[tar|bz2|gz|zip|tar.bz2|tar.gz] (感谢@群友) -7. 【Agent】新增配置控制台日志文件编码格式(详情查看extConfig.yml) - -### 解决BUG、优化功能 - -1. 【Server】节点首页,右上角管理路径错误(感谢@其锋) -2. 【Server】查看用户操作日志支持筛选用户 -3. 【Server】页面数据路径权限判断修复(感谢@Will) -4. 【Agent】优化获取进程监听端口的,防止卡死 -5. 文件的读写锁不使用 synchronized关键字提高效率 -6. 优化数据id字段的输入限制,数字+字母+中划线+下划线(感谢@JAVA jesion) -7. 【Agent】连接JVM失败则跳过(感谢@JAVA jesion) -8. 【Server】编辑用户页面优化选择授权项目 -9. 【Agent】项目Jvm参数和Args参数兼容回车符(感谢@牛旺) - ------------------------------------------------------------ - -# 2.4.0 - -### 新增功能 - -1. 首页进程列表显示属于Jpom项目名称(感谢@〓下页) -2. 多节点统一管理(插件模式) -3. 证书解析支持cer 证书(感谢@JAVA jesion) -4. 新增记录用户操作日志[采用H2数据库](感谢@〓下页) -5. 节点分发功能、合并管理项目(感谢@其锋) - -### 解决BUG、优化功能 - -1. 解析端口信息兼容`:::8084`(感谢@Agoni 、) -2. 进程id解析端口、解析项目名称带缓存 -3. 项目分组变更,项目列表及时刷新(感谢@〓下页) -4. 批量上传文件数量进度显示(感谢@群友) -5. linux udp端口信息解析失败(感谢@Ruby) -6. jar模式读取主jar包错误(感谢@其锋) \ No newline at end of file diff --git a/docs/changelog/2.x.md b/docs/changelog/2.x.md deleted file mode 100644 index 687d83722c..0000000000 --- a/docs/changelog/2.x.md +++ /dev/null @@ -1,28 +0,0 @@ -# 2.0 ~ 2.2 版本日志 - -# 2.2 - -1. 解决批量上传文件造成卡死的问题 -2. 控制台读取自动识别文件编码格式 -3. 退出登录出现异常页面 -4. 根据对应权限显示对应菜单 -5. 系统管理员可以在线解锁锁定的用户 - ------------------------------------------------------------ - -# 2.1 - -1. 全面取消调用命令文件执行 -2. 静态资源缓存问题 -3. 首页监控图表更新 -4. 多处细节优化 -5. 分别支持ClassPath和Jar模式 -6. 证书文件支持验证私钥是否匹配 - ------------------------------------------------------------ - -# 2.0 - -1. 优化安全问题 -2. 兼容windows -3. 使用JVM获取运行状态 diff --git a/docs/fun-releases/.funignore b/docs/fun-releases/.funignore deleted file mode 100644 index a69cf12257..0000000000 --- a/docs/fun-releases/.funignore +++ /dev/null @@ -1,3 +0,0 @@ -.env -template.yml -.funignore diff --git a/docs/fun-releases/index.py b/docs/fun-releases/index.py deleted file mode 100644 index 284c4e27fc..0000000000 --- a/docs/fun-releases/index.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -import requests - -OK = '200 OK' -FOUND = '302 FOUND' -TEXT_HEADER = [('Content-type', 'text/plain')] -TAB_NAME = 'tag_name' - - -# 使用码云资源 -def ossDownload(environ, start_response): - # 查询版本 - result = requests.get('https://dromara.gitee.io/jpom/docs/versions.json') - json = result.json() - return doJson(environ, start_response, json) - - -# 使用GitHub -def githubOssDownload(environ, start_response): - # 查询版本 - result = requests.get('https://api.github.com/repos/dromara/Jpom/releases/latest') - json = result.json() - return doJson(environ, start_response, json) - - -# 查询版本号 -def showVersion(environ, start_response): - # 查询版本 - result = requests.get('https://api.github.com/repos/dromara/Jpom/releases/latest') - json = result.json() - tag_name = getVersion(json) - if tag_name == '': - return responseOK('没有tagName', start_response) - return responseOK(tag_name, start_response) - - -def getVersion(json): - if json and TAB_NAME in json: - tag_name = json[TAB_NAME] - if tag_name and tag_name.strip() != '': - tag_name = tag_name.replace('v', '') - return tag_name.strip() - else: - return '' - else: - return '' - - -# 处理查询到的版本json -def doJson(environ, start_response, json): - tag_name = getVersion(json) - if tag_name == '': - return responseOK('没有tagName', start_response) - # 处理请求参数 - try: - query_string = environ['QUERY_STRING'] - except KeyError: - query_string = "" - pars = query_string.split('&') - typeName = 'Server' - for par in pars: - if par.startswith('type='): - typeName = par.strip().split("=")[1] - # 重定向到下载地址 - url = "https://jpom-releases.oss-cn-hangzhou.aliyuncs.com/" + typeName.lower() + "-" + tag_name + "-release.zip" - start_response(FOUND, [('Location', url)]) - return [bytes(url, encoding="utf8")] - - -# 响应ok -def responseOK(value, start_response): - start_response(OK, TEXT_HEADER) - return [bytes(value, encoding="utf8")] diff --git a/docs/fun-releases/local.py b/docs/fun-releases/local.py deleted file mode 100644 index 89cb3ababb..0000000000 --- a/docs/fun-releases/local.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding:utf-8 -*- -# -*- created by: mo -*- - - -import requests -import tornado.ioloop -import tornado.web - - -class MainHandler(tornado.web.RequestHandler): - def get(self): - """get请求""" - type = self.get_argument('type') - result = requests.get('https://api.github.com/repos/dromara/Jpom/releases/latest') - json = result.json() - tag_name = json['tag_name'] - if tag_name.strip() == '': - self.write("没有发布版") - return - tag_name = tag_name.replace('v', '') - print(tag_name) - url = "https://jpom-releases.oss-cn-hangzhou.aliyuncs.com/" + type + "-" + tag_name + "-release.zip" - print(url) - self.redirect(url) - - -application = tornado.web.Application([(r"/", MainHandler), ]) - -if __name__ == "__main__": - application.listen(8868) - tornado.ioloop.IOLoop.instance().start() diff --git a/docs/fun-releases/template.yml b/docs/fun-releases/template.yml deleted file mode 100644 index 143b25b42b..0000000000 --- a/docs/fun-releases/template.yml +++ /dev/null @@ -1,56 +0,0 @@ -# https://github.com/alibaba/funcraft/blob/master/docs/specs/2018-04-03-zh-cn.md -ROSTemplateFormatVersion: '2015-09-01' -Transform: 'Aliyun::Serverless-2018-04-03' -Resources: - jpom: - Type: 'Aliyun::Serverless::Service' - Properties: - Description: 'jpom' - jpom-releases: - Type: 'Aliyun::Serverless::Function' - Properties: - Handler: index.ossDownload - Runtime: python3 - CodeUri: './' - Description: 'Jpom gitee安装url处理' - MemorySize: 320 - Timeout: 5 - InitializationTimeout: 5 - Events: - install-api: - Type: HTTP - Properties: - AuthType: ANONYMOUS - Methods: ['GET'] - jpom-releases2: - Type: 'Aliyun::Serverless::Function' - Properties: - Handler: index.githubOssDownload - Runtime: python3 - CodeUri: './' - Description: 'Jpom github安装url处理' - MemorySize: 320 - Timeout: 5 - InitializationTimeout: 5 - Events: - install-api: - Type: HTTP - Properties: - AuthType: ANONYMOUS - Methods: ['GET'] - jpom-getVerson: - Type: 'Aliyun::Serverless::Function' - Properties: - Handler: index.showVersion - Runtime: python3 - CodeUri: './' - Description: 'Jpom 查询最新版本号' - MemorySize: 320 - Timeout: 5 - InitializationTimeout: 5 - Events: - install-api: - Type: HTTP - Properties: - AuthType: ANONYMOUS - Methods: ['GET'] \ No newline at end of file diff --git a/docs/install.sh b/docs/install.sh deleted file mode 100644 index cc8e66a8ba..0000000000 --- a/docs/install.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash -PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:$(cd `dirname $0`; pwd) -export PATH -LANG=en_US.UTF-8 - -# 解压命令 -if [[ ! -f "/usr/bin/unzip" ]];then - #rm -f /etc/yum.repos.d/epel.repo - yum install unzip -y -fi -TYPE="$1" - -module="$2" - -# 判断是否包含jdk -installJd="jdk" - -if [[ $module = *$installJdk* ]]; then - if [[ ! -x "${JAVA_HOME}/bin/java" ]]; then - JAVA=`which java` - if [[ ! -x "$JAVA" ]]; then - # 判断是否存在文件 - if [[ ! -f "jdk-8u251-linux-x64.tar.gz" ]]; then - wget -O jdk-8u251-linux-x64.tar.gz https://jpom-releases.oss-cn-hangzhou.aliyuncs.com/jdk-8u251-linux-x64.tar.gz - fi - mkdir /usr/java/ - # - tar -zxf jdk-8u251-linux-x64.tar.gz -C /usr/java/ - # - #PATH=$PATH:/usr/java/jdk1.8.0_251/bin - #export PATH - echo '安装jdk,路径/usr/java/jdk1.8.0_251/' - # 修改环境变量 - echo ''>>/etc/profile - echo 'export JAVA_HOME=/usr/java/jdk1.8.0_251'>>/etc/profile - echo 'export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar'>>/etc/profile - echo 'export PATH=$PATH:$JAVA_HOME/bin'>>/etc/profile - #export JAVA_HOME=/usr/java/jdk1.8.0_251 - # 更新环境变量 - source /etc/profile - # 删除jdk压缩包 - rm -f jdk-8u251-linux-x64.tar.gz - else - echo "已经存在java环境${JAVA}/bin/java" - fi - else - echo "已经存在java环境${JAVA_HOME}/bin/java" - fi -fi - -# 判断是否包含mvn -installMvn="mvn" - -if [[ $module = *$installMvn* ]]; then - if [[ ! -x "${MAVEN_HOME}/bin/mvn" ]]; then - MVN=`which mvn` - if [[ ! -x "$MVN" ]]; then - # 判断是否存在文件 - if [[ ! -f "apache-maven-3.6.3-bin.tar.gz" ]]; then - wget -O apache-maven-3.6.3-bin.tar.gz https://jpom-releases.oss-cn-hangzhou.aliyuncs.com/apache-maven-3.6.3-bin.tar.gz - fi - mkdir /usr/maven/ - # - tar -zxf apache-maven-3.6.3-bin.tar.gz -C /usr/maven/ - # - echo '安装maven,路径/usr/maven/apache-maven-3.6.3/' - # 修改环境变量 - echo ''>>/etc/profile - echo 'export MAVEN_HOME=/usr/maven/apache-maven-3.6.3/'>>/etc/profile - echo 'export PATH=$PATH:$MAVEN_HOME/bin'>>/etc/profile - - export MAVEN_HOME=/usr/maven/apache-maven-3.6.3/ - export PATH=$MAVEN_HOME/bin:$PATH - # 删除maven压缩包 - rm -f apache-maven-3.6.3-bin.tar.gz - else - echo "已经存在maven环境${MVN}/bin/mvn" - fi - else - echo "已经存在maven环境${MAVEN_HOME}/bin/mvn" - fi -fi - -# 判断 -if [[ -z "${TYPE}" ]] ; then - TYPE="Server"; -fi -# 判断是否在文件 -if [[ ! -f "${TYPE}.zip" ]]; then - # 下载 - wget -O ${TYPE}.zip https://1232788122276831.cn-beijing.fc.aliyuncs.com/2016-08-15/proxy/jpom/jpom-releases/?type=${TYPE} -fi -# 解压 -unzip -o ${TYPE}.zip -# 删除安装包 -rm -f ${TYPE}.zip -# 删除安装命令 -rm -f install.sh -# 添加权限 -chmod 755 ${TYPE}.sh -# 启动 -sh ${TYPE}.sh start diff --git a/docs/jpom-service.sh b/docs/jpom-service.sh deleted file mode 100644 index 212b0fac54..0000000000 --- a/docs/jpom-service.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# chkconfig: 356 10 90 -# description: Jpom-Server service -# processname: jpom-server -# The MIT License (MIT) -# -# Copyright (c) 2019 码之科技工作室 -# -# 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.' > $FILE_NAME - -# loading env -if [ -f /etc/profile ]; then - . /etc/profile -fi -if [ -f /etc/bashrc ]; then - . /etc/bashrc -fi -if [ -f ~/.bashrc ]; then - . ~/.bashrc -fi -if [ -f ~/.bash_profile ]; then - . ~/.bash_profile -fi - -RUN_PATH="JPOM_RUN_PATH" - -# 启动程序 -function start() { - sh ${RUN_PATH} start -} - -# 停止程序 -function stop() { - sh ${RUN_PATH} stop -} - -# 获取程序状态 -function status() { - sh ${RUN_PATH} status -} - -# 提示使用语法 -function usage() { - echo "Usage: $0 {start|stop|restart|status}" - RETVAL="2" -} - -# See how we were called. -RETVAL="0" -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - status) - status - ;; - *) - usage - ;; -esac - -exit $RETVAL diff --git a/docs/js/version.js b/docs/js/version.js deleted file mode 100644 index 9c27caddb6..0000000000 --- a/docs/js/version.js +++ /dev/null @@ -1 +0,0 @@ -var version = '2.7.3'; diff --git a/docs/praise/praise.md b/docs/praise/praise.md deleted file mode 100644 index 794337ee29..0000000000 --- a/docs/praise/praise.md +++ /dev/null @@ -1,26 +0,0 @@ -# 赞赏公示 - -| 日期 | 渠道 | 金额 | 昵称 | -|------------|---|-----|---------| -| 2021-12-10 | 微信赞赏码 | 50 | Dream | -| 2021-10-25 | 微信赞赏码 | 1 | Jame | -| 2021-10-11 | 微信赞赏码 | 10 | chenice | -| 2021-10-11 | 微信赞赏码 | 1 | chenice | -| 2021-09-01 | 微信赞赏码 | 100 | CoCo | -| 2021-09-01 | 微信赞赏码 | 20 | 大灰灰 | -| 2021-08-31 | 微信赞赏码 | 1 | 大灰灰 | - - -### 历史赞赏(已经消费) - - -| 日期 | 渠道 | 金额 | 昵称 | -|---|---|---|---| -| 2021-04-21 | 码云捐赠 | 10 | [jason](https://gitee.com/bwcx_jzy) | -| 2020-03-31 | 码云捐赠 | 20 | [开源oschina](https://gitee.com/bdj) | -| 2020-02-25 | 码云捐赠 | 20 | [辣椒酱](https://gitee.com/yokead_admin) | -| 2020-02-13 | 码云捐赠 | 100 | [大森林](https://gitee.com/jmdhappy) | -| 2019-08-20 | 码云捐赠 | 15 | [YountMan](https://gitee.com/YountMan) | -| 2019-07-29 | 码云捐赠 | 50 | [Yashin](https://gitee.com/yashin) | -| 2019-07-28 | 码云捐赠 | 100 | 老李 | -| 2019-03-27 | 码云捐赠 | 10 | [jason](https://gitee.com/bwcx_jzy) | \ No newline at end of file diff --git a/docs/versions.json b/docs/versions.json deleted file mode 100644 index 384f113b97..0000000000 --- a/docs/versions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "tag_name": "v2.7.3", - "agentUrl": "https://jpom-releases.oss-cn-hangzhou.aliyuncs.com/agent-2.7.3-release.zip", - "serverUrl": "https://jpom-releases.oss-cn-hangzhou.aliyuncs.com/server-2.7.3-release.zip", - "changelogUrl": "https://gitee.com/dromara/Jpom/raw/master/CHANGELOG.md" -} diff --git a/docs/wx_qrcode.jpg b/docs/wx_qrcode.jpg deleted file mode 100644 index ff651a74e9..0000000000 Binary files a/docs/wx_qrcode.jpg and /dev/null differ diff --git a/env-beta.env b/env-beta.env new file mode 100644 index 0000000000..5b5fa58aa4 --- /dev/null +++ b/env-beta.env @@ -0,0 +1,3 @@ +JPOM_VERSION=2.11.6.6 +# Server Token 生产部署请更换 +SERVER_TOKEN=7094f673-2c53-4fc1-82e7-86e528449d97 diff --git a/env-release.env b/env-release.env new file mode 100644 index 0000000000..91dc2b61ce --- /dev/null +++ b/env-release.env @@ -0,0 +1,3 @@ +JPOM_VERSION=2.11.6 +# Server Token 生产部署请更换 +SERVER_TOKEN=7094f673-2c53-4fc1-82e7-86e528449d97 diff --git a/index.html b/index.html deleted file mode 100644 index ca2301dbe5..0000000000 --- a/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - jpom - - - 进入中:https://jpom-site.keepbx.cn - - \ No newline at end of file diff --git a/modules/agent-transport/agent-transport-common/pom.xml b/modules/agent-transport/agent-transport-common/pom.xml new file mode 100644 index 0000000000..fbb83741f5 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/pom.xml @@ -0,0 +1,51 @@ + + + + 4.0.0 + + org.dromara.jpom.agent-transport + jpom-agent-transport-parent + 2.11.6.6 + ../pom.xml + + + agent-transport-common + + + 8 + 8 + UTF-8 + + + + + cn.hutool + hutool-core + + + + org.slf4j + slf4j-api + true + + + + com.alibaba.fastjson2 + fastjson2 + true + + + + diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/DataContentType.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/DataContentType.java new file mode 100644 index 0000000000..f654d5d1a1 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/DataContentType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +/** + * 请求数据传输类型 + * + * @author bwcx_jzy + * @since 2022/12/24 + */ +public enum DataContentType { + /** + * URL + */ + FORM_URLENCODED, + /** + * JSON + */ + JSON +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/DownloadCallback.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/DownloadCallback.java new file mode 100644 index 0000000000..19fbed9bb5 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/DownloadCallback.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import lombok.Builder; +import lombok.Data; + +import java.io.InputStream; + +/** + * @author bwcx_jzy + * @since 2022/12/24 + */ +@Builder +@Data +public class DownloadCallback { + + private String contentDisposition; + + private String contentType; + + private InputStream inputStream; +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/INodeInfo.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/INodeInfo.java new file mode 100644 index 0000000000..6491c5c0ba --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/INodeInfo.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import java.net.Proxy; + +/** + * 节点通讯的 接口 + * + * @author bwcx_jzy + * @since 2022/12/23 + */ +public interface INodeInfo { + + /** + * 节点名称 + * + * @return 名称 + */ + String name(); + + /** + * 节点 url + *

+ * HOST:PORT + * + * @return 节点 url + */ + String url(); + + /** + * 协议 + * + * @return http + */ + String scheme(); + + /** + * 节点 授权信息 + * sha1(user@pwd) + * + * @return 用户 + */ + String authorize(); + + /** + * 节点通讯代理 + * + * @return proxy + */ + Proxy proxy(); + + /** + * 超时时间 + * + * @return 超时时间 单位秒 + */ + Integer timeout(); + + /** + * 传输加密方式 + * + * @return 传输加密方式 0 不加密 1 BASE64 2 AES + */ + Integer transportEncryption(); +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/IProxyWebSocket.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/IProxyWebSocket.java new file mode 100644 index 0000000000..905947b6b1 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/IProxyWebSocket.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +/** + * @author bwcx_jzy + * @since 2022/12/26 + */ +public interface IProxyWebSocket extends AutoCloseable { + + /** + * 关闭连接 + * + * @throws IOException 关闭异常 + */ + void close() throws IOException; + + /** + * 打开连接,默认停留一秒 + * + * @return 打开状态 + */ + boolean connect(); + + /** + * 重新打开连接 + * + * @return 打开状态 + * @throws IOException 关闭异常 + */ + default boolean reconnect() throws IOException { + this.close(); + return this.connect(); + } + + /** + * 重新打开连接 + * + * @return 打开状态 + * @throws IOException 关闭异常 + */ + default boolean reconnectBlocking() throws IOException { + this.close(); + return this.connectBlocking(); + } + + /** + * 打开连接,使用节点配置的超时时间 + * + * @return 打开状态 + */ + boolean connectBlocking(); + + /** + * 打开连接,阻塞指定时间 + * + * @param seconds 阻塞时间 建议大于 1秒 + * @return 打开状态 + */ + boolean connectBlocking(int seconds); + + /** + * 发送消息 + * + * @param msg 消息 + * @throws IOException 发送异常 + */ + void send(String msg) throws IOException; + + /** + * 发送消息 + * + * @param bytes 消息 + * @throws IOException 发送异常 + */ + void send(ByteBuffer bytes) throws IOException; + + /** + * 收到消息 + * + * @param consumer 回调 + */ + void onMessage(Consumer consumer); + + /** + * 是否连接上 + * + * @return true + */ + boolean isConnected(); + + /** + * 获取关闭状态描述 + * + * @return 状态描述 + */ + String getCloseStatusMsg(); +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/IUrlItem.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/IUrlItem.java new file mode 100644 index 0000000000..e1d6da6c62 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/IUrlItem.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/12/23 + */ +public interface IUrlItem { + + /** + * 请求路径 + * + * @return path + */ + String path(); + + /** + * 请求超时时间 + * 单位秒 + * + * @return 超时时间 + */ + Integer timeout(); + + /** + * 当前工作空间id + * + * @return 工作空间 + */ + String workspaceId(); + + /** + * 请求类型 + * + * @return contentType + */ + DataContentType contentType(); + + /** + * 请求头 + * + * @return 请求头 + */ + Map header(); +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransformServer.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransformServer.java new file mode 100644 index 0000000000..c1267d6cfd --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransformServer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import com.alibaba.fastjson2.TypeReference; + +/** + * 消息转换服务 + * + * @author bwcx_jzy + * @since 2022/12/24 + */ +public interface TransformServer { + + /** + * 数据类型转换 + * + * @param data 数据 + * @param tTypeReference 类型 + * @param 范型 + * @return data + */ + T transform(String data, TypeReference tTypeReference); + + /** + * 数据类型转换,只返回成功的数据 + * + * @param data 数据 + * @param tClass 类型 + * @param 范型 + * @return data + */ + T transformOnlyData(String data, Class tClass); + + /** + * 转换异常 + * + * @param e 请求的异常 + * @param nodeInfo 节点信息 + * @return 转换后的异常 + */ + default Exception transformException(Exception e, INodeInfo nodeInfo) { + return e; + } +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransformServerFactory.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransformServerFactory.java new file mode 100644 index 0000000000..c549bc0c51 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransformServerFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.ServiceLoaderUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * @author bwcx_jzy + * @since 2022/12/24 + */ +@Slf4j +public class TransformServerFactory { + + /** + * 获得单例的 TransformServer + * + * @return 单例的 TransformServer + */ + public static TransformServer get() { + return Singleton.get(TransformServer.class.getName(), TransformServerFactory::doCreate); + } + + /** + * 根据用户引入的 Transform 客户端引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code TransformServer} + */ + public static TransformServer of() { + final TransformServer transportServer = doCreate(); + log.debug("Use [{}] Agent Transport As Default.", transportServer.getClass().getSimpleName()); + return transportServer; + } + + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code EngineFactory} + */ + private static TransformServer doCreate() { + final TransformServer engine = ServiceLoaderUtil.loadFirstAvailable(TransformServer.class); + if (null != engine) { + return engine; + } + + throw new RuntimeException("No jpom agent transform jar found ! Please add one of it to your project !"); + } +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportAgentException.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportAgentException.java new file mode 100644 index 0000000000..3effc42997 --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportAgentException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import lombok.NoArgsConstructor; + +/** + * @author bwcx_jzy + * @since 2022/12/24 + */ +@NoArgsConstructor +public class TransportAgentException extends RuntimeException { + + public TransportAgentException(String message) { + super(message); + } +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportServer.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportServer.java new file mode 100644 index 0000000000..89233a6e5e --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportServer.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import com.alibaba.fastjson2.TypeReference; + +import java.util.function.Consumer; + +/** + * 插件端消息传输服务 + * + * @author bwcx_jzy + * @since 2022/12/18 + */ +public interface TransportServer { + + /** + * 请求 header + */ + String WORKSPACE_ID_REQ_HEADER = "workspaceId"; + + String JPOM_AGENT_AUTHORIZE = "Jpom-Agent-Authorize"; + + String TRANSPORT_ENCRYPTION = "transport-encryption"; + + /** + * 执行请求 + * + * @param nodeInfo 节点信息 + * @param urlItem 请求 item + * @param data 参数 + * @return 响应的字符串 + */ + String execute(INodeInfo nodeInfo, IUrlItem urlItem, Object data); + + /** + * 执行请求,返回响应的所有数据 + * + * @param nodeInfo 节点信息 + * @param urlItem 请求 item + * @param data 参数 + * @param tTypeReference 返回的泛型 + * @param 泛型 + * @return 响应的字符串 + */ + default T executeToType(INodeInfo nodeInfo, IUrlItem urlItem, Object data, TypeReference tTypeReference) { + String body = this.execute(nodeInfo, urlItem, data); + return TransformServerFactory.get().transform(body, tTypeReference); + } + + /** + * 执行请求,仅返回成功的数据 + * + * @param nodeInfo 节点信息 + * @param urlItem 请求 item + * @param data 参数 + * @param tClass 返回的泛型 + * @param 泛型 + * @return 响应的字符串 + */ + default T executeToTypeOnlyData(INodeInfo nodeInfo, IUrlItem urlItem, Object data, Class tClass) { + String body = this.execute(nodeInfo, urlItem, data); + return TransformServerFactory.get().transformOnlyData(body, tClass); + } + + /** + * 下载文件 + * + * @param nodeInfo 节点信息 + * @param urlItem 请求 item + * @param data 参数 + * @param consumer 回调 + */ + void download(INodeInfo nodeInfo, IUrlItem urlItem, Object data, Consumer consumer); + + /** + * 创建 websocket 连接 + * + * @param nodeInfo 节点信息 + * @param urlItem 请求 item + * @param parameters 参数 + * @return websocket + */ + IProxyWebSocket websocket(INodeInfo nodeInfo, IUrlItem urlItem, Object... parameters); +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportServerFactory.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportServerFactory.java new file mode 100644 index 0000000000..838e77888e --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportServerFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.ServiceLoaderUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * @author bwcx_jzy + * @since 2022/12/23 + */ +@Slf4j +public class TransportServerFactory { + + /** + * 获得单例的 TransportServer + * + * @return 单例的 TransportServer + */ + public static TransportServer get() { + return Singleton.get(TransportServer.class.getName(), TransportServerFactory::doCreate); + } + + /** + * 根据用户引入的 Transport 客户端引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code TransportServer} + */ + public static TransportServer of() { + final TransportServer transportServer = doCreate(); + log.debug("Use [{}] Agent Transport As Default.", transportServer.getClass().getSimpleName()); + return transportServer; + } + + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code EngineFactory} + */ + private static TransportServer doCreate() { + final TransportServer engine = ServiceLoaderUtil.loadFirstAvailable(TransportServer.class); + if (null != engine) { + return engine; + } + + throw new RuntimeException("No jpom agent transport jar found ! Please add one of it to your project !"); + } +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/i18n/II18nMessageUtil.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/i18n/II18nMessageUtil.java new file mode 100644 index 0000000000..189f2c542f --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/i18n/II18nMessageUtil.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport.i18n; + +/** + * @author bwcx_jzy1 + * @since 2024/6/11 + */ +public interface II18nMessageUtil { + + /** + * 获取翻译 + * + * @param key 键 + * @return 翻译 + */ + String get(String key); +} diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/i18n/TransportI18nMessageUtil.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/i18n/TransportI18nMessageUtil.java new file mode 100644 index 0000000000..a0ae9b250c --- /dev/null +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/i18n/TransportI18nMessageUtil.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport.i18n; + +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.ServiceLoaderUtil; + +/** + * @author bwcx_jzy1 + * @since 2024/6/11 + */ +public class TransportI18nMessageUtil { + + /** + * 获得单例的 TransportServer + * + * @return 单例的 TransportServer + */ + public static String get(String key) { + return Singleton.get(II18nMessageUtil.class.getName(), TransportI18nMessageUtil::doCreate).get(key); + } + + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code EngineFactory} + */ + private static II18nMessageUtil doCreate() { + final II18nMessageUtil engine = ServiceLoaderUtil.loadFirstAvailable(II18nMessageUtil.class); + if (null != engine) { + return engine; + } + + throw new RuntimeException("No jpom IMessageUtil jar found ! Please add one of it to your project !"); + } +} diff --git a/modules/agent-transport/agent-transport-http/pom.xml b/modules/agent-transport/agent-transport-http/pom.xml new file mode 100644 index 0000000000..e8acaa36bd --- /dev/null +++ b/modules/agent-transport/agent-transport-http/pom.xml @@ -0,0 +1,69 @@ + + + + 4.0.0 + + org.dromara.jpom.agent-transport + jpom-agent-transport-parent + 2.11.6.6 + ../pom.xml + + + agent-transport-http + + + 8 + 8 + UTF-8 + + + + + org.dromara.jpom.plugins + encrypt + ${project.version} + + + + org.dromara.jpom.agent-transport + agent-transport-common + ${project.version} + + + + cn.hutool + hutool-http + + + + org.slf4j + slf4j-api + true + + + + com.alibaba.fastjson2 + fastjson2 + true + + + + org.springframework.boot + spring-boot-starter-websocket + true + + + + 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 new file mode 100644 index 0000000000..9b244c94d4 --- /dev/null +++ b/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/HttpTransportServer.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.*; +import com.alibaba.fastjson2.JSONObject; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.encrypt.EncryptFactory; +import org.dromara.jpom.encrypt.Encryptor; +import org.dromara.jpom.transport.i18n.TransportI18nMessageUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * 插件端消息传输服务 + * + * @author bwcx_jzy + * @since 2022/12/18 + */ +@Slf4j +public class HttpTransportServer implements TransportServer { + + + private HttpRequest createRequest(INodeInfo nodeInfo, IUrlItem urlItem, Method method) { + String url = StrUtil.format("{}://{}/", nodeInfo.scheme(), nodeInfo.url()); + UrlBuilder urlBuilder = UrlBuilder.of(url).addPath(urlItem.path()); + HttpRequest httpRequest = HttpRequest.of(urlBuilder); + httpRequest.setMethod(method); + // 添加请求头 + Map header = urlItem.header(); + httpRequest.headerMap(header, true); + + Optional.ofNullable(urlItem.timeout()).ifPresent(integer -> httpRequest.timeout(integer * 1000)); + + httpRequest.header(TRANSPORT_ENCRYPTION, nodeInfo.transportEncryption() + ""); + + httpRequest.header(JPOM_AGENT_AUTHORIZE, nodeInfo.authorize()); + // + httpRequest.header(WORKSPACE_ID_REQ_HEADER, urlItem.workspaceId()); + Optional.ofNullable(nodeInfo.proxy()).ifPresent(httpRequest::setProxy); + return httpRequest; + } + + private HttpRequest createRequest(INodeInfo nodeInfo, IUrlItem urlItem) { + return createRequest(nodeInfo, urlItem, Method.POST); + } + + @SuppressWarnings("unchecked") + private void appendRequestData(HttpRequest httpRequest, IUrlItem urlItem, Object data, INodeInfo nodeInfo) { + DataContentType dataContentType = urlItem.contentType(); + Optional.ofNullable(data).ifPresent(o -> { + Encryptor encryptor; + try { + encryptor = EncryptFactory.createEncryptor(nodeInfo.transportEncryption()); + if (dataContentType == DataContentType.FORM_URLENCODED) { + if (o instanceof Map) { + Map map = (Map) o; + Map encryptedMap = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + String encryptedKey = encryptor.encrypt(entry.getKey()); + Object value = entry.getValue(); + Object newValue; + if (value instanceof String[]) { + String[] valueStr = (String[]) value; + for (int i = 0; i < valueStr.length; i++) { + valueStr[i] = encryptor.encrypt(valueStr[i]); + } + newValue = valueStr; + } else if (value instanceof Resource) { + newValue = value; + } else { + newValue = encryptor.encrypt(StrUtil.toStringOrNull(entry.getValue())); + } + encryptedMap.put(encryptedKey, newValue); + } + httpRequest.form(encryptedMap); + } else { + throw new IllegalArgumentException(TransportI18nMessageUtil.get("i18n.unsupported_type_with_colon.1050") + o.getClass()); + } + } else if (dataContentType == DataContentType.JSON) { + httpRequest.body(encryptor.encrypt(JSONObject.toJSONString(o)), ContentType.JSON.getValue()); + } else { + throw new IllegalArgumentException(TransportI18nMessageUtil.get("i18n.content_type_not_supported.81a9")); + } + } catch (Exception e) { + log.error(TransportI18nMessageUtil.get("i18n.encoding_error.b685"), e); + throw new TransportAgentException(TransportI18nMessageUtil.get("i18n.node_transfer_info_encoding_exception.12c8") + e.getMessage()); + } + }); + } + + private String executeRequest(HttpRequest httpRequest, INodeInfo nodeInfo, IUrlItem urlItem) { + // + if (log.isDebugEnabled()) { + log.debug("{}[{}] -> {} {}", nodeInfo.name(), httpRequest.getUrl(), urlItem.workspaceId(), Optional.ofNullable((Object) httpRequest.form()).orElse("-")); + } + return httpRequest.thenFunction(response -> { + int status = response.getStatus(); + String body = response.body(); + log.debug("Completed {}", body); + if (status != HttpStatus.HTTP_OK) { + log.warn(TransportI18nMessageUtil.get("i18n.response_exception_status_code.cbca"), nodeInfo.name(), status, body); + throw new TransportAgentException(nodeInfo.name() + TransportI18nMessageUtil.get("i18n.node_response_error.efc6") + status); + } + return body; + }); + } + + @Override + public String execute(INodeInfo nodeInfo, IUrlItem urlItem, Object data) { + HttpRequest httpRequest = this.createRequest(nodeInfo, urlItem); + this.appendRequestData(httpRequest, urlItem, data, nodeInfo); + try { + return this.executeRequest(httpRequest, nodeInfo, urlItem); + } catch (Exception e) { + throw Lombok.sneakyThrow(TransformServerFactory.get().transformException(e, nodeInfo)); + } + } + + + @Override + public void download(INodeInfo nodeInfo, IUrlItem urlItem, Object data, Consumer consumer) { + HttpRequest httpRequest = this.createRequest(nodeInfo, urlItem, Method.GET); + httpRequest.setFollowRedirects(true); + this.appendRequestData(httpRequest, urlItem, data, nodeInfo); + try (HttpResponse response1 = httpRequest.execute()) { + String contentDisposition = response1.header(Header.CONTENT_DISPOSITION); + String contentType = response1.header(Header.CONTENT_TYPE); + DownloadCallback build = DownloadCallback.builder() + .contentDisposition(contentDisposition).contentType(contentType).inputStream(response1.bodyStream()) + .build(); + consumer.accept(build); + } catch (Exception e) { + throw Lombok.sneakyThrow(TransformServerFactory.get().transformException(e, nodeInfo)); + } + } + + @Override + public IProxyWebSocket websocket(INodeInfo nodeInfo, IUrlItem urlItem, Object... parameters) { + String url = StrUtil.format("{}://{}/", nodeInfo.scheme(), nodeInfo.url()); + UrlBuilder urlBuilder = UrlBuilder.of(url).addPath(urlItem.path()); + // + urlBuilder.addQuery(JPOM_AGENT_AUTHORIZE, nodeInfo.authorize()); + // + urlBuilder.addQuery(WORKSPACE_ID_REQ_HEADER, urlItem.workspaceId()); + for (int i = 0; i < parameters.length; i += 2) { + Object parameter = parameters[i + 1]; + String value = Convert.toStr(parameter, StrUtil.EMPTY); + urlBuilder.addQuery(parameters[i].toString(), value); + } + urlBuilder.setWithEndTag(false); + String uriTemplate = urlBuilder.build(); + uriTemplate = StrUtil.removePrefixIgnoreCase(uriTemplate, nodeInfo.scheme()); + String wss = "wss"; + String ws = "ws"; + String protocol = "https".equalsIgnoreCase(nodeInfo.scheme()) ? wss : ws; + uriTemplate = StrUtil.format("{}{}", protocol, uriTemplate); + // + if (log.isDebugEnabled()) { + log.debug("{}[{}] -> {}", nodeInfo.name(), uriTemplate, urlItem.workspaceId()); + } + Integer timeout = urlItem.timeout(); + return new ServletWebSocketClientHandler(uriTemplate, timeout); + } +} diff --git a/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/ServletWebSocketClientHandler.java b/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/ServletWebSocketClientHandler.java new file mode 100644 index 0000000000..ceb771812b --- /dev/null +++ b/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/ServletWebSocketClientHandler.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.transport; + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.SystemPropsUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.transport.i18n.TransportI18nMessageUtil; +import org.springframework.util.Assert; +import org.springframework.util.unit.DataSize; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.WebSocketConnectionManager; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; +import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * @author bwcx_jzy + * @since 2022/12/26 + */ +@Slf4j +public class ServletWebSocketClientHandler extends AbstractWebSocketHandler implements IProxyWebSocket { + + private static final StandardWebSocketClient CLIENT = new StandardWebSocketClient(); + + private WebSocketSession session; + private final Integer timeout; + private final String uriTemplate; + private Consumer consumerText; + private WebSocketConnectionManager manager; + private CloseStatus closeStatus; + + public ServletWebSocketClientHandler(String uriTemplate, Integer timeout) { + this.uriTemplate = uriTemplate; + this.timeout = timeout; + } + + @Override + public void onMessage(Consumer consumer) { + this.consumerText = consumer; + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + Optional.ofNullable(this.consumerText).ifPresent(consumer -> consumer.accept(message.getPayload())); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + // 发送消息时间限制 60 秒 + long messageSizeLimit = SystemPropsUtil.getLong("JPOM_NODE_WEB_SOCKET_MESSAGE_SIZE_LIMIT", DataSize.ofMegabytes(5).toBytes()); + this.session = new ConcurrentWebSocketSessionDecorator(session, 60 * 1000, (int) messageSizeLimit); + // 消息大小限制 + this.session.setTextMessageSizeLimit((int) messageSizeLimit); + this.session.setBinaryMessageSizeLimit((int) messageSizeLimit); + } + + @Override + public void close() throws IOException { + if (this.manager == null) { + return; + } + this.manager.stop(); + this.manager = null; + this.session = null; + } + + @Override + public boolean connect() { + Assert.isNull(this.manager, "The connection has been established, do not repeat the connection"); + this.manager = new WebSocketConnectionManager(CLIENT, this, this.uriTemplate); + this.manager.start(); + // 时间不能太短,需要大于 1 秒 + return this.blocking(5); + } + + @Override + public boolean connectBlocking() { + int maxTimeout = Optional.ofNullable(this.timeout).orElse(60); + return this.connectBlocking(maxTimeout); + } + + @Override + public boolean connectBlocking(int seconds) { + if (this.connect()) { + return true; + } + return this.blocking(seconds); + } + + private boolean blocking(int seconds) { + int waitTime = 0; + do { + if (this.isConnected()) { + return true; + } + waitTime++; + ThreadUtil.sleep(500, TimeUnit.MILLISECONDS); + } while (waitTime * 2 <= seconds); + return false; + } + + @Override + public void send(String msg) throws IOException { + Assert.notNull(this.session, TransportI18nMessageUtil.get("i18n.not_connected.fa55")); + session.sendMessage(new TextMessage(msg)); + } + + @Override + public void send(ByteBuffer bytes) throws IOException { + Assert.notNull(this.session, TransportI18nMessageUtil.get("i18n.not_connected.fa55")); + session.sendMessage(new BinaryMessage(bytes)); + } + + @Override + public boolean isConnected() { + if (this.manager == null) { + return false; + } + return this.manager.isConnected(); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + log.error(TransportI18nMessageUtil.get("i18n.websocket_error.2bb4"), session.getId(), exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + this.closeStatus = status; + log.warn(TransportI18nMessageUtil.get("i18n.connection_closed.6d4e"), status.getCode(), status.getReason()); + } + + @Override + public String getCloseStatusMsg() { + return Optional.ofNullable(this.closeStatus) + .map(closeStatus -> StrUtil.format("{}:{}", closeStatus.getCode(), closeStatus.getReason())) + .orElse(StrUtil.EMPTY); + } +} diff --git a/modules/agent-transport/agent-transport-http/src/main/resources/META-INF/services/org.dromara.jpom.transport.TransportServer b/modules/agent-transport/agent-transport-http/src/main/resources/META-INF/services/org.dromara.jpom.transport.TransportServer new file mode 100644 index 0000000000..6f02d2f6f1 --- /dev/null +++ b/modules/agent-transport/agent-transport-http/src/main/resources/META-INF/services/org.dromara.jpom.transport.TransportServer @@ -0,0 +1 @@ +org.dromara.jpom.transport.HttpTransportServer diff --git a/modules/agent-transport/pom.xml b/modules/agent-transport/pom.xml new file mode 100644 index 0000000000..29c7d8de82 --- /dev/null +++ b/modules/agent-transport/pom.xml @@ -0,0 +1,173 @@ + + + + + jpom-parent + org.dromara.jpom + 2.11.6.6 + ../../pom.xml + + pom + + agent-transport-common + agent-transport-http + + 4.0.0 + 2.11.6.6 + org.dromara.jpom.agent-transport + jpom-agent-transport-parent + Jpom Agent Transport + + Jpom java 插件传输模块 + + + UTF-8 + 1.8 + + true + true + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + once + -Dfile.encoding=UTF-8 + + + + + + + + release + + + maven-repo + https://oss.sonatype.org/content/repositories/snapshots/ + + + maven-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + attach-javadoc + package + + jar + + + + + + + date + a + 创建时间 + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + verify-gpg + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + + + + + master + git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + + + + bwcx_jzy + bwcx_jzy@163.com + bwcx_jzy + + + + diff --git a/modules/agent/Dockerfile b/modules/agent/Dockerfile index 3599a8604a..7b4e5b452d 100644 --- a/modules/agent/Dockerfile +++ b/modules/agent/Dockerfile @@ -1,52 +1,52 @@ # -# The MIT License (MIT) +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. # -# Copyright (c) 2019 码之科技工作室 -# -# 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. -# - -FROM centos:7 -ENV LANG en_US.utf8 +FROM maven:3.9.6-eclipse-temurin-8 as builder +WORKDIR /target/dependency +COPY . . -ENV JPOM_HOME /usr/local/jpom-agent -ENV JPOM_PKG agent-2.8.0-release.zip +VOLUME ["/root/.m2"] +# 多次 builder 不同的版本号 +ARG TEMP_VERSION="" +ARG JPOM_VERSION +ENV USE_JPOM_VERSION=${JPOM_VERSION}${TEMP_VERSION} +RUN --mount=type=cache,target=/root/.m2 sh ./script/replaceVersion.sh ${USE_JPOM_VERSION} "release" -ADD jdk-8u202-linux-x64.tar.gz /usr/local/java/ -ENV JAVA_HOME /usr/local/java/jdk1.8.0_202 -ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar -ENV PATH $JAVA_HOME/bin:$PATH +RUN --mount=type=cache,target=/root/.m2 mvn -B -e -T 1C clean package -pl modules/agent -am -Dmaven.test.skip=true -Dmaven.compile.fork=true -s script/settings.xml -ADD apache-maven-3.8.4-bin.tar.gz /usr/local/maven/ -ENV MAVEN_HOME /usr/local/maven/apache-maven-3.8.4 -ENV PATH $MAVEN_HOME/bin:$PATH +FROM openjdk:8 +ARG BUILD_DATE +ARG JPOM_VERSION +ARG TEMP_VERSION="" +ARG RUN_ARG="" +ARG DEPENDENCY=/target/dependency -RUN yum install -y unzip +LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}" +LABEL maintainer="bwcx-jzy " +LABEL documentation="https://jpom.top" -RUN mkdir -p $JPOM_HOME -COPY $JPOM_PKG $JPOM_HOME -RUN unzip -o $JPOM_HOME/$JPOM_PKG -d $JPOM_HOME -RUN chmod +x $JPOM_HOME/Agent.sh -RUN rm -rf $JPOM_HOME/$JPOM_PKG +ENV JPOM_HOME /usr/local/jpom-agent +ENV JPOM_PKG_VERSION ${JPOM_VERSION}${TEMP_VERSION} +ENV JPOM_PKG agent-${JPOM_PKG_VERSION}-release +ENV RUN_ARG ${RUN_ARG} WORKDIR $JPOM_HOME +COPY --from=builder ${DEPENDENCY}/modules/agent/target/${JPOM_PKG} ${JPOM_HOME} + +# 时区 +ENV TZ Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +VOLUME $JPOM_HOME EXPOSE 2123 -ENTRYPOINT ["/bin/sh", "Agent.sh", "start"] +ENTRYPOINT ["/bin/bash", "./bin/Agent.sh", "start"] + diff --git a/modules/agent/pom.xml b/modules/agent/pom.xml index 3a535dbc94..df68cd6518 100644 --- a/modules/agent/pom.xml +++ b/modules/agent/pom.xml @@ -1,141 +1,212 @@ + - - jpom-parent - io.jpom - 2.8.0 - ../../pom.xml - - 4.0.0 - agent - 2.8.0 - Jpom 插件端 - - io.jpom.JpomAgentApplication - - - - io.jpom - common - ${pom.version} - - - - com.github.odiszapc - nginxparser - 0.9.6 - - - - org.bouncycastle - bcprov-jdk15on - 1.69 - - - org.apache.commons - commons-compress - 1.21 - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.1 - - - - ${start-class} - - true - - ./ - - - - ${project.version} - - ${maven.build.timestamp} - ${project.artifactId} - https://gitee.com/dromara/Jpom - - - - - - - - - false - ../../ - - CHANGELOG.md - LICENSE - - - - src/main/resources - false - - - - - - agent-default-profile - - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - true - ${start-class} - -Dfile.encoding=UTF-8 - - - - - repackage - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.1 - - ${project.build.sourceEncoding} - - script/release.xml - - target - - - - make-assembly - package - - single - - - - - - - - - - install-plugin-profile - - - release-plugin-profile - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + jpom-parent + org.dromara.jpom + 2.11.6.6 + ../../pom.xml + + 4.0.0 + agent + 2.11.6.6 + Jpom Agent + + org.dromara.jpom.JpomAgentApplication + + 1.0.0 + + + + + org.dromara.jpom + common + ${project.version} + + + + org.dromara.jpom.plugins + webhook + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + commons-codec + commons-codec + 1.15 + + + + + + + + + + + false + ../../ + + CHANGELOG.md + CHANGELOG-BETA.md + LICENSE + + + + src/main/resources + false + + + + + + agent-default-profile + + true + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + ${start-class} + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + ${jpom-min-version} + + true + + + logback.xml + application.yml + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + ${start-class} + -Dfile.encoding=UTF-8 + + + + org.projectlombok + lombok + + + + + + + repackage + + + + + + + + + + install-assembly + + true + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + ${project.build.sourceEncoding} + + src/main/assembly/release.xml + + ${project.build.directory} + + + + make-assembly + package + + single + + + + + + + net.nicoulaj.maven.plugins + checksum-maven-plugin + 1.11 + + + checksum-maven-plugin-files + package + + files + + + + + + + ${project.build.directory} + + *.jar + *.zip + *.tar.gz + + + + + SHA-1 + + + + + + + diff --git a/modules/agent/script/Agent.bat b/modules/agent/script/Agent.bat deleted file mode 100644 index 8dc80dbaa3..0000000000 --- a/modules/agent/script/Agent.bat +++ /dev/null @@ -1,142 +0,0 @@ -@REM The MIT License (MIT) -@REM -@REM Copyright (c) 2019 码之科技工作室 -@REM -@REM Permission is hereby granted, free of charge, to any person obtaining a copy of -@REM this software and associated documentation files (the "Software"), to deal in -@REM the Software without restriction, including without limitation the rights to -@REM use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -@REM the Software, and to permit persons to whom the Software is furnished to do so, -@REM subject to the following conditions: -@REM -@REM The above copyright notice and this permission notice shall be included in all -@REM copies or substantial portions of the Software. -@REM -@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -@REM FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -@REM COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -@REM IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -@REM CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -@REM - -@echo off -CHCP 65001 -setlocal enabledelayedexpansion - -@REM 设置环境变量,避免部分服务器没有 taskkill -set PATH = %PATH%;C:\Windows\system32;C:\Windows;C:\Windows\system32\Wbem - -set Tag=KeepBx-Agent-System-JpomAgentApplication -set MainClass=org.springframework.boot.loader.JarLauncher -set basePath=%~dp0 -set Lib=%basePath%lib\ -@REM 请勿修改----------------------------------↓ -set LogName=agent.log -@REM 在线升级会自动修改此属性 -set RUNJAR= -@REM 请勿修改----------------------------------↑ -@REM 是否开启控制台日志文件备份 -set LogBack=true -set JVM=-server -Xms200m -Xmx400m -set ARGS= --jpom.applicationTag=%Tag% --jpom.log=%basePath%log --spring.profiles.active=pro --server.port=2123 - -@REM 读取jar -call:listDir - -if "%1"=="" ( - color 0a - TITLE Jpom管理系统BAT控制台 - echo. ***** Jpom管理系统BAT控制台 ***** - ::************************************************************************************************************* - echo. - echo. [1] 启动 start - echo. [2] 关闭 stop - echo. [3] 查看运行状态 status - echo. [4] 重启 restart - echo. [5] 帮助 use - echo. [6] 清除 IP 白名单配置 - echo. [0] 退 出 0 - echo. - @REM 输入 - echo.请输入选择的序号: - set /p ID= - IF "!ID!"=="1" call:start - IF "!ID!"=="2" call:stop - IF "!ID!"=="3" call:status - IF "!ID!"=="4" call:restart - IF "!ID!"=="5" call:use - IF "!ID!"=="6" call:restart --rest:ip_config - IF "!ID!"=="0" EXIT -)else ( - if "%1"=="restart" ( - call:restart - )else ( - call:use - ) -) -if "%2" NEQ "upgrade" ( - PAUSE -)else ( - @REM 升级直接结束 -) -EXIT 0 - -@REM 启动 -:start - if "%JAVA_HOME%"=="" ( - echo 请配置【JAVA_HOME】环境变量 - PAUSE - EXIT 2 - ) - - echo 启动中.....启动成功后关闭窗口不影响运行 - echo 启动详情请查看:%LogName% - javaw %JVM% -Djava.class.path="%RUNJAR%" -Dapplication=%Tag% -Dbasedir=%basePath% %MainClass% %ARGS% %1 >> %basePath%%LogName% - timeout 3 -goto:eof - - -@REM 获取jar -:listDir - if "%RUNJAR%"=="" ( - for /f "delims=" %%I in ('dir /B %Lib%') do ( - if exist %Lib%%%I if not exist %Lib%%%I\nul ( - if "%%~xI" ==".jar" ( - if "%RUNJAR%"=="" ( - set RUNJAR=%Lib%%%I - ) - ) - ) - ) - )else ( - set RUNJAR=%Lib%%RUNJAR% - ) - echo 运行:%RUNJAR% -goto:eof - -@REM 关闭Jpom -:stop - java -Djava.class.path="%JAVA_HOME%/lib/tools.jar;%RUNJAR%" %MainClass% %ARGS% --event=stop -goto:eof - -@REM 查看Jpom运行状态 -:status - java -Djava.class.path="%JAVA_HOME%/lib/tools.jar;%RUNJAR%" %MainClass% %ARGS% --event=status -goto:eof - -@REM 重启Jpom -:restart - echo 停止中.... - call:stop - timeout 3 - echo 启动中.... - call:start %1 -goto:eof - -@REM 提示用法 -:use - echo please use (start、stop、restart、status) -goto:eof - - diff --git a/modules/agent/script/Agent.sh b/modules/agent/script/Agent.sh deleted file mode 100644 index 5f55275942..0000000000 --- a/modules/agent/script/Agent.sh +++ /dev/null @@ -1,219 +0,0 @@ -#!/bin/bash -# The MIT License (MIT) -# -# Copyright (c) 2019 码之科技工作室 -# -# 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. -# ssh 支持读取环境变量 -if [ -f /etc/profile ]; then - . /etc/profile -fi -if [ -f /etc/bashrc ]; then - . /etc/bashrc -fi -if [ -f ~/.bash_profile ]; then - . ~/.bash_profile -fi -if [ -f ~/.bashrc ]; then - . ~/.bashrc -fi -# 请不要修改 tag 属性的值,修改后会影响程序的停止、查看状态 -Tag="KeepBx-Agent-System-JpomAgentApplication" -# 自动获取当前路径 -Path=$( - cd $(dirname $0) - pwd -)"/" -Lib="${Path}lib/" -RUNJAR="" -Log="${Path}agent.log" -LogBack="${Path}log/" -JVM="-server -Xms200m -Xmx400m" -# 修改项目端口号 日志路径 -ARGS="--jpom.applicationTag=${Tag} --spring.profiles.active=pro --server.port=2123 --jpom.log=${Path}log $@" - -echo ${Tag} -echo ${Path} -RETVAL="0" -# 升级执行命令标识 -upgrade="$2" - -# now set the path to java -if [[ -x "${JAVA_HOME}/bin/java" ]]; then - JAVA="${JAVA_HOME}/bin/java" - NOW_JAVA_HOME="${JAVA_HOME}" -else - set +e - JAVA=$(which java) - NOW_JAVA_HOME="${JAVA}/../" - set -e -fi - -if [[ ! -x "$JAVA" ]]; then - echo "没有找到JAVA 文件,请配置【JAVA_HOME】环境变量" - exit 1 -fi - -# 启动程序 -function start() { - pid=$(getPid) - if [[ "$pid" != "" ]]; then - echo "程序正在运行中:${pid}" - exit 2 - fi - echo ${Log} - # 备份日志 - if [[ -f ${Log} ]]; then - if [[ ! -d ${LogBack} ]]; then - mkdir ${LogBack} - fi - cur_dateTime="$(date +%Y-%m-%d_%H:%M:%S).log" - mv ${Log} ${LogBack}${cur_dateTime} - echo "mv to $LogBack$cur_dateTime" - touch ${Log} - fi - # jar - if [[ -z "${RUNJAR}" ]]; then - RUNJAR=$(listDir ${Lib}) - echo "自动运行:${RUNJAR}" - fi - # error - if [[ -z "${RUNJAR}" ]]; then - echo "没有找到jar" - exit 2 - fi - - nohup ${JAVA} ${JVM} -jar ${Lib}${RUNJAR} -Dapplication=${Tag} -Dbasedir=${Path} ${ARGS} >>${Log} 2>&1 & - # 升级不执行查看日志 - if [[ ${upgrade} == "upgrade" ]]; then - exit 0 - fi - if [[ -f ${Log} ]]; then - tail -f ${Log} - else - sleep 3 - if [[ -f ${Log} ]]; then - tail -f ${Log} - else - echo "还没有生成日志文件:${Log}" - fi - fi -} - -# 找出第一个jar包 -function listDir() { - ALL="" - for file in "$1"/*.jar; do - if [[ -f "${file}" ]]; then - #得到文件的完整的目录 - ALL="${file}" - break - fi - done - echo ${ALL##*/} -} - -# 停止程序 -function stop() { - pid=$(getPid) - if [[ "$pid" != "" ]]; then - echo -n "boot ( pid $pid) is running" - echo - echo -n $"Shutting down boot: wait" - kill $(pgrep -f ${Tag}) 2>/dev/null - sleep 3 - pid=$(getPid) - if [[ "$pid" != "" ]]; then - echo "kill boot process" - kill -9 "$pid" - fi - else - echo "boot is stopped" - fi - - status -} - -# 获取程序状态 -function status() { - pid=$(getPid) - #echo "$pid" - if [[ "$pid" != "" ]]; then - echo "boot is running,pid is $pid" - else - echo "boot is stopped" - fi -} - -function getPid() { - pid=$(ps -ef | grep -v 'grep' | egrep ${Tag} | awk '{printf $2 " "}') - echo ${pid} -} - -# 提示使用语法 -function usage() { - echo "Usage: $0 {start|stop|restart|status|create}" - RETVAL="2" -} - -# 创建自启动服务文件 -function create() { - yum install -y wget && wget -O jpom-agent https://dromara.gitee.io/jpom/docs/jpom-service.sh - #判断当前脚本是否为绝对路径,匹配以/开头下的所有 - if [[ $0 =~ ^\/.* ]] - then - selfpath=$0 - else - selfpath=$(pwd)/$0 - fi - #获取文件的真实路径 - selfpath=`readlink -f $selfpath` - # 替换路径 - sed -i "s|JPOM_RUN_PATH|${selfpath}|g" jpom-agent - echo 'create jpom-agent file done' - mv -f jpom-agent /etc/init.d/jpom-agent - chmod +x /etc/init.d/jpom-agent - chkconfig --add jpom-agent - echo 'create jpom-agent success' -} - -# See how we were called. -RETVAL="0" -case "$1" in -start) - start - ;; -stop) - stop - ;; -restart) - stop - start - ;; -status) - status - ;; -create) - create - ;; -*) - usage - ;; -esac - -exit $RETVAL diff --git a/modules/agent/script/release.xml b/modules/agent/script/release.xml deleted file mode 100644 index d58a4dc8bc..0000000000 --- a/modules/agent/script/release.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - release - false - - dir - zip - - - - - - script/ - / - - Agent.sh - Agent.bat - - - - - src/main/resources/bin/ - / - - extConfig.yml - - - - - ../../ - / - - LICENSE - - - - - - - - lib - - io.jpom:agent - - - - - diff --git a/modules/agent/src/main/assembly/release.xml b/modules/agent/src/main/assembly/release.xml new file mode 100644 index 0000000000..950a30bdc9 --- /dev/null +++ b/modules/agent/src/main/assembly/release.xml @@ -0,0 +1,71 @@ + + + + release + false + + dir + zip + tar.gz + + + + + + ./src/main/bin + bin + + *.sh + + unix + + + ./src/main/bin + bin + + *.bat + + dos + + + + ./src/main/resources/config_default/ + /conf + + logback.xml + application.yml + + + + + ../../ + / + + LICENSE + + + + + + + + lib + + org.dromara.jpom:agent + + + + + diff --git a/modules/agent/src/main/bin/Agent.bat b/modules/agent/src/main/bin/Agent.bat new file mode 100644 index 0000000000..1a7ad522eb --- /dev/null +++ b/modules/agent/src/main/bin/Agent.bat @@ -0,0 +1,171 @@ +@REM +@REM Copyright (c) 2019 Of Him Code Technology Studio +@REM Jpom is licensed under Mulan PSL v2. +@REM You can use this software according to the terms and conditions of the Mulan PSL v2. +@REM You may obtain a copy of Mulan PSL v2 at: +@REM http://license.coscl.org.cn/MulanPSL2 +@REM THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +@REM See the Mulan PSL v2 for more details. +@REM + +@echo off +@if not "%ECHO%" == "" echo %ECHO% +setlocal enabledelayedexpansion +set ENV_PATH=.\ +if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% + +@REM Set environment variables to prevent some servers from failing to taskkill +set PATH = %PATH%;C:\Windows\system32;C:\Windows;C:\Windows\system32\Wbem + +if "%JAVA_HOME%"=="" ( + echo please configure [JAVA_HOME] environment variable + PAUSE + EXIT 2 +) + +set PID_TAG="JPOM_AGENT_APPLICATION" +set conf_dir="%ENV_PATH%/../conf/" +set tmpdir="%ENV_PATH%/../tmp/" +if not exist %tmpdir% md %tmpdir% + +@REM see org.springframework.util.StringUtils.cleanPath +@REM set org.springframework.boot.context.config.StandardConfigDataLocationResolver.getResourceLocation +cd %conf_dir% +set conf_dir=%cd% +cd %tmpdir% +set tmpdir=%cd% +cd %ENV_PATH% + +set log_dir=%ENV_PATH%\..\logs +set logback_configurationFile=%conf_dir%\logback.xml +set application_conf=%conf_dir%\application.yml + +set Lib=%ENV_PATH%\..\lib\ +set "RUN_JAR=" +set "JAR_MSG=" +set agent_log="%log_dir%\agent.log" +set stdout_log="%log_dir%\stdout.log" + +set JAVA_MEM_OPTS= -Xms200m -Xmx600m -XX:+UseG1GC +set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 -Djava.io.tmpdir="%tmpdir%" +set APP_OPTS= -Djpom.application.tag="%PID_TAG%" -Dlogging.config="%logback_configurationFile%" -Dspring.config.location="%application_conf%" +set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %APP_OPTS% + +set ARGS=%* +set JPOM_LOG=%log_dir% +if not exist %log_dir% md %log_dir% + +@REM get list jar +call:listDir + +if "%1"=="" ( + color 0a + TITLE Jpom management system BAT console + echo. ***** Jpom management system BAT console ***** + echo. !JAR_MSG! + ::************************************************************************************************************* + echo. + echo. [1] start + echo. [2] status + echo. [3] restart + echo. [4] stop + echo. [0] exit 0 + echo. + @REM enter + for /l %%i in (1,1,10000) do ( + echo. Please enter the selected serial number: + set /p ID= + IF "!ID!"=="1" call:start + IF "!ID!"=="2" call:status + IF "!ID!"=="3" call:restart + IF "!ID!"=="4" call:stop + IF "!ID!"=="0" EXIT + ) +)else ( + if "%1"=="restart" ( + call:restart + )else if "%1"=="start" ( + call:start + )else if "%1"=="status" ( + call:status + )else if "%1"=="stop" ( + call:stop + )else ( + call:use + ) +) +if "%2" == "upgrade" ( + @REM The upgrade ends directly + EXIT 0 +) + +:end +goto:eof + +@REM start +:start + echo Starting..... Closing the window after a successful start does not affect the operation + echo Please check for startup details:%agent_log% or !stdout_log! + start /b javaw %JAVA_OPTS% -jar %Lib%!RUN_JAR! %ARGS% > "!stdout_log!" 2>&1 + @REM timeout 3 > NUL + ping 127.0.0.1 -n 3 > nul +goto:eof + + +@REM get jar +:listDir + if "%RUN_JAR%"=="" ( + if exist "%Lib%\run.bin" ( + set /P RUN_JAR=<"%Lib%\run.bin" + set JAR_MSG=specify running !RUN_JAR! + )else ( + for /f "delims=" %%I in ('dir /B %Lib%') do ( + if exist %Lib%%%I if not exist %Lib%%%I\nul ( + if "%%~xI" ==".jar" ( + if "%RUN_JAR%"=="" ( + set "RUN_JAR=%%I" + ) + ) + ) + ) + set JAR_MSG=auto running !RUN_JAR! + ) + )else ( + set JAR_MSG=specify2 running %RUN_JAR% + ) + if not exist %Lib%!RUN_JAR! ( + echo %JAR_MSG% + echo file not exist %Lib%!RUN_JAR! + PAUSE + EXIT -1 + ) + @REM stdout_log + if exist "%Lib%\run.bin" ( + set /P RUN_LOG=<"%Lib%\run.log" + set stdout_log="%log_dir%\!RUN_LOG!" + ) +goto:eof + +@REM stop Jpom +:stop + echo "jpom agent stop " + for /f "tokens=1 delims= " %%I in ('jps -v ^| findstr "%PID_TAG%"') do taskkill /F /PID %%I +goto:eof + +@REM view Jpom status +:status + echo "jpom agent status " + set pid= + for /f "tokens=1 delims= " %%I in ('jps -v ^| findstr "%PID_TAG%"') do set pid=%%I + echo "running: %pid%" +goto:eof + +@REM restart Jpom +:restart + echo Stopping.... + call:stop + @REM timeout 3 > NUL + ping 127.0.0.1 -n 3 > nul + echo starting.... + call:start %1 +goto:eof diff --git a/modules/agent/src/main/bin/Agent.sh b/modules/agent/src/main/bin/Agent.sh new file mode 100644 index 0000000000..968177cb6c --- /dev/null +++ b/modules/agent/src/main/bin/Agent.sh @@ -0,0 +1,274 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# description: Auto-starts jpom agent + +case "$(uname)" in +Linux) + bin_abs_path=$(readlink -f "$(dirname "$0")") + ;; +*) + bin_abs_path=$( + cd "$(dirname "$0")" || exit + pwd + ) + ;; +esac + +command_exists() { + command -v "$@" >/dev/null 2>&1 +} + +function errorExit() { + echo "$1" 2>&2 + if [ "${mode}" == "-s" ]; then + logStdout "$1" + fi + exit 1 +} + +function logStdout() { + # out stdout + if [ ! -f "$Log" ]; then + touch "$Log" + fi + echo "$1" >"$Log" +} + +base=${bin_abs_path}/.. + +conf_path="${base}/conf" +Lib="${base}/lib/" +LogPath="${base}/logs/" +tmpdir="${base}/tmp/" +Log="${LogPath}/stdout.log" +logback_configurationFile="${conf_path}/logback.xml" +application_conf="${conf_path}/application.yml" +pidfile="$base/bin/agent.pid" + +export JPOM_LOG=${LogPath} + +PID_TAG="JPOM_AGENT_APPLICATION" +agent_log="${LogPath}/agent.log" + +## set java path +if [ -z "$JAVA" ]; then + JAVA=$(which java) +fi +if [ -z "$JAVA" ]; then + if command_exists java; then + JAVA="java" + fi +fi +if [ -z "$JAVA" ]; then + echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.8) in your PATH." 2>&2 + exit 1 +fi + +JavaVersion=$($JAVA -version 2>&1 | awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}') +Java64Str=$($JAVA -version 2>&1 | grep -E '64-bit|64-Bit') +JAVA_OPTS="$JAVA_OPTS -Xss512k -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LogPath" + +if [ "${JavaVersion}" -ge 11 ]; then + JAVA_OPTS="$JAVA_OPTS" +else + JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution" +fi + +#-Xms500m -Xmx1024m +if [[ -z "${USR_JVM_SIZE}" ]]; then + USR_JVM_SIZE="-Xms200m -Xmx600m" +fi + +if [ -n "$Java64Str" ]; then + # For G1 + JAVA_OPTS="-server ${USR_JVM_SIZE} -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS" +else + JAVA_OPTS="-server ${USR_JVM_SIZE} -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS" +fi +JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" +JAVA_OPTS="$JAVA_OPTS -Dlogging.config=$logback_configurationFile -Dspring.config.location=$application_conf" +JAVA_OPTS="$JAVA_OPTS -Djava.io.tmpdir=$tmpdir" + +if [[ -z "${RUN_ARG}" ]]; then + MAIN_ARGS="$*" +else + # docker run + MAIN_ARGS="${RUN_ARG}" +fi + +# mode -s -9 +mode="$2" + +RUN_JAR="" + +function checkConfig() { + if [ ! -d "$LogPath" ]; then + mkdir -p "$LogPath" + fi + if [[ ! -f "$logback_configurationFile" ]] || [[ ! -f "$application_conf" ]]; then + echo "Cannot find $application_conf or $logback_configurationFile" + exit 1 + fi + + if [[ -z "${RUN_JAR}" ]]; then + if [ -f "$Lib/run.bin" ]; then + RUN_JAR=$(cat "$Lib/run.bin") + if [ ! -f "$Lib/$RUN_JAR" ]; then + echo "Cannot find $Lib/$RUN_JAR jar" 2>&2 + exit 1 + fi + echo "specify running:${RUN_JAR}" + else + RUN_JAR=$(find "${Lib}" -type f -name "*.jar" -exec ls -t {} + | head -1 | sed 's#.*/##') + # error + if [[ -z "${RUN_JAR}" ]]; then + echo "Jar not found" + exit 2 + fi + echo "automatic running:${RUN_JAR}" + fi + fi + mkdir -p "$tmpdir" +} + +function getPid() { + cygwin=false + linux=false + case "$(uname)" in + CYGWIN*) + cygwin=true + ;; + Linux*) + linux=true + ;; + esac + if $cygwin; then + JAVA_CMD="$JAVA_HOME\bin\java" + JAVA_CMD=$(cygpath --path --unix "$JAVA_CMD") + JAVA_PID=$(ps | grep "$JAVA_CMD" | awk '{print $1}') + else + if $linux; then + JAVA_PID=$(ps -C java -f --width 1000 | grep "$PID_TAG" | grep -v grep | awk '{print $2}') + else + JAVA_PID=$(ps aux | grep "$PID_TAG" | grep -v grep | awk '{print $2}') + fi + fi + echo "$JAVA_PID" +} + +# See how we were called. +function start() { + echo $PID_TAG + # check running + pid=$(getPid) + #echo "$pid" + if [ "$pid" != "" ]; then + echo "Running, please do not run repeatedly:$pid" + exit 0 + fi + checkConfig + + if [ ! -f "$agent_log" ]; then + touch "$agent_log" + fi + # start + command="${JAVA} -Djpom.application.tag=${PID_TAG} ${JAVA_OPTS} -jar ${Lib}${RUN_JAR} ${MAIN_ARGS}" + echo "$command" >"$Log" + eval "nohup $command >>$Log 2>&1 &" + echo $! >"$pidfile" + + pid=$(cat "$pidfile") + + if [ "${mode}" == "-s" ] || [ "${mode}" == "upgrade" ]; then + echo "silence auto exit 0,${pid}" + exit 0 + fi + echo "Jpom agent starting:$pid" + pid=$(getPid) + if [ "$pid" == "" ]; then + echo "Please check the $Log for failure details" + errorExit "Jpom agent Startup failed" + fi + tail -fn 0 --pid="$pid" "$agent_log" +} + +function stop() { + pid=$(getPid) + killMode="" + if [ "${mode}" == "-s" ] || [ "${mode}" == "upgrade" ]; then + # Compatible with online upgrade ./Agent.sh restart upgrade or ./Agent.sh restart -s + killMode="" + else + killMode=${mode} + fi + if [ "$pid" != "" ]; then + echo -n "jpom agent ( pid $pid) is running" + echo + echo -n $"Shutting down (kill $killMode $pid) jpom server: " + if [ "$killMode" == "" ]; then + kill "$pid" + else + kill "$killMode" "$pid" + fi + LOOPS=0 + while (true); do + pid=$(getPid) + if [ "$pid" == "" ]; then + echo "Stop and end, in $LOOPS seconds" + break + fi + ((LOOPS++)) || true + sleep 1 + done + else + echo "jpom agent is stopped" + fi + eval "$(rm -f "$pidfile")" +} + +function status() { + pid=$(getPid) + #echo "$pid" + if [ "$pid" != "" ]; then + echo "jpom agent running:$pid" + else + echo "jpom agent is stopped" + fi +} + +function usage() { + echo "Usage: $0 {start|stop|restart|status}" 2>&2 + RETVAL="2" +} + +# See how we were called. +RETVAL="0" +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + stop + start + ;; +status) + status + ;; +*) + usage + ;; +esac + +exit $RETVAL diff --git a/modules/agent/src/main/bin/Service.sh b/modules/agent/src/main/bin/Service.sh new file mode 100644 index 0000000000..ad89ec4007 --- /dev/null +++ b/modules/agent/src/main/bin/Service.sh @@ -0,0 +1,185 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# description: manage jpom agent Service + +function absPath() { + dir="$1" + case "$(uname)" in + Linux) + abs_path=$(readlink -f "$dir") + ;; + *) + abs_path=$( + cd "$dir" || exit + pwd + ) + ;; + esac + # + echo "$abs_path" +} + +command_exists() { + command -v "$@" >/dev/null 2>&1 +} + +binAbsPath=$(absPath "$(dirname "$0")") +serviceName="jpom-agent.service" +serviceFile="/etc/systemd/system/$serviceName" +binAbsName=$(absPath "$binAbsPath/Agent.sh") +pidfile="$binAbsPath/agent.pid" + +# +user="$(id -un 2>/dev/null || true)" +user_group="$(id -gn 2>/dev/null || true)" + +sh_c='sh -c' +exec_user="" +if [ "$user" != 'root' ]; then + if command_exists sudo; then + sh_c='sudo -E sh -c' + elif command_exists su; then + sh_c='su -c' + else + cat >&2 <<-EOF + Error: this installer needs the ability to run commands as root. + We are unable to find either "sudo" or "su" available to make this happen. + EOF + exit 1 + fi + exec_user="$user" +fi + +function install() { + + if [ -f "$serviceFile" ]; then + echo "service file already exists" 2>&2 + exit 2 + fi + if [ ! -f "$binAbsName" ]; then + echo "$binAbsName not found" 2>&2 + exit 2 + fi + if [ -z "$JAVA_HOME" ]; then + echo "JAVA_HOME variable not found" 2>&2 + exit 2 + fi + if [ -z "$CLASSPATH" ]; then + echo "CLASSPATH variable not found" 2>&2 + exit 2 + fi + + $sh_c "cat >$serviceFile" <&2 <<-EOF + ERROR: $serviceName write failed Installing the service requires the ability to run commands as root. + EOF + exit 1 + fi + + echo "$serviceName write success :$serviceFile" + + $sh_c 'systemctl daemon-reload' + + cat >&2 <<-EOF + INFO: You can execute the following commands to manage $serviceName. + INFO: systemctl start $serviceName (Start the service ) + INFO: systemctl stop $serviceName (Stop the service) + INFO: systemctl enable $serviceName (Set up autostart) + INFO: systemctl disable $serviceName (stop autostart) + INFO: systemctl status $serviceName (View the current status of the service) + INFO: systemctl restart $serviceName (Restart the service) + EOF +} + +function uninstall() { + if [ -f "$serviceFile" ]; then + $sh_c "systemctl disable $serviceName" + $sh_c "systemctl stop $serviceName" + $sh_c "rm -f $serviceFile" + if [ -f "$serviceFile" ]; then + cat >&2 <<-EOF + ERROR: $serviceName write uninstall . + EOF + exit 1 + fi + echo "$serviceName uninstalled successfully" + $sh_c 'systemctl daemon-reload' + else + echo "$serviceFile not found" + fi +} + +function enable() { + if [ -f "$serviceFile" ]; then + $sh_c "systemctl enable $serviceName" + else + echo "$serviceFile not found" 2>&2 + echo "Usage: $0 install" 2>&2 + fi +} + +function action() { + case "$1" in + install) + install + ;; + uninstall) + uninstall + ;; + reinstall) + uninstall + echo "--------------------------------------" + install + ;; + enable) + enable + ;; + *) + echo "Usage: $0 {install|uninstall|reinstall|enable}" 2>&2 + exit 1 + ;; + esac + +} + +if [ -z "$1" ]; then + echo "Usage: $0 {install|uninstall|reinstall|enable}" 2>&2 + exit 1 +fi + +for i in "$@"; do + action "$i" +done diff --git a/modules/agent/src/main/java/io/jpom/JpomAgentApplication.java b/modules/agent/src/main/java/io/jpom/JpomAgentApplication.java index f20383c8d7..cc3c643aa8 100644 --- a/modules/agent/src/main/java/io/jpom/JpomAgentApplication.java +++ b/modules/agent/src/main/java/io/jpom/JpomAgentApplication.java @@ -1,64 +1,23 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package io.jpom; -import cn.hutool.core.date.BetweenFormatter; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.lang.Console; -import cn.jiangzeyin.common.EnableCommonBoot; -import io.jpom.common.Type; -import io.jpom.common.interceptor.AuthorizeInterceptor; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.ServletComponentScan; - /** - * jpom 启动类 + * 兼容低版本包检查是否为 jpom 对应类型的程序 * - * @author jiangzeyin - * @date 2017/9/14. + * @author bwcx_jzy + * @since 2023/3/31 */ -@SpringBootApplication -@ServletComponentScan -@EnableCommonBoot +@Deprecated public class JpomAgentApplication { - - /** - * 启动执行 - * - * @param args 参数 - * @throws Exception 异常 - */ - public static void main(String[] args) throws Exception { - long time = SystemClock.now(); - JpomApplication jpomApplication = new JpomApplication(Type.Agent, JpomAgentApplication.class, args); - jpomApplication - // 拦截器 - .addInterceptor(AuthorizeInterceptor.class) - // 添加 参数 url 解码 - // .addHandlerMethodArgumentResolver(UrlDecodeHandlerMethodArgumentResolver.class) - .run(args); - Console.log("本次启动耗时:{}", DateUtil.formatBetween(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND)); - } - + public static void main(String[] args) throws Exception { + org.dromara.jpom.JpomAgentApplication.main(args); + } } diff --git a/modules/agent/src/main/java/io/jpom/common/AgentExceptionHandler.java b/modules/agent/src/main/java/io/jpom/common/AgentExceptionHandler.java deleted file mode 100644 index 386ab62062..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/AgentExceptionHandler.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.exceptions.ValidateException; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.system.JpomRuntimeException; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 全局异常处理 - * - * @author jiangzeyin - * @date 2019/04/17 - */ -@ControllerAdvice -public class AgentExceptionHandler { - - /** - * 声明要捕获的异常 - * - * @param request 请求 - * @param response 响应 - * @param e 异常 - */ - @ExceptionHandler({JpomRuntimeException.class, RuntimeException.class, Exception.class}) - public void defExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { - DefaultSystemLog.getLog().error("controller " + request.getRequestURI(), e); - if (e instanceof JpomRuntimeException) { - ServletUtil.write(response, JsonMessage.getString(500, e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } else { - ServletUtil.write(response, JsonMessage.getString(500, "服务异常:" + e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } - } - - /** - * 声明要捕获的异常 (参数或者状态异常) - * - * @param request 请求 - * @param response 响应 - * @param e 异常 - */ - @ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class, ValidateException.class}) - public void paramExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { - DefaultSystemLog.getLog().error("controller " + request.getRequestURI(), e); - ServletUtil.write(response, JsonMessage.getString(405, e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/BaseAgentController.java b/modules/agent/src/main/java/io/jpom/common/BaseAgentController.java deleted file mode 100644 index 8e3306dda8..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/BaseAgentController.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.controller.base.AbstractController; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.manage.ProjectInfoService; -import io.jpom.system.ConfigBean; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import java.util.Objects; - -/** - * agent 端 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -public abstract class BaseAgentController extends BaseJpomController { - @Resource - protected ProjectInfoService projectInfoService; - - protected String getUserName() { - return getUserName(getRequest()); - } - - /** - * 获取server 端操作人 - * - * @param request req - * @return name - */ - private static String getUserName(HttpServletRequest request) { - String name = ServletUtil.getHeaderIgnoreCase(request, ConfigBean.JPOM_SERVER_USER_NAME); - name = CharsetUtil.convert(name, CharsetUtil.CHARSET_ISO_8859_1, CharsetUtil.CHARSET_UTF_8); - name = StrUtil.emptyToDefault(name, StrUtil.DASHED); - return URLUtil.decode(name, CharsetUtil.CHARSET_UTF_8); - } - - /** - * 获取server 端操作人 - * - * @return name - */ - public static String getNowUserName() { - ServletRequestAttributes servletRequestAttributes = AbstractController.tryGetRequestAttributes(); - if (servletRequestAttributes == null) { - return StrUtil.DASHED; - } - HttpServletRequest request = servletRequestAttributes.getRequest(); - return getUserName(request); - } - - protected String getWorkspaceId() { - return ServletUtil.getHeader(getRequest(), Const.WORKSPACEID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); - } - - /** - * 获取拦截器中缓存的项目信息 - * - * @return this - */ - protected NodeProjectInfoModel getProjectInfoModel() { - NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); - Objects.requireNonNull(nodeProjectInfoModel, "获取项目信息失败"); - return nodeProjectInfoModel; - } - - protected NodeProjectInfoModel tryGetProjectInfoModel() { - NodeProjectInfoModel nodeProjectInfoModel = null; - String id = getParameter("id"); - if (StrUtil.isNotEmpty(id)) { - nodeProjectInfoModel = projectInfoService.getItem(id); - } - return nodeProjectInfoModel; - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/UrlDecodeHandlerMethodArgumentResolver.java b/modules/agent/src/main/java/io/jpom/common/UrlDecodeHandlerMethodArgumentResolver.java deleted file mode 100644 index 165f501ca9..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/UrlDecodeHandlerMethodArgumentResolver.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.URLUtil; -import cn.jiangzeyin.common.interceptor.DefaultHandlerMethodArgumentResolver; -import io.jpom.model.BaseJsonModel; -import org.springframework.core.MethodParameter; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.ModelAndViewContainer; - -import java.lang.reflect.Field; - -/** - * 解析 参数 url 编码 - * - * @author bwcx_jzy - * @since 2021/10/25 - */ -public class UrlDecodeHandlerMethodArgumentResolver extends DefaultHandlerMethodArgumentResolver { - -// @Override -// public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { -// Object argument = super.resolveArgument(parameter, mavContainer, webRequest, binderFactory); -// if (argument instanceof String) { -// // 解码 -// return URLUtil.decode(argument.toString()); -// } else if (argument instanceof BaseJsonModel) { -// // 解码对象属性 -// Field[] fields = ReflectUtil.getFields(argument.getClass()); -// for (Field field : fields) { -// Class type = field.getType(); -// if (type == String.class) { -// String fieldValue = (String) ReflectUtil.getFieldValue(argument, field); -// fieldValue = URLUtil.decode(fieldValue); -// ReflectUtil.setFieldValue(argument, field, fieldValue); -// } -// } -// } -// return argument; -// } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/AbstractProjectCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/AbstractProjectCommander.java deleted file mode 100644 index e967661360..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/AbstractProjectCommander.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander; - -import cn.hutool.cache.impl.LRUCache; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.JarClassLoader; -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.http.HttpRequest; -import cn.hutool.http.HttpUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.commander.impl.LinuxProjectCommander; -import io.jpom.common.commander.impl.MacOSProjectCommander; -import io.jpom.common.commander.impl.WindowsProjectCommander; -import io.jpom.model.RunMode; -import io.jpom.model.data.JdkInfoModel; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.model.system.NetstatModel; -import io.jpom.service.manage.JdkInfoService; -import io.jpom.service.manage.ProjectInfoService; -import io.jpom.system.AgentExtConfigBean; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.CommandUtil; -import io.jpom.util.FileUtils; -import io.jpom.util.JvmUtil; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -/** - * 项目命令执行基类 - * - * @author Administrator - */ -public abstract class AbstractProjectCommander { - - public static final String RUNNING_TAG = "running"; - public static final String STOP_TAG = "stopped"; - - private static AbstractProjectCommander abstractProjectCommander = null; - - /** - * 进程id 对应Jpom 名称 - */ - public static final ConcurrentHashMap PID_JPOM_NAME = new ConcurrentHashMap<>(); - /** - * 进程Id 获取端口号 - */ - public static final LRUCache PID_PORT = new LRUCache<>(100, TimeUnit.MINUTES.toMillis(10)); - - /** - * 实例化Commander - * - * @return 命令执行对象 - */ - public static AbstractProjectCommander getInstance() { - if (abstractProjectCommander != null) { - return abstractProjectCommander; - } - if (SystemUtil.getOsInfo().isLinux()) { - // Linux系统 - abstractProjectCommander = new LinuxProjectCommander(); - } else if (SystemUtil.getOsInfo().isWindows()) { - // Windows系统 - abstractProjectCommander = new WindowsProjectCommander(); - } else if (SystemUtil.getOsInfo().isMac()) { - abstractProjectCommander = new MacOSProjectCommander(); - } else { - throw new JpomRuntimeException("不支持的:" + SystemUtil.getOsInfo().getName()); - } - return abstractProjectCommander; - } - - //---------------------------------------------------- 基本操作----start - - /** - * 生成可以执行的命令 - * - * @param nodeProjectInfoModel 项目 - * @param javaCopyItem 副本信息 - * @return null 是条件不足 - */ - public abstract String buildCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem); - - protected String getRunJavaPath(NodeProjectInfoModel nodeProjectInfoModel, boolean w) { - if (StrUtil.isEmpty(nodeProjectInfoModel.getJdkId())) { - return w ? "javaw" : "java"; - } - JdkInfoService bean = SpringUtil.getBean(JdkInfoService.class); - JdkInfoModel item = bean.getItem(nodeProjectInfoModel.getJdkId()); - if (item == null) { - return w ? "javaw" : "java"; - } - String jdkJavaPath = FileUtils.getJdkJavaPath(item.getPath(), w); - if (jdkJavaPath.contains(StrUtil.SPACE)) { - jdkJavaPath = String.format("\"%s\"", jdkJavaPath); - } - return jdkJavaPath; - } - - /** - * 启动 - * - * @param nodeProjectInfoModel 项目 - * @return 结果 - * @throws Exception 异常 - */ - public String start(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - String msg = checkStart(nodeProjectInfoModel, javaCopyItem); - if (msg != null) { - return msg; - } - String command = buildCommand(nodeProjectInfoModel, javaCopyItem); - if (command == null) { - return "没有需要执行的命令"; - } - // 执行命令 - ThreadUtil.execute(() -> { - try { - File file = FileUtil.file(nodeProjectInfoModel.allLib()); - if (SystemUtil.getOsInfo().isWindows()) { - CommandUtil.execSystemCommand(command, file); - } else { - CommandUtil.asyncExeLocalCommand(file, command); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行命令失败", e); - } - }); - // - loopCheckRun(nodeProjectInfoModel.getId(), true); - String status = status(nodeProjectInfoModel.getId()); - this.asyncWebHooks(nodeProjectInfoModel, javaCopyItem, "start", "result", status); - return status; - } - - /** - * 查询出指定端口信息 - * - * @param pid 进程id - * @param listening 是否只获取检查状态的 - * @return 数组 - */ - public abstract List listNetstat(int pid, boolean listening); - - /** - * 停止 - * - * @param nodeProjectInfoModel 项目 - * @return 结果 - * @throws Exception 异常 - */ - public String stop(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - String beforeStop = this.webHooks(nodeProjectInfoModel, javaCopyItem, "beforeStop"); - if (StrUtil.isNotEmpty(beforeStop)) { - return beforeStop; - } - // 再次查看进程信息 - String result = status(tag); - // - int pid = parsePid(result); - if (pid > 0) { - // 清空名称缓存 - PID_JPOM_NAME.remove(pid); - // 端口号缓存 - PID_PORT.remove(pid); - } - this.asyncWebHooks(nodeProjectInfoModel, javaCopyItem, "stop", "result", result); - return result; - } - - /** - * 执行 webhooks 通知 - * - * @param nodeProjectInfoModel 项目信息 - * @param javaCopyItem 副本信息 - * @param type 类型 - * @param other 其他参数 - */ - private void asyncWebHooks(NodeProjectInfoModel nodeProjectInfoModel, - NodeProjectInfoModel.JavaCopyItem javaCopyItem, - String type, Object... other) { - ThreadUtil.execute(() -> this.webHooks(nodeProjectInfoModel, javaCopyItem, type, other)); - } - - /** - * 执行 webhooks 通知 - * - * @param nodeProjectInfoModel 项目信息 - * @param javaCopyItem 副本信息 - * @param type 类型 - * @param other 其他参数 - * @return 结果 - */ - private String webHooks(NodeProjectInfoModel nodeProjectInfoModel, - NodeProjectInfoModel.JavaCopyItem javaCopyItem, - String type, Object... other) { - String token = nodeProjectInfoModel.getToken(); - if (StrUtil.isEmpty(token)) { - return StrUtil.EMPTY; - } - try { - HttpRequest httpRequest = HttpUtil.createGet(token); - httpRequest.form("projectId", nodeProjectInfoModel.getId()); - httpRequest.form("projectName", nodeProjectInfoModel.getName()); - httpRequest.form("type", type, other); - if (javaCopyItem != null) { - httpRequest.form("copyId", javaCopyItem.getId()); - } - String body = httpRequest.execute().body(); - DefaultSystemLog.getLog().info(nodeProjectInfoModel.getName() + ":" + body); - return body; - } catch (Exception e) { - DefaultSystemLog.getLog().error("WebHooks 调用错误", e); - return "WebHooks error:" + e.getMessage(); - } - } - - /** - * 重启 - * - * @param nodeProjectInfoModel 项目 - * @return 结果 - * @throws Exception 异常 - */ - public String restart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - this.asyncWebHooks(nodeProjectInfoModel, javaCopyItem, "beforeRestart"); - boolean run = this.isRun(nodeProjectInfoModel, javaCopyItem); - if (run) { - stop(nodeProjectInfoModel, javaCopyItem); - } - return start(nodeProjectInfoModel, javaCopyItem); - } - - /** - * 启动项目前基本检查 - * - * @param nodeProjectInfoModel 项目 - * @return null 检查一切正常 - * @throws Exception 异常 - */ - private String checkStart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - int pid = javaCopyItem == null ? getPid(nodeProjectInfoModel.getId()) : this.getPid(javaCopyItem.getTagId()); - if (pid > 0) { - return "当前程序正常运行中,不能重复启动,PID:" + pid; - } - String lib = nodeProjectInfoModel.allLib(); - File fileLib = new File(lib); - File[] files = fileLib.listFiles(); - if (files == null || files.length <= 0) { - return "没有jar包,请先到文件管理中上传程序的jar"; - } - // - if (nodeProjectInfoModel.getRunMode() == RunMode.ClassPath || nodeProjectInfoModel.getRunMode() == RunMode.JavaExtDirsCp) { - JarClassLoader jarClassLoader = JarClassLoader.load(fileLib); - // 判断主类 - try { - jarClassLoader.loadClass(nodeProjectInfoModel.getMainClass()); - } catch (ClassNotFoundException notFound) { - return "没有找到对应的MainClass:" + nodeProjectInfoModel.getMainClass(); - } - } else { - List fileList = NodeProjectInfoModel.listJars(nodeProjectInfoModel); - if (fileList.size() <= 0) { - return String.format("没有%s包,请先到文件管理中上传程序的%s", nodeProjectInfoModel.getRunMode().name(), nodeProjectInfoModel.getRunMode().name()); - } - File jarFile = fileList.get(0); - String checkJar = checkJar(jarFile); - if (checkJar != null) { - return checkJar; - } - } - // 备份日志 - backLog(nodeProjectInfoModel, javaCopyItem); - return null; - } - - private static String checkJar(File jarFile) { - try (JarFile jarFile1 = new JarFile(jarFile)) { - Manifest manifest = jarFile1.getManifest(); - Attributes attributes = manifest.getMainAttributes(); - String mainClass = attributes.getValue(Attributes.Name.MAIN_CLASS); - if (mainClass == null) { - return jarFile.getAbsolutePath() + "中没有找到对应的MainClass属性"; - } - JarClassLoader jarClassLoader = JarClassLoader.load(jarFile); - try { - jarClassLoader.loadClass(mainClass); - } catch (ClassNotFoundException notFound) { - return jarFile.getAbsolutePath() + "中没有找到对应的MainClass:" + mainClass; - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("解析jar", e); - return jarFile.getAbsolutePath() + " 解析错误:" + e.getMessage(); - } - return null; - } - - /** - * 清空日志信息 - * - * @param nodeProjectInfoModel 项目 - * @return 结果 - */ - public String backLog(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - File file = javaCopyItem == null ? new File(nodeProjectInfoModel.getLog()) : nodeProjectInfoModel.getLog(javaCopyItem); - if (!file.exists() || file.isDirectory()) { - return "not exists"; - } - // 文件内容太少不处理 - if (file.length() <= 1000) { - return "ok"; - } - if (AgentExtConfigBean.getInstance().openLogBack()) { - // 开启日志备份才移动文件 - File backPath = javaCopyItem == null ? nodeProjectInfoModel.getLogBack() : nodeProjectInfoModel.getLogBack(javaCopyItem); - backPath = new File(backPath, DateTime.now().toString(DatePattern.PURE_DATETIME_FORMAT) + ".log"); - FileUtil.copy(file, backPath, true); - } - // 清空日志 - String r = AbstractSystemCommander.getInstance().emptyLogFile(file); - if (StrUtil.isNotEmpty(r)) { - DefaultSystemLog.getLog().info(r); - } - return "ok"; - } - - public String status(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - return this.status(tag); - } - - /** - * 查看状态 - * - * @param tag 运行标识 - * @return 查询结果 - */ - protected String status(String tag) { - String jpsStatus = getJpsStatus(tag); - if (StrUtil.equals(AbstractProjectCommander.STOP_TAG, jpsStatus) && SystemUtil.getOsInfo().isLinux()) { - return getLinuxPsStatus(tag); - } - return jpsStatus; - } - - /** - * 尝试jps 中查看进程id - * - * @param tag 进程标识 - * @return 运行标识 - */ - private String getJpsStatus(String tag) { - Integer pid = JvmUtil.getPidByTag(tag); - if (pid == null || pid <= 0) { - return AbstractProjectCommander.STOP_TAG; - } - 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); - 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 - - /** - * 获取进程占用的主要端口 - * - * @param pid 进程id - * @return 端口 - */ - public String getMainPort(int pid) { - String cachePort = PID_PORT.get(pid); - if (cachePort != null) { - return cachePort; - } - List list = listNetstat(pid, true); - if (list == null) { - return StrUtil.DASHED; - } - List ports = new ArrayList<>(); - for (NetstatModel model : list) { - String local = model.getLocal(); - String portStr = getPortFormLocalIp(local); - if (portStr == null) { - continue; - } - // 取最小的端口号 - int minPort = Convert.toInt(portStr, Integer.MAX_VALUE); - if (minPort == Integer.MAX_VALUE) { - continue; - } - ports.add(minPort); - } - if (CollUtil.isEmpty(ports)) { - return StrUtil.DASHED; - } - String allPort = CollUtil.join(ports, ","); - // 缓存 - PID_PORT.put(pid, allPort); - return allPort; - } - - /** - * 判断ip 信息是否为本地ip - * - * @param local ip信息 - * @return true 是本地ip - */ - private String getPortFormLocalIp(String local) { - if (StrUtil.isEmpty(local)) { - return null; - } - List ipPort = StrSplitter.splitTrim(local, StrUtil.COLON, true); - if (ipPort.isEmpty()) { - return null; - } - if ("0.0.0.0".equals(ipPort.get(0)) || ipPort.size() == 1) { - // 0.0.0.0:8084 || :::18000 - return ipPort.get(ipPort.size() - 1); - } - return null; - } - - - /** - * 根据指定进程id获取Jpom 名称 - * - * @param pid 进程id - * @return false 不是来自Jpom - * @throws IOException 异常 - */ - public String getJpomNameByPid(int pid) throws IOException { - String name = PID_JPOM_NAME.get(pid); - if (name != null) { - return name; - } - DefaultSystemLog.getLog().debug("getJpomNameByPid pid: {}", pid); - ProjectInfoService projectInfoService = SpringUtil.getBean(ProjectInfoService.class); - List nodeProjectInfoModels = projectInfoService.list(); - if (nodeProjectInfoModels == null || nodeProjectInfoModels.isEmpty()) { - return StrUtil.DASHED; - } - String virtualMachine = JvmUtil.getVirtualMachineInfo(pid); - if (virtualMachine == null) { - return StrUtil.DASHED; - } - - for (NodeProjectInfoModel nodeProjectInfoModel : nodeProjectInfoModels) { - if (JvmUtil.checkCommandLineIsJpom(virtualMachine, nodeProjectInfoModel.getId())) { - name = nodeProjectInfoModel.getName(); - break; - } - } - - if (name != null) { - PID_JPOM_NAME.put(pid, name); - return name; - } - return StrUtil.DASHED; - } - - - /** - * 获取进程id - * - * @param tag 项目Id - * @return 未运行 返回 0 - * @throws Exception 异常 - */ - public int getPid(String tag) throws Exception { - String result = status(tag); - return parsePid(result); - } - - /** - * 转换pid - * - * @param result 查询信息 - * @return int - */ - public static int parsePid(String result) { - if (result.startsWith(AbstractProjectCommander.RUNNING_TAG)) { - String[] split = result.split(":"); - return Convert.toInt(ArrayUtil.get(split, 1), 0); - } - return 0; - } - - /** - * 是否正在运行 - * - * @param nodeProjectInfoModel 项目 - * @return true 正在运行 - */ - public boolean isRun(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - String result = this.status(tag); - return result.contains(AbstractProjectCommander.RUNNING_TAG); - } - - /** - * 是否正在运行 - * - * @param tag 运行标识 - * @return true 正在运行 - */ - private boolean isRun(String tag) { - String result = this.status(tag); - return result.contains(AbstractProjectCommander.RUNNING_TAG); - } - - /*** - * 阻塞检查程序状态 - * @param tag 程序tag - * @param status 要检查的状态 - * @throws Exception 异常 - * @return 和参数status相反 - */ - protected boolean loopCheckRun(String tag, boolean status) throws Exception { - int stopWaitTime = AgentExtConfigBean.getInstance().getStopWaitTime(); - stopWaitTime = Math.max(stopWaitTime, 1); - int loopCount = (int) (TimeUnit.SECONDS.toMillis(stopWaitTime) / 500); - int count = 0; - do { - if (isRun(tag) == status) { - return status; - } - ThreadUtil.sleep(500); - } while (count++ < loopCount); - return !status; - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/AbstractSystemCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/AbstractSystemCommander.java deleted file mode 100644 index 54b8d8af74..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/AbstractSystemCommander.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander; - -import cn.hutool.system.SystemUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.commander.impl.LinuxSystemCommander; -import io.jpom.common.commander.impl.MacOSSystemCommander; -import io.jpom.common.commander.impl.WindowsSystemCommander; -import io.jpom.model.system.ProcessModel; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.CommandUtil; - -import java.io.File; -import java.util.List; - -/** - * 系统监控命令 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public abstract class AbstractSystemCommander { - - 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; - } - - /** - * 获取整个服务器监控信息 - * - * @return data - */ - public abstract JSONObject getAllMonitor(); - - /** - * 获取当前服务器的所有进程列表 - * - * @return array - */ - public abstract List getProcessList(); - - /** - * 获取指定进程的 内存信息 - * - * @param pid 进程id - * @return json - */ - public abstract ProcessModel getPidInfo(int pid); - - /** - * 清空文件内容 - * - * @param file 文件 - * @return 执行结果 - */ - public abstract String emptyLogFile(File file); - - /** - * 磁盘占用 - * - * @return 磁盘占用 - */ - protected static String getHardDisk() { - File[] files = File.listRoots(); - double totalSpace = 0; - double useAbleSpace = 0; - for (File file : files) { - double total = file.getTotalSpace(); - totalSpace += total; - useAbleSpace += total - file.getUsableSpace(); - } - return totalSpace <= 0 ? "0" : String.format("%.2f", useAbleSpace / totalSpace * 100); - } - - /** - * 查询服务状态 - * - * @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 命令 - * - * @param pid 进程编号 - * @return 结束进程命令 - */ - public abstract String buildKill(int pid); - - /** - * kill - * - * @param pid 进程编号 - */ - public String kill(File file, int pid) { - String kill = buildKill(pid); - return CommandUtil.execSystemCommand(kill, file); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/AbstractTomcatCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/AbstractTomcatCommander.java deleted file mode 100644 index c29b4f1cee..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/AbstractTomcatCommander.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.common.commander.impl.LinuxTomcatCommander; -import io.jpom.common.commander.impl.WindowsTomcatCommander; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.system.JpomRuntimeException; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * tomcat命令执行工具类 - * - * @author LF - */ -public abstract class AbstractTomcatCommander { - - private static AbstractTomcatCommander abstractTomcatCommander; - - public static AbstractTomcatCommander getInstance() { - if (abstractTomcatCommander != null) { - return abstractTomcatCommander; - } - if (SystemUtil.getOsInfo().isLinux()) { - // Linux系统 - abstractTomcatCommander = new LinuxTomcatCommander(); - } else if (SystemUtil.getOsInfo().isWindows()) { - // Windows系统 - abstractTomcatCommander = new WindowsTomcatCommander(); - } else if (SystemUtil.getOsInfo().isMac()) { - abstractTomcatCommander = new LinuxTomcatCommander(); - } else { - throw new JpomRuntimeException("不支持的:" + SystemUtil.getOsInfo().getName()); - } - return abstractTomcatCommander; - } - - /** - * 执行tomcat命令 - * - * @param tomcatInfoModel tomcat信息 - * @param cmd 执行的命令,包括start stop - * @return 返回tomcat启动结果 - */ - public abstract String execCmd(TomcatInfoModel tomcatInfoModel, String cmd); - - /** - * 检查tomcat状态 - * - * @param tomcatInfoModel tomcat信息 - * @param cmd 操作命令 - * @return 状态结果 - */ - protected String getStatus(TomcatInfoModel tomcatInfoModel, String cmd) { - String strReturn = "start".equals(cmd) ? "stopped" : "started"; - int i = 0; - while (i < 10) { - int result = 0; - String url = String.format("http://127.0.0.1:%d/", tomcatInfoModel.getPort()); - HttpRequest httpRequest = new HttpRequest(url); - // 设置超时时间为3秒 - httpRequest.setConnectionTimeout(3000); - try { - httpRequest.execute(); - result = 1; - } catch (Exception ignored) { - } - - i++; - if ("start".equals(cmd) && result == 1) { - strReturn = "started"; - break; - } - if ("stop".equals(cmd) && result == 0) { - strReturn = "stopped"; - break; - } - ThreadUtil.sleep(1000); - } - return strReturn; - } - - protected void exec(String command, boolean close) { - DefaultSystemLog.getLog().info(command); - try { - // 执行命令 - Process process = Runtime.getRuntime().exec(command); - process.getInputStream().close(); - process.getErrorStream().close(); - process.getOutputStream().close(); - process.waitFor(5, TimeUnit.SECONDS); - if (close) { - process.destroy(); - } - } catch (IOException | InterruptedException e) { - DefaultSystemLog.getLog().error("tomcat执行名称失败", e); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxProjectCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxProjectCommander.java deleted file mode 100644 index f710b6b90a..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxProjectCommander.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.model.system.NetstatModel; -import io.jpom.util.CommandUtil; -import io.jpom.util.JvmUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * linux - * - * @author Administrator - */ -public class LinuxProjectCommander extends AbstractProjectCommander { - - @Override - public String buildCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - String path = NodeProjectInfoModel.getClassPathLib(nodeProjectInfoModel); - if (StrUtil.isBlank(path)) { - return null; - } - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - return String.format("nohup %s %s %s" + - " %s %s %s >> %s 2>&1 &", - getRunJavaPath(nodeProjectInfoModel, false), - javaCopyItem == null ? nodeProjectInfoModel.getJvm() : javaCopyItem.getJvm(), - JvmUtil.getJpomPidTag(tag, nodeProjectInfoModel.allLib()), - path, - nodeProjectInfoModel.getMainClass(), - javaCopyItem == null ? nodeProjectInfoModel.getArgs() : javaCopyItem.getArgs(), - nodeProjectInfoModel.getAbsoluteLog(javaCopyItem)); - } - - @Override - public String stop(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - String result = super.stop(nodeProjectInfoModel, javaCopyItem); - int pid = parsePid(result); - if (pid > 0) { - String kill = AbstractSystemCommander.getInstance().kill(FileUtil.file(nodeProjectInfoModel.allLib()), pid); - if (loopCheckRun(nodeProjectInfoModel.getId(), false)) { - // 强制杀进程 - String cmd = String.format("kill -9 %s", pid); - CommandUtil.asyncExeLocalCommand(FileUtil.file(nodeProjectInfoModel.allLib()), cmd); - } - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - result = status(tag) + StrUtil.SPACE + kill; - } - return result; - } - - @Override - public List listNetstat(int pId, boolean listening) { - String cmd; - if (listening) { - cmd = "netstat -antup | grep " + pId + " |grep \"LISTEN\" | head -20"; - } else { - cmd = "netstat -antup | grep " + pId + " |grep -v \"CLOSE_WAIT\" | head -20"; - } - String result = CommandUtil.execSystemCommand(cmd); - List netList = StrSplitter.splitTrim(result, "\n", true); - if (netList == null || netList.size() <= 0) { - return null; - } - List array = new ArrayList<>(); - for (String str : netList) { - List list = StrSplitter.splitTrim(str, " ", true); - if (list.size() < 5) { - continue; - } - NetstatModel netstatModel = new NetstatModel(); - netstatModel.setProtocol(list.get(0)); - netstatModel.setReceive(list.get(1)); - netstatModel.setSend(list.get(2)); - netstatModel.setLocal(list.get(3)); - netstatModel.setForeign(list.get(4)); - if ("tcp".equalsIgnoreCase(netstatModel.getProtocol())) { - netstatModel.setStatus(CollUtil.get(list, 5)); - netstatModel.setName(CollUtil.get(list, 6)); - } else { - netstatModel.setStatus(StrUtil.DASHED); - netstatModel.setName(CollUtil.get(list, 5)); - } - array.add(netstatModel); - } - return array; - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java deleted file mode 100644 index 0ddca8c76f..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.system.ProcessModel; -import io.jpom.util.CommandUtil; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * @author jiangzeyin - * @date 2019/4/16 - */ -public class LinuxSystemCommander extends AbstractSystemCommander { - - @Override - public JSONObject getAllMonitor() { - String result = CommandUtil.execSystemCommand("top -i -b -n 1"); - if (StrUtil.isEmpty(result)) { - return null; - } - String[] split = result.split(StrUtil.LF); - int length = split.length; - JSONObject jsonObject = new JSONObject(); - if (length >= 2) { - String cpus = split[2]; - //cpu占比 - String cpu = getLinuxCpu(cpus); - jsonObject.put("cpu", cpu); - } - if (length >= 3) { - String mem = split[3]; - //内存占比 - String[] memory = getLinuxMemory(mem); - jsonObject.put("memory", ArrayUtil.get(memory, 0)); - // @author jzy - jsonObject.put("memoryUsed", ArrayUtil.get(memory, 1)); - } - jsonObject.put("disk", getHardDisk()); - return jsonObject; - } - - @Override - public List getProcessList() { - String s = CommandUtil.execSystemCommand("top -b -n 1 | grep java"); - return formatLinuxTop(s, false); - } - - - @Override - public ProcessModel getPidInfo(int pid) { - String command = "top -b -n 1 -p " + pid; - String internal = CommandUtil.execSystemCommand(command); - List processModels = formatLinuxTop(internal, true); - if (processModels == null || processModels.isEmpty()) { - return null; - } - return processModels.get(0); - } - - - @Override - public String emptyLogFile(File file) { - return CommandUtil.execSystemCommand("cp /dev/null " + file.getAbsolutePath()); - } - - /** - * 将linux的top信息转为集合 - * - * @param top top - */ - private static List formatLinuxTop(String top, boolean header) { - List list = StrSplitter.splitTrim(top, StrUtil.LF, true); - if (list.size() <= 0) { - return null; - } - List list1 = new ArrayList<>(); - ProcessModel processModel; - for (int i = header ? 6 : 0, len = list.size(); i < len; i++) { - processModel = new ProcessModel(); - String item = list.get(i); - List values = StrSplitter.splitTrim(item, StrUtil.SPACE, true); - processModel.setPid(Integer.parseInt(values.get(0))); - processModel.setUser(values.get(1)); - processModel.setPr(values.get(2)); - processModel.setNi(values.get(3)); - // - processModel.setVirt(formSize(values.get(4))); - processModel.setRes(formSize(values.get(5))); - processModel.setShr(formSize(values.get(6))); - // - processModel.setStatus(formStatus(values.get(7))); - // - processModel.setCpu(values.get(8) + "%"); - processModel.setMem(values.get(9) + "%"); - // - processModel.setTime(values.get(10)); - processModel.setCommand(values.get(11)); - list1.add(processModel); - } - return list1; - } - - - private static String formStatus(String val) { - String value = "未知"; - if ("S".equalsIgnoreCase(val)) { - value = "睡眠"; - } else if ("R".equalsIgnoreCase(val)) { - value = "运行"; - } else if ("T".equalsIgnoreCase(val)) { - value = "跟踪/停止"; - } else if ("Z".equalsIgnoreCase(val)) { - value = "僵尸进程 "; - } else if ("D".equalsIgnoreCase(val)) { - value = "不可中断的睡眠状态 "; - } else if ("i".equalsIgnoreCase(val)) { - value = "多线程 "; - } - return value; - } - - private static String formSize(String val) { - if (StrUtil.endWithIgnoreCase(val, "g")) { - String newVal = val.substring(0, val.length() - 1); - return String.format("%.2f MB", Convert.toDouble(newVal, 0D) * 1024); - } - if (StrUtil.endWithIgnoreCase(val, "m")) { - String newVal = val.substring(0, val.length() - 1); - return Convert.toLong(newVal, 0L) / 1024 + " MB"; - } - return Convert.toLong(val, 0L) / 1024 + " MB"; - } - - /** - * 获取内存信息 - * - * @param info 内存信息 - * @return 内存信息 - */ - private static String[] getLinuxMemory(String info) { - if (StrUtil.isEmpty(info)) { - return null; - } - int index = info.indexOf(":") + 1; - String[] split = info.substring(index).split(","); -// 509248k total — 物理内存总量(509M) -// 495964k used — 使用中的内存总量(495M) -// 13284k free — 空闲内存总量(13M) -// 25364k buffers — 缓存的内存量 (25M) - double total = 0, free = 0, used = 0; - for (String str : split) { - str = str.trim(); - if (str.endsWith("free")) { - // 减去了 buff - String value = str.replace("free", "").replace("k", "").trim(); - free = Convert.toDouble(value, 0.0); - } - if (str.endsWith("total")) { - String value = str.replace("total", "").replace("k", "").trim(); - total = Convert.toDouble(value, 0.0); - } - if (str.endsWith("used")) { - // 计算出时间使用 - String value = str.replace("used", "").replace("k", "").trim(); - used = Convert.toDouble(value, 0.0); - } - } - return new String[]{String.format("%.2f", (total - free) / total * 100), String.format("%.2f", (used) / total * 100)}; - } - - /** - * 获取占用cpu信息 - * - * @param info cpu信息 - * @return cpu信息 - */ - private static String getLinuxCpu(String info) { - if (StrUtil.isEmpty(info)) { - return null; - } - int i = info.indexOf(":"); - String[] split = info.substring(i + 1).split(","); -// 1.3% us — 用户空间占用CPU的百分比。 -// 1.0% sy — 内核空间占用CPU的百分比。 -// 0.0% ni — 改变过优先级的进程占用CPU的百分比 -// 97.3% id — 空闲CPU百分比 -// 0.0% wa — IO等待占用CPU的百分比 -// 0.3% hi — 硬中断(Hardware IRQ)占用CPU的百分比 -// 0.0% si — 软中断(Software Interrupts)占用CPU的百分比 - for (String str : split) { - str = str.trim(); - String value = str.substring(0, str.length() - 2).trim(); - String tag = str.substring(str.length() - 2); - if ("id".equalsIgnoreCase(tag)) { - value = value.replace("%", ""); - double val = Convert.toDouble(value, 0.0); - return String.format("%.2f", 100.00 - val); - } - } - return "0"; - } - - @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(new File(SystemUtil.getUserInfo().getHomeDir()), serviceName); - return "ok"; - } catch (Exception e) { - DefaultSystemLog.getLog().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) { - return String.format("kill %s", pid); - } - - private String getPs(String serviceName) { - String ps = StrUtil.format("ps -ef | grep -v 'grep' | egrep {}", serviceName); - return CommandUtil.execSystemCommand(ps); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxTomcatCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxTomcatCommander.java deleted file mode 100644 index bef7f1d6d4..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxTomcatCommander.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.commander.AbstractTomcatCommander; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.util.CommandUtil; - -import java.util.List; - -/** - * tomcat的linux管理命令 - * - * @author LF - */ -public class LinuxTomcatCommander extends AbstractTomcatCommander { - - @Override - public String execCmd(TomcatInfoModel tomcatInfoModel, String cmd) { - String tomcatPath = tomcatInfoModel.pathAndCheck(); - if (StrUtil.isBlank(tomcatPath)) { - return "tomcat path blank"; - } - String command = null; - if ("stop".equals(cmd)) { - String setPidCmd = CommandUtil.execSystemCommand("jps -mv"); - List list = StrSplitter.splitTrim(setPidCmd, StrUtil.LF, true); - for (String item : list) { - //路径格式转换 - String msg = FileUtil.normalize(item + StrUtil.SLASH); - //判断集合中元素是否包含指定Tomcat路径 - boolean w = msg.contains(tomcatInfoModel.getPath()); - if (w) { - //截取TomcatPid - if (msg.indexOf(" ") > 1) { - String tmPid = msg.substring(0, msg.indexOf(" ")); - //判断截取的PID是否为纯数字 - if (NumberUtil.isInteger(tmPid)) { - command = String.format("kill -9 %s", tmPid); - exec(command, false); - } - } - } - } - } else { - command = String.format("/bin/sh -c %s/bin/startup.sh", tomcatPath); - // - exec(command, false); - } - if (StrUtil.isBlank(tomcatPath)) { - return "tomcat path blank"; - } - // 查询操作结果并返回 - return getStatus(tomcatInfoModel, cmd); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/MacOSProjectCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/MacOSProjectCommander.java deleted file mode 100644 index 26b7bec999..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/MacOSProjectCommander.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.model.system.NetstatModel; -import io.jpom.util.CommandUtil; -import io.jpom.util.JvmUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * MacOSProjectCommander - * - * @author Hotstrip - * @Description some commands cannot execute success on Mac OS - */ -public class MacOSProjectCommander extends AbstractProjectCommander { - - @Override - public String buildCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - String path = NodeProjectInfoModel.getClassPathLib(nodeProjectInfoModel); - if (StrUtil.isBlank(path)) { - return null; - } - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - return String.format("nohup %s %s %s" + - " %s %s %s >> %s 2>&1 &", - getRunJavaPath(nodeProjectInfoModel, false), - javaCopyItem == null ? nodeProjectInfoModel.getJvm() : javaCopyItem.getJvm(), - JvmUtil.getJpomPidTag(tag, nodeProjectInfoModel.allLib()), - path, - nodeProjectInfoModel.getMainClass(), - javaCopyItem == null ? nodeProjectInfoModel.getArgs() : javaCopyItem.getArgs(), - nodeProjectInfoModel.getAbsoluteLog(javaCopyItem)); - } - - @Override - public List listNetstat(int pId, boolean listening) { - String cmd; - if (listening) { - cmd = "lsof -n -P -iTCP -sTCP:LISTEN |grep " + pId + " | head -20"; - } else { - cmd = "lsof -n -P -iTCP -sTCP:CLOSE_WAIT |grep " + pId + " | head -20"; - } - String result = CommandUtil.execSystemCommand(cmd); - //DefaultSystemLog.getLog().debug("Mac OS lsof data: {}", result); - List netList = StrSplitter.splitTrim(result, "\n", true); - if (netList == null || netList.size() <= 0) { - return null; - } - List array = new ArrayList<>(); - for (String str : netList) { - List list = StrSplitter.splitTrim(str, " ", true); - if (list.size() < 5) { - continue; - } - NetstatModel netstatModel = new NetstatModel(); - netstatModel.setProtocol(list.get(0)); - netstatModel.setReceive(list.get(1)); - netstatModel.setSend(list.get(2)); - netstatModel.setLocal(list.get(3)); - netstatModel.setForeign(list.get(4)); - if ("tcp".equalsIgnoreCase(netstatModel.getProtocol())) { - netstatModel.setStatus(CollUtil.get(list, 5)); - netstatModel.setName(CollUtil.get(list, 6)); - } else { - netstatModel.setStatus(StrUtil.DASHED); - netstatModel.setName(CollUtil.get(list, 5)); - } - array.add(netstatModel); - } - return array; - } - - @Override - public String stop(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - String result = super.stop(nodeProjectInfoModel, javaCopyItem); - int pid = parsePid(result); - if (pid > 0) { - String kill = AbstractSystemCommander.getInstance().kill(FileUtil.file(nodeProjectInfoModel.allLib()), pid); - if (loopCheckRun(nodeProjectInfoModel.getId(), false)) { - // 强制杀进程 - String cmd = String.format("kill -9 %s", pid); - CommandUtil.asyncExeLocalCommand(FileUtil.file(nodeProjectInfoModel.allLib()), cmd); - } - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - result = status(tag) + StrUtil.SPACE + kill; - } - return result; - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/MacOSSystemCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/MacOSSystemCommander.java deleted file mode 100644 index 9615e32726..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/MacOSSystemCommander.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.system.ProcessModel; -import io.jpom.util.CommandUtil; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * @author User - */ -public class MacOSSystemCommander extends AbstractSystemCommander { - - @Override - public JSONObject getAllMonitor() { - String result = CommandUtil.execSystemCommand("top -l 1 | head"); - if (StrUtil.isEmpty(result)) { - return null; - } - String[] split = result.split(StrUtil.LF); - int length = split.length; - JSONObject jsonObject = new JSONObject(); - // cpu 在第 4 行,下标为 3 - if (length > 3) { - String cpus = split[3]; - // cpu占比 - String cpu = getLinuxCpu(cpus); - jsonObject.put("cpu", cpu); - } - // 内存占比 第 7 行 下标为 6 - if (length > 6) { - String mem = split[6]; - //内存占比 - String memory = getLinuxMemory(mem); - jsonObject.put("memory", memory); - } - jsonObject.put("disk", getHardDisk()); - DefaultSystemLog.getLog().info("Mac OS monitor data: {}", jsonObject.toJSONString()); - return jsonObject; - } - - /** - * 返回内存占比信息 - * 这里返回的数据跟 Mac OS 自带活动监视器显示的内存压力不是同一种东西 - * 内存使用占比高,不代表内存压力就大 - * - * @param info - * @return 已使用的 / 总内存 * 100% - */ - private String getLinuxMemory(final String info) { - if (StrUtil.isEmpty(info)) { - return null; - } - double used = 0, free = 0; - DefaultSystemLog.getLog().debug("Mac Os mem info: {}", info); - int index = info.indexOf(":") + 1; - String[] split = info.substring(index).split(","); - for (String str : split) { - str = str.trim(); - if (str.contains("unused.")) { - String value = str.split("\\s+")[0].replace("M", ""); - free = Convert.toDouble(value, 0.0); - } else if (str.contains("used")) { - String value = str.split("\\s+")[0].replace("M", ""); - used = Convert.toDouble(value, 0.0); - } - } - DefaultSystemLog.getLog().debug("Mac OS mem: used: {}, unused: {}", used, free); - return String.format("%.2f", used / (used + free) * 100); - } - - /** - * 返回 Mac OS cpu 占用信息 - * - * @param info - * @return 100 - idle (100 - 空闲的 cpu) - */ - private String getLinuxCpu(final String info) { - if (StrUtil.isEmpty(info)) { - return null; - } - DefaultSystemLog.getLog().debug("Mac Os cpu info: {}", info); - int i = info.indexOf(":"); - String[] split = info.substring(i + 1).split(","); - for (String str : split) { - str = str.trim(); - if (str.contains("idle")) { - String value = str.split("\\s+")[0].replace("%", ""); - double val = Convert.toDouble(value, 0.0); - return String.format("%.2f", 100.00 - val); - } - } - return "0"; - } - - @Override - public List getProcessList() { - String s = CommandUtil.execSystemCommand("top -l 1 | grep java"); - return formatLinuxTop(s, false); - } - - @Override - public String emptyLogFile(File file) { - return CommandUtil.execSystemCommand("cp /dev/null " + file.getAbsolutePath()); - } - - /** - * 把 top 返回的数据组装成集合 - * - * @param top - * @param header 是否有 header - * @return - */ - private List formatLinuxTop(final String top, final boolean header) { - List list = StrSplitter.splitTrim(top, StrUtil.LF, true); - if (list.size() <= 0) { - return null; - } - List list1 = new ArrayList<>(); - ProcessModel processModel; - for (String item : list) { - processModel = new ProcessModel(); - DefaultSystemLog.getLog().debug("process item: {}", item); - List values = StrSplitter.splitTrim(item, StrUtil.SPACE, true); - DefaultSystemLog.getLog().debug(JSON.toJSONString(values)); - processModel.setPid(Convert.toInt(values.get(0),0)); - processModel.setPort(values.get(6)); - processModel.setCommand(values.get(1)); - processModel.setCpu(values.get(2) + "%"); - processModel.setMem(values.get(14) + "%"); - processModel.setStatus(formStatus(values.get(12))); - processModel.setTime(values.get(3)); - processModel.setRes(values.get(7)); - processModel.setUser(values.get(29)); - list1.add(processModel); - } - return list1; - } - - private String formStatus(final String val) { - String tempVal = val.toUpperCase(); - String value = "未知"; - if (tempVal.startsWith("S")) { - value = "睡眠"; - } else if (tempVal.startsWith("R")) { - value = "运行"; - } else if (tempVal.startsWith("T")) { - value = "跟踪/停止"; - } else if (tempVal.startsWith("Z")) { - value = "僵尸进程 "; - } else if (tempVal.startsWith("D")) { - value = "不可中断的睡眠状态 "; - } else if (tempVal.startsWith("I")) { - value = "多线程 "; - } - return value; - } - - @Override - public ProcessModel getPidInfo(int pid) { - String command = "top -l 1 | grep " + pid; - String internal = CommandUtil.execSystemCommand(command); - List processModels = formatLinuxTop(internal, true); - if (processModels == null || processModels.isEmpty()) { - return null; - } - return processModels.get(0); - } - - @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(new File(SystemUtil.getUserInfo().getHomeDir()), serviceName); - return "ok"; - } catch (Exception e) { - DefaultSystemLog.getLog().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) { - return String.format("kill %s", pid); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsProjectCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsProjectCommander.java deleted file mode 100644 index e0d253764d..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsProjectCommander.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.model.system.NetstatModel; -import io.jpom.util.CommandUtil; -import io.jpom.util.JvmUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * windows 版 - * - * @author Administrator - */ -public class WindowsProjectCommander extends AbstractProjectCommander { - - @Override - public String buildCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - String classPath = NodeProjectInfoModel.getClassPathLib(nodeProjectInfoModel); - if (StrUtil.isBlank(classPath)) { - return null; - } - // 拼接命令 - String jvm = javaCopyItem == null ? nodeProjectInfoModel.getJvm() : javaCopyItem.getJvm(); - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - String mainClass = nodeProjectInfoModel.getMainClass(); - String args = javaCopyItem == null ? nodeProjectInfoModel.getArgs() : javaCopyItem.getArgs(); - return String.format("%s %s %s " + - "%s %s %s >> %s &", - getRunJavaPath(nodeProjectInfoModel, true), - jvm, JvmUtil.getJpomPidTag(tag, nodeProjectInfoModel.allLib()), - classPath, mainClass, args, nodeProjectInfoModel.getAbsoluteLog(javaCopyItem)); - } - - @Override - public String stop(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) throws Exception { - String result = super.stop(nodeProjectInfoModel, javaCopyItem); - String tag = javaCopyItem == null ? nodeProjectInfoModel.getId() : javaCopyItem.getTagId(); - // 查询状态,如果正在运行,则执行杀进程命令 - int pid = parsePid(result); - if (pid > 0) { - String kill = AbstractSystemCommander.getInstance().kill(FileUtil.file(nodeProjectInfoModel.allLib()), pid); - loopCheckRun(nodeProjectInfoModel.getId(), false); - result = status(tag) + StrUtil.SPACE + kill; - } - return result; - } - - @Override - public List listNetstat(int pId, boolean listening) { - String cmd; - if (listening) { - cmd = "netstat -nao -p tcp | findstr \"LISTENING\" | findstr " + pId; - } else { - cmd = "netstat -nao -p tcp | findstr /V \"CLOSE_WAIT\" | findstr " + pId; - } - String result = CommandUtil.execSystemCommand(cmd); - List netList = StrSplitter.splitTrim(result, StrUtil.LF, true); - if (netList == null || netList.size() <= 0) { - return null; - } - List array = new ArrayList<>(); - for (String str : netList) { - List list = StrSplitter.splitTrim(str, " ", true); - if (list.size() < 5) { - continue; - } - NetstatModel netstatModel = new NetstatModel(); - netstatModel.setProtocol(list.get(0)); - netstatModel.setLocal(list.get(1)); - netstatModel.setForeign(list.get(2)); - netstatModel.setStatus(list.get(3)); - netstatModel.setName(list.get(4)); - array.add(netstatModel); - } - return array; - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsSystemCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsSystemCommander.java deleted file mode 100644 index 60e7a699f2..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsSystemCommander.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.StrUtil; -import cn.hutool.cron.CronUtil; -import cn.hutool.cron.Scheduler; -import cn.hutool.cron.task.Task; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONObject; -import com.sun.management.OperatingSystemMXBean; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.system.ProcessModel; -import io.jpom.util.CommandUtil; - -import java.io.File; -import java.lang.management.ManagementFactory; -import java.util.ArrayList; -import java.util.List; - -/** - * windows 系统查询命令 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public class WindowsSystemCommander extends AbstractSystemCommander { - - private static String lastResult; - - private static final String ID = "windows_system_process_list"; - - /** - * 获取windows 监控 - * https://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html - * - * @return 返回cpu占比和内存占比 - */ - @Override - public JSONObject getAllMonitor() { - OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); - JSONObject jsonObject = new JSONObject(); - double total = operatingSystemMXBean.getTotalPhysicalMemorySize(); - double free = operatingSystemMXBean.getFreePhysicalMemorySize(); - jsonObject.put("memory", String.format("%.2f", (total - free) / total * 100)); - //最近系统cpu使用量 - double systemCpuLoad = operatingSystemMXBean.getSystemCpuLoad(); - if (systemCpuLoad <= 0) { - systemCpuLoad = 0; - } - jsonObject.put("cpu", String.format("%.2f", systemCpuLoad * 100)); - jsonObject.put("disk", getHardDisk()); - return jsonObject; - } - - @Override - public List getProcessList() { - Scheduler scheduler = CronUtil.getScheduler(); - Task task = scheduler.getTask(ID); - if (task == null) { - CronUtil.schedule(ID, "0 0/1 * * * ?", () -> { - try { - lastResult = CommandUtil.execSystemCommand("tasklist /V | findstr java"); - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行控制台进程统计错误", e); - } - }); - } - if (lastResult == null) { - // 返回上一次结果 - lastResult = CommandUtil.execSystemCommand("tasklist /V | findstr java"); - } - return formatWindowsProcess(lastResult, false); - } - - @Override - public ProcessModel getPidInfo(int pid) { - String command = "tasklist /V /FI \"pid eq " + pid + "\""; - String result = CommandUtil.execSystemCommand(command); - List array = formatWindowsProcess(result, true); - if (array == null || array.isEmpty()) { - return null; - } - return array.get(0); - } - - @Override - public String emptyLogFile(File file) { - return CommandUtil.execSystemCommand("echo \"\" > " + file.getAbsolutePath()); - } - - /** - * 将windows的tasklist转为集合 - * - * @param header 是否包含投信息 - * @param result 进程信息 - * @return jsonArray - */ - private static List formatWindowsProcess(String result, boolean header) { - List list = StrSplitter.splitTrim(result, StrUtil.LF, true); - if (list.isEmpty()) { - return null; - } - List processModels = new ArrayList<>(); - for (int i = header ? 2 : 0, len = list.size(); i < len; i++) { - String param = list.get(i); - List memList = StrSplitter.splitTrim(param, StrUtil.SPACE, true); - ProcessModel processModel = new ProcessModel(); - int pid = Convert.toInt(memList.get(1), 0); - processModel.setPid(pid); - // - String name = memList.get(0); - processModel.setCommand(name); - //使用内存 kb - String mem = memList.get(4).replace(",", ""); - long aLong = Convert.toLong(mem, 0L); -// FileUtil.readableFileSize() - processModel.setRes(aLong / 1024 + " MB"); - String status = memList.get(6); - processModel.setStatus(formatStatus(status)); - // - processModel.setUser(memList.get(7)); - processModel.setTime(memList.get(8)); - - try { -// JvmUtil.getOperatingSystemMXBean(memList.get(1), operatingSystemMXBean -> { -// if (operatingSystemMXBean != null) { -// //最近jvm cpu使用率 -// double processCpuLoad = operatingSystemMXBean.getProcessCpuLoad() * 100; -// if (processCpuLoad <= 0) { -// processCpuLoad = 0; -// } -// processModel.setCpu(String.format("%.2f", processCpuLoad) + "%"); -// //服务器总内存 -// long totalMemorySize = operatingSystemMXBean.getTotalPhysicalMemorySize(); -// BigDecimal total = new BigDecimal(totalMemorySize / 1024); -// // 进程 -// double v = new BigDecimal(aLong).divide(total, 4, BigDecimal.ROUND_HALF_UP).doubleValue() * 100; -// processModel.setMem(String.format("%.2f", v) + "%"); -// } -// }); - - } catch (Exception ignored) { - - } - processModels.add(processModel); - } - return processModels; - } - - private static String formatStatus(String status) { - if ("RUNNING".equalsIgnoreCase(status)) { - return "运行"; - } - if ("SUSPENDED".equalsIgnoreCase(status)) { - return "睡眠"; - } - if ("NOT RESPONDING".equalsIgnoreCase(status)) { - return "无响应"; - } - if ("Unknown".equalsIgnoreCase(status)) { - return "未知"; - } - return status; - } - - @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) { - return String.format("taskkill /F /PID %s", pid); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsTomcatCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsTomcatCommander.java deleted file mode 100644 index f7ebeee4d7..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/WindowsTomcatCommander.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.commander.AbstractTomcatCommander; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.util.CommandUtil; - -import java.util.List; - -/** - * tomcat的Windows管理命令 - * - * @author LF - */ -public class WindowsTomcatCommander extends AbstractTomcatCommander { - - /** - * windows下执行tomcat命令 - * - * @param tomcatInfoModel tomcat信息 - * @param cmd 执行的命令,包括start stop - * @return 返回tomcat启动结果 - */ - @Override - public String execCmd(TomcatInfoModel tomcatInfoModel, String cmd) { - String tomcatPath = tomcatInfoModel.pathAndCheck(); - //截取盘符 - String dcPath = null; - if (tomcatPath != null && tomcatPath.indexOf(StrUtil.SLASH) > 1) { - dcPath = tomcatPath.substring(0, tomcatPath.indexOf(StrUtil.SLASH)); - } - String command = null; - if (StrUtil.isBlank(tomcatPath)) { - return "tomcat path blank"; - } - - if ("stop".equals(cmd)) { - String setPidCmd = CommandUtil.execSystemCommand("jps -mv"); - List list = StrSplitter.splitTrim(setPidCmd, StrUtil.LF, true); - for (String item : list) { - //window下路径格式转换 - String msg = FileUtil.normalize(item + StrUtil.SLASH); - //判断集合中元素是否包含指定Tomcat路径 - boolean w = msg.contains(tomcatInfoModel.getPath()); - if (w) { - //截取TomcatPid - if (msg.indexOf(" ") > 1) { - String tmPid = msg.substring(0, msg.indexOf(" ")); - //判断截取的PID是否为纯数字 - if (NumberUtil.isInteger(tmPid)) { - command = String.format("taskkill /F /PID %s", tmPid); - exec(command, true); - } - } - } - } - } else { - command = String.format("cmd /k %s && cd %s/bin && start startup.bat", dcPath, tomcatPath); - exec(command, true); - } - - // 查询操作结果并返回 - return getStatus(tomcatInfoModel, cmd); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/package-info.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/package-info.java deleted file mode 100644 index 838bea1d34..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; \ No newline at end of file diff --git a/modules/agent/src/main/java/io/jpom/common/commander/package-info.java b/modules/agent/src/main/java/io/jpom/common/commander/package-info.java deleted file mode 100644 index 5f5d676fcd..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/commander/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander; \ No newline at end of file diff --git a/modules/agent/src/main/java/io/jpom/common/interceptor/AuthorizeInterceptor.java b/modules/agent/src/main/java/io/jpom/common/interceptor/AuthorizeInterceptor.java deleted file mode 100644 index 6a39c807c3..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/interceptor/AuthorizeInterceptor.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.interceptor.BaseInterceptor; -import cn.jiangzeyin.common.interceptor.InterceptorPattens; -import io.jpom.system.AgentAuthorize; -import io.jpom.system.ConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.method.HandlerMethod; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 授权拦截 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@InterceptorPattens() -public class AuthorizeInterceptor extends BaseInterceptor { - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - super.preHandle(request, response, handler); - if (handler instanceof HandlerMethod) { - HandlerMethod handlerMethod = (HandlerMethod) handler; - NotAuthorize notAuthorize = handlerMethod.getMethodAnnotation(NotAuthorize.class); - if (notAuthorize == null) { - String authorize = ServletUtil.getHeaderIgnoreCase(request, ConfigBean.JPOM_AGENT_AUTHORIZE); - if (StrUtil.isEmpty(authorize)) { - this.error(response); - return false; - } - if (!AgentAuthorize.getInstance().checkAuthorize(authorize)) { - this.error(response); - return false; - } - } - } - return true; - } - - private void error(HttpServletResponse response) { - ServletUtil.write(response, JsonMessage.getString(ConfigBean.AUTHORIZE_ERROR, "授权信息错误"), MediaType.APPLICATION_JSON_VALUE); - } -} diff --git a/modules/agent/src/main/java/io/jpom/common/interceptor/NotAuthorize.java b/modules/agent/src/main/java/io/jpom/common/interceptor/NotAuthorize.java deleted file mode 100644 index 88cf4e7b74..0000000000 --- a/modules/agent/src/main/java/io/jpom/common/interceptor/NotAuthorize.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import java.lang.annotation.*; - -/** - * 不需要授权 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@Documented -@Target(ElementType.METHOD) -@Inherited -@Retention(RetentionPolicy.RUNTIME) -public @interface NotAuthorize { -} - diff --git a/modules/agent/src/main/java/io/jpom/controller/IndexController.java b/modules/agent/src/main/java/io/jpom/controller/IndexController.java deleted file mode 100644 index 3280bc3dbb..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/IndexController.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.JpomManifest; -import io.jpom.common.RemoteVersion; -import io.jpom.common.interceptor.NotAuthorize; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.service.manage.ProjectInfoService; -import io.jpom.util.JvmUtil; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.util.List; - -/** - * 首页 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@RestController -public class IndexController extends BaseAgentController { - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - @Resource - private ProjectInfoService projectInfoService; - - @RequestMapping(value = {"index", "", "index.html", "/"}, produces = MediaType.TEXT_PLAIN_VALUE) - @NotAuthorize - public String index() { - return "Jpom-Agent,Can't access directly,Please configure it to JPOM server"; - } - - @RequestMapping(value = "info", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String info() { - int code; - if (whitelistDirectoryService.isInstalled()) { - code = 200; - } else { - code = 201; - } - JpomManifest instance = JpomManifest.getInstance(); - RemoteVersion remoteVersion = RemoteVersion.cacheInfo(); - // - JSONObject jsonObject = new JSONObject(); - jsonObject.put("manifest", instance); - jsonObject.put("remoteVersion", remoteVersion); - return JsonMessage.getString(code, "", jsonObject); - } - - /** - * 返回节点项目状态信息 - * - * @return array - */ - @RequestMapping(value = "status", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String status() { - List nodeProjectInfoModels = projectInfoService.list(); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("javaVirtualCount", JvmUtil.getJavaVirtualCount()); - jsonObject.put("osName", JpomManifest.getInstance().getOsName()); - jsonObject.put("jpomVersion", JpomManifest.getInstance().getVersion()); - jsonObject.put("javaVersion", SystemUtil.getJavaRuntimeInfo().getVersion()); - // 获取JVM中内存总大小 - long totalMemory = SystemUtil.getTotalMemory(); - jsonObject.put("totalMemory", FileUtil.readableFileSize(totalMemory)); - // - long freeMemory = SystemUtil.getFreeMemory(); - jsonObject.put("freeMemory", FileUtil.readableFileSize(freeMemory)); - int count = 0; - if (nodeProjectInfoModels != null) { - count = nodeProjectInfoModels.size(); - } - jsonObject.put("count", count); - // 运行时间 - jsonObject.put("runTime", JpomManifest.getInstance().getUpTime()); - return JsonMessage.getString(200, "", jsonObject); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/WelcomeController.java b/modules/agent/src/main/java/io/jpom/controller/WelcomeController.java deleted file mode 100644 index 0033420075..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/WelcomeController.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; - -import cn.hutool.cache.impl.CacheObj; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.base.AbstractController; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.JpomManifest; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.model.system.ProcessModel; -import io.jpom.system.TopManager; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -/** - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -public class WelcomeController extends AbstractController { - - @RequestMapping(value = "getDirectTop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getDirectTop() { - JSONObject topInfo = AbstractSystemCommander.getInstance().getAllMonitor(); - // - topInfo.put("time", System.currentTimeMillis()); - return JsonMessage.getString(200, "ok", topInfo); - } - - @RequestMapping(value = "getTop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getTop(Long millis) { - Iterator> cacheObjIterator = TopManager.get(); - List series = new ArrayList<>(); - List scale = new ArrayList<>(); - int count = 60; - int minSize = 12; - while (cacheObjIterator.hasNext()) { - CacheObj cacheObj = cacheObjIterator.next(); - String key = cacheObj.getKey(); - scale.add(key); - JSONObject value = cacheObj.getValue(); - series.add(value); - } - //限定数组最大数量 - if (series.size() > count) { - series = series.subList(series.size() - count, series.size()); - scale = scale.subList(scale.size() - count, scale.size()); - } - while (scale.size() <= minSize) { - if (scale.size() == 0) { - scale.add(DateUtil.formatTime(DateUtil.date())); - } - String time = scale.get(scale.size() - 1); - String newTime = StringUtil.getNextScaleTime(time, millis); - scale.add(newTime); - } - JSONObject object = new JSONObject(); - object.put("scales", scale); - object.put("series", series); - return JsonMessage.getString(200, "", object); - } - - - @RequestMapping(value = "processList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getProcessList() { - List array = AbstractSystemCommander.getInstance().getProcessList(); - if (array != null && !array.isEmpty()) { - array.sort(Comparator.comparingInt(ProcessModel::getPid)); - return JsonMessage.getString(200, "", array); - } - return JsonMessage.getString(402, "没有获取到进程信息"); - } - - - @RequestMapping(value = "kill.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String kill(int pid) { - long jpomAgentId = JpomManifest.getInstance().getPid(); - Assert.state(StrUtil.equals(StrUtil.toString(jpomAgentId), StrUtil.toString(pid)), "不支持在线关闭 Jpom Agent 进程"); - String result = AbstractSystemCommander.getInstance().kill(null, pid); - if (StrUtil.isEmpty(result)) { - result = "成功kill"; - } - return JsonMessage.getString(200, result); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/JdkListController.java b/modules/agent/src/main/java/io/jpom/controller/manage/JdkListController.java deleted file mode 100644 index 973e2823bc..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/JdkListController.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseAgentController; -import io.jpom.model.data.JdkInfoModel; -import io.jpom.service.manage.JdkInfoService; -import io.jpom.util.FileUtils; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.File; -import java.util.List; - -/** - * @author bwcx_jzy - * @date 2019/11/1 - */ -@RestController -@RequestMapping(value = "/manage/jdk/") -public class JdkListController extends BaseAgentController { - - @Resource - private JdkInfoService jdkInfoService; - - @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String list() { - List list = jdkInfoService.list(); - return JsonMessage.getString(200, "", list); - } - - @RequestMapping(value = "update", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String update(JdkInfoModel model) { - String path = model.getPath(); - if (StrUtil.isEmpty(path)) { - return JsonMessage.getString(400, "请填写jdk路径"); - } - String newPath = FileUtil.normalize(path); - File file = FileUtil.file(newPath); - //去除bin - if ("bin".equals(file.getName())) { - newPath = file.getParentFile().getAbsolutePath(); - } - if (!FileUtils.isJdkPath(newPath)) { - return JsonMessage.getString(400, "路径错误,该路径不是jdk路径"); - } - model.setPath(newPath); - String id = model.getId(); - String jdkVersion = FileUtils.getJdkVersion(newPath); - model.setVersion(jdkVersion); - String name = model.getName(); - if (StrUtil.isEmpty(name)) { - model.setName(jdkVersion); - } - if (StrUtil.isEmpty(id)) { - model.setId(IdUtil.fastSimpleUUID()); - jdkInfoService.addItem(model); - return JsonMessage.getString(200, "添加成功"); - } - jdkInfoService.updateItem(model); - return JsonMessage.getString(200, "修改成功"); - } - - @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String delete(String id) { - if (StrUtil.isEmpty(id)) { - return JsonMessage.getString(400, "删除失败"); - } - jdkInfoService.deleteItem(id); - return JsonMessage.getString(200, "删除成功"); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/ManageEditProjectController.java b/modules/agent/src/main/java/io/jpom/controller/manage/ManageEditProjectController.java deleted file mode 100644 index a413571cda..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/ManageEditProjectController.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.RegexPool; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.JpomApplication; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.RunMode; -import io.jpom.model.data.JdkInfoModel; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.service.manage.JdkInfoService; -import io.jpom.system.ConfigBean; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -/** - * 编辑项目 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -@RequestMapping(value = "/manage/") -public class ManageEditProjectController extends BaseAgentController { - - private final WhitelistDirectoryService whitelistDirectoryService; - private final JdkInfoService jdkInfoService; - - public ManageEditProjectController(WhitelistDirectoryService whitelistDirectoryService, - JdkInfoService jdkInfoService) { - this.whitelistDirectoryService = whitelistDirectoryService; - this.jdkInfoService = jdkInfoService; - } - - /** - * 基础检查 - * - * @param projectInfo 项目实体 - * @param whitelistDirectory 白名单 - * @param previewData 预检查数据 - */ - private void checkParameter(NodeProjectInfoModel projectInfo, String whitelistDirectory, boolean previewData) { - String id = projectInfo.getId(); - Assert.state(!StrUtil.isEmptyOrUndefined(id), "项目id不能为空"); - Assert.state(StringUtil.isGeneral(id, 2, 20), "项目id 长度范围2-20(英文字母 、数字和下划线)"); - Assert.state(!JpomApplication.SYSTEM_ID.equals(id), "项目id " + JpomApplication.SYSTEM_ID + " 关键词被系统占用"); - // 防止和Jpom冲突 - ConfigBean instance = ConfigBean.getInstance(); - Assert.state(!instance.applicationTag.equalsIgnoreCase(id), "当前项目id已经被Jpom占用"); - // 运行模式 - String runMode = getParameter("runMode"); - RunMode runMode1 = RunMode.ClassPath; - try { - runMode1 = RunMode.valueOf(runMode); - } catch (Exception ignored) { - } - projectInfo.setRunMode(runMode1); - // 监测 - if (runMode1 == RunMode.ClassPath || runMode1 == RunMode.JavaExtDirsCp) { - Assert.hasText(projectInfo.getMainClass(), "ClassPath、JavaExtDirsCp 模式 MainClass必填"); - } else if (runMode1 == RunMode.Jar || runMode1 == RunMode.JarWar) { - projectInfo.setMainClass(StrUtil.EMPTY); - } - if (runMode1 == RunMode.JavaExtDirsCp) { - Assert.hasText(projectInfo.getJavaExtDirsCp(), "JavaExtDirsCp 模式 javaExtDirsCp必填"); - } - // 判断是否为分发添加 - 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 (outGivingProject) { - whitelistDirectoryService.addProjectWhiteList(logPath); - } else { - throw new IllegalArgumentException("请填写的项目日志存储路径,或者还没有配置白名单"); - } - } - } - } - // - String lib = projectInfo.getLib(); - Assert.state(StrUtil.isNotEmpty(lib) && !StrUtil.SLASH.equals(lib) && !Validator.isChinese(lib), "项目Jar路径不能为空,不能为顶级目录,不能包含中文"); - - Assert.state(checkPathSafe(lib), "项目Jar路径存在提升目录问题"); - - // java 程序副本 - if (runMode1 == RunMode.ClassPath || runMode1 == RunMode.Jar || runMode1 == RunMode.JarWar || runMode1 == RunMode.JavaExtDirsCp) { - String javaCopyIds = getParameter("javaCopyIds"); - if (StrUtil.isEmpty(javaCopyIds)) { - projectInfo.setJavaCopyItemList(null); - } else { - String[] split = StrUtil.splitToArray(javaCopyIds, StrUtil.COMMA); - List javaCopyItemList = new ArrayList<>(split.length); - for (String copyId : split) { - String jvm = getParameter("jvm_" + copyId); - String args = getParameter("args_" + copyId); - // - NodeProjectInfoModel.JavaCopyItem javaCopyItem = new NodeProjectInfoModel.JavaCopyItem(); - javaCopyItem.setId(copyId); - javaCopyItem.setParendId(id); - javaCopyItem.setModifyTime(DateUtil.now()); - javaCopyItem.setJvm(StrUtil.emptyToDefault(jvm, StrUtil.EMPTY)); - javaCopyItem.setArgs(StrUtil.emptyToDefault(args, StrUtil.EMPTY)); - javaCopyItemList.add(javaCopyItem); - } - projectInfo.setJavaCopyItemList(javaCopyItemList); - } - } else { - projectInfo.setJavaCopyItemList(null); - } - } - - - @RequestMapping(value = "saveProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String saveProject(NodeProjectInfoModel projectInfo) { - // 预检查数据 - String strPreviewData = getParameter("previewData"); - boolean previewData = Convert.toBool(strPreviewData, false); - String whitelistDirectory = projectInfo.getWhitelistDirectory(); - // - this.checkParameter(projectInfo, whitelistDirectory, previewData); - - String id = projectInfo.getId(); - // - String allLib = projectInfo.allLib(); - // 重复lib - List list = projectInfoService.list(); - if (list != null) { - for (NodeProjectInfoModel nodeProjectInfoModel : list) { - if (!nodeProjectInfoModel.getId().equals(id) && nodeProjectInfoModel.allLib().equals(allLib)) { - return JsonMessage.getString(401, "当前项目Jar路径已经被【" + nodeProjectInfoModel.getName() + "】占用,请检查"); - } - } - } - File checkFile = new File(allLib); - if (checkFile.exists() && checkFile.isFile()) { - return JsonMessage.getString(401, "项目Jar路径是一个已经存在的文件"); - } - // 自动生成log文件 -// String log = new File(allLib).getParent(); -// log = String.format("%s/%s.log", log, id); -// projectInfo.setLog(FileUtil.normalize(log)); - String log = projectInfo.getLog(); - Assert.hasText(log, "项目log解析读取失败"); - checkFile = new File(log); - if (checkFile.exists() && checkFile.isDirectory()) { - return JsonMessage.getString(401, "项目log是一个已经存在的文件夹"); - } - // - String token = projectInfo.getToken(); - if (StrUtil.isNotEmpty(token)) { - Validator.validateMatchRegex(RegexPool.URL_HTTP, token, "WebHooks 地址不合法"); - } - // 判断空格 - if (id.contains(StrUtil.SPACE) || allLib.contains(StrUtil.SPACE)) { - return JsonMessage.getString(401, "项目Id、项目Jar不能包含空格"); - } - String jdkId = projectInfo.getJdkId(); - if (StrUtil.isNotEmpty(jdkId)) { - JdkInfoModel item = jdkInfoService.getItem(jdkId); - Assert.notNull(item, "jdk 信息错误"); - } - return save(projectInfo, previewData); - } - - /** - * 保存项目 - * - * @param projectInfo 项目 - * @param previewData 是否是预检查 - * @return 错误信息 - */ - private String save(NodeProjectInfoModel projectInfo, boolean previewData) { - projectInfo.setWorkspaceId(getWorkspaceId()); - NodeProjectInfoModel exits = projectInfoService.getItem(projectInfo.getId()); - try { - this.checkPath(projectInfo); - if (exits == null) { - // 检查运行中的tag 是否被占用 - Assert.state(!AbstractProjectCommander.getInstance().isRun(projectInfo, null), "当前项目id已经被正在运行的程序占用"); - if (previewData) { - // 预检查数据 - return JsonMessage.getString(200, "检查通过"); - } else { - projectInfoService.addItem(projectInfo); - return JsonMessage.getString(200, "新增成功!"); - } - } - if (previewData) { - // 预检查数据 - return JsonMessage.getString(200, "检查通过"); - } else { - exits.setLog(projectInfo.getLog()); - exits.setLogPath(projectInfo.getLogPath()); - exits.setName(projectInfo.getName()); -// exits.setGroup(projectInfo.getGroup()); - exits.setAutoStart(projectInfo.getAutoStart()); - exits.setMainClass(projectInfo.getMainClass()); - exits.setLib(projectInfo.getLib()); - exits.setJvm(projectInfo.getJvm()); - exits.setArgs(projectInfo.getArgs()); - exits.setWorkspaceId(this.getWorkspaceId()); - exits.setOutGivingProject(projectInfo.isOutGivingProject()); - exits.setRunMode(projectInfo.getRunMode()); - exits.setWhitelistDirectory(projectInfo.getWhitelistDirectory()); - exits.setToken(projectInfo.getToken()); - exits.setJdkId(projectInfo.getJdkId()); - // 检查是否非法删除副本集 - List javaCopyItemList = exits.getJavaCopyItemList(); - List javaCopyItemList1 = projectInfo.getJavaCopyItemList(); - if (CollUtil.isNotEmpty(javaCopyItemList) && !CollUtil.containsAll(javaCopyItemList1, javaCopyItemList)) { - // 重写了 equals - return JsonMessage.getString(405, "修改中不能删除副本集、请到副本集中删除"); - } - exits.setJavaCopyItemList(javaCopyItemList1); - exits.setJavaExtDirsCp(projectInfo.getJavaExtDirsCp()); - // - moveTo(exits, projectInfo); - projectInfoService.updateItem(exits); - return JsonMessage.getString(200, "修改成功"); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - return JsonMessage.getString(500, "保存数据异常"); - } - } - - private void moveTo(NodeProjectInfoModel old, NodeProjectInfoModel news) { - // 移动目录 - if (!old.allLib().equals(news.allLib())) { - File oldLib = new File(old.allLib()); - if (oldLib.exists()) { - File newsLib = new File(news.allLib()); - FileUtil.move(oldLib, newsLib, true); - } - } - // log - if (!old.getLog().equals(news.getLog())) { - File oldLog = new File(old.getLog()); - if (oldLog.exists()) { - File newsLog = new File(news.getLog()); - FileUtil.move(oldLog, newsLog, true); - } - // logBack - File oldLogBack = old.getLogBack(); - if (oldLogBack.exists()) { - FileUtil.move(oldLogBack, news.getLogBack(), true); - } - } - - } - - /** - * 路径存在包含关系 - * - * @param nodeProjectInfoModel 比较的项目 - */ - private void checkPath(NodeProjectInfoModel nodeProjectInfoModel) { - List nodeProjectInfoModelList = projectInfoService.list(); - if (nodeProjectInfoModelList == null) { - return; - } - NodeProjectInfoModel nodeProjectInfoModel1 = null; - for (NodeProjectInfoModel model : nodeProjectInfoModelList) { - if (!model.getId().equals(nodeProjectInfoModel.getId())) { - File file1 = new File(model.allLib()); - File file2 = new File(nodeProjectInfoModel.allLib()); - if (FileUtil.pathEquals(file1, file2)) { - nodeProjectInfoModel1 = model; - break; - } - // 包含关系 - if (FileUtil.isSub(file1, file2) || FileUtil.isSub(file2, file1)) { - nodeProjectInfoModel1 = model; - break; - } - } - } - if (nodeProjectInfoModel1 != null) { - throw new IllegalArgumentException("项目Jar路径和【" + nodeProjectInfoModel1.getName() + "】项目冲突:" + nodeProjectInfoModel1.allLib()); - } - } - - /** - * 删除项目 - * - * @return json - */ - @RequestMapping(value = "deleteProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String deleteProject(String copyId) { - NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); - Assert.notNull(nodeProjectInfoModel, "项目不存在"); - try { - NodeProjectInfoModel.JavaCopyItem copyItem = nodeProjectInfoModel.findCopyItem(copyId); - - if (copyItem == null) { - // 运行判断 - boolean run = AbstractProjectCommander.getInstance().isRun(nodeProjectInfoModel, null); - Assert.state(!run, "不能删除正在运行的项目"); - projectInfoService.deleteItem(nodeProjectInfoModel.getId()); - } else { - boolean run = AbstractProjectCommander.getInstance().isRun(nodeProjectInfoModel, copyItem); - Assert.state(!run, "不能删除正在运行的项目副本"); - boolean removeCopyItem = nodeProjectInfoModel.removeCopyItem(copyId); - Assert.state(removeCopyItem, "删除对应副本集不存在"); - projectInfoService.updateItem(nodeProjectInfoModel); - } - return JsonMessage.getString(200, "删除成功!"); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - return JsonMessage.getString(500, "删除异常:" + e.getMessage()); - } - } - - @RequestMapping(value = "releaseOutGiving", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String releaseOutGiving() { - NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); - if (nodeProjectInfoModel != null) { - nodeProjectInfoModel.setOutGivingProject(false); - projectInfoService.updateItem(nodeProjectInfoModel); - } - return JsonMessage.getString(200, "ok"); - } - - /** - * 检查项目lib 情况 - * - * @param id 项目id - * @param newLib 新路径 - * @return 状态码,400是一定不能操作的,401 是提醒 - */ - @RequestMapping(value = "judge_lib.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String saveProject(String id, String newLib) { - File file = new File(newLib); - // 填写的jar路径是一个存在的文件 - Assert.state(!FileUtil.isFile(file), "填写jar目录当前是一个已经存在的文件,请修改"); - - NodeProjectInfoModel exits = projectInfoService.getItem(id); - if (exits == null) { - // 创建项目 填写的jar路径是已经存在的文件夹 - Assert.state(!FileUtil.exist(file), "填写jar目录当前已经在,创建成功后会自动同步文件"); - } else { - // 已经存在的项目 - File oldLib = new File(exits.allLib()); - Path newPath = file.toPath(); - Path oldPath = oldLib.toPath(); - if (newPath.equals(oldPath)) { - // 新 旧没有变更 - return JsonMessage.getString(200, ""); - } - if (file.exists()) { - String msg; - if (oldLib.exists()) { - // 新旧jar路径都存在,会自动覆盖新的jar路径中的文件 - msg = "原jar目录已经存在并且新的jar目录已经存在,保存将覆盖新文件夹并会自动同步原jar目录"; - } else { - msg = "填写jar目录当前已经在,创建成功后会自动同步文件"; - } - return JsonMessage.getString(401, msg); - } - } - Assert.state(!Validator.isChinese(newLib), "不建议使用中文目录"); - - return JsonMessage.getString(200, ""); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectFileControl.java b/modules/agent/src/main/java/io/jpom/controller/manage/ProjectFileControl.java deleted file mode 100644 index e65fe4868b..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectFileControl.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.http.HttpUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.AfterOpt; -import io.jpom.model.BaseEnum; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.service.manage.ConsoleService; -import io.jpom.socket.ConsoleCommandOp; -import io.jpom.system.AgentConfigBean; -import io.jpom.util.CompressionFileUtil; -import io.jpom.util.FileUtils; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import java.io.File; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * 项目文件管理 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@RestController -@RequestMapping(value = "/manage/file/") -public class ProjectFileControl extends BaseAgentController { - - @Resource - private ConsoleService consoleService; - - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - - @RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getFileList(String id, String path) { - // 查询项目路径 - NodeProjectInfoModel pim = projectInfoService.getItem(id); - Assert.notNull(pim, "查询失败:项目不存在"); - String lib = pim.allLib(); - File fileDir = FileUtil.file(lib, StrUtil.emptyToDefault(path, FileUtil.FILE_SEPARATOR)); - boolean exist = FileUtil.exist(fileDir); - if (!exist) { - return JsonMessage.getString(200, "查询成功", new JSONArray()); - } - // - File[] filesAll = fileDir.listFiles(); - if (ArrayUtil.isEmpty(filesAll)) { - return JsonMessage.getString(200, "查询成功", new JSONArray()); - } - JSONArray arrayFile = FileUtils.parseInfo(filesAll, false, lib); - AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); - for (Object o : arrayFile) { - JSONObject jsonObject = (JSONObject) o; - String filename = jsonObject.getString("filename"); - jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(whitelist.getAllowEditSuffix(), filename)); - } - - return JsonMessage.getString(200, "查询成功", arrayFile); - } - - - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String upload() throws Exception { - NodeProjectInfoModel pim = getProjectInfoModel(); - MultipartFileBuilder multipartFileBuilder = createMultipart() - .addFieldName("file"); - // 压缩文件 - String type = getParameter("type"); - // 是否清空 - String clearType = getParameter("clearType"); - String levelName = getParameter("levelName"); - File lib; - if (StrUtil.isEmpty(levelName)) { - lib = new File(pim.allLib()); - } else { - lib = FileUtil.file(pim.allLib(), levelName); - } - // 判断是否需要清空 - if ("clear".equalsIgnoreCase(clearType)) { - if (!FileUtil.clean(lib)) { - FileUtil.del(lib.toPath()); - //return JsonMessage.getString(500, "清除旧lib失败"); - } - } - if ("unzip".equals(type)) { - multipartFileBuilder.setFileExt(StringUtil.PACKAGE_EXT); - multipartFileBuilder.setSavePath(AgentConfigBean.getInstance().getTempPathName()); - String path = multipartFileBuilder.save(); - // 解压 - File file = new File(path); - try { - CompressionFileUtil.unCompress(file, lib); - } finally { - if (!FileUtil.del(file)) { - DefaultSystemLog.getLog().error("删除文件失败:" + file.getPath()); - } - } - } else { - multipartFileBuilder.setSavePath(FileUtil.getAbsolutePath(lib)) - .setUseOriginalFilename(true); - // 保存 - multipartFileBuilder.save(); - } - // 修改使用状态 - pim.setUseLibDesc("upload"); - projectInfoService.updateItem(pim); - // - String after = getParameter("after"); - if (StrUtil.isNotEmpty(after)) { - // - List javaCopyItemList = pim.getJavaCopyItemList(); - // - AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(after, AfterOpt.No.getCode())); - if ("restart".equalsIgnoreCase(after) || afterOpt == AfterOpt.Restart) { - String result = consoleService.execCommand(ConsoleCommandOp.restart, pim, null); - // 自动处理副本集 - if (javaCopyItemList != null) { - ThreadUtil.execute(() -> javaCopyItemList.forEach(javaCopyItem -> { - try { - consoleService.execCommand(ConsoleCommandOp.restart, pim, javaCopyItem); - } catch (Exception e) { - DefaultSystemLog.getLog().error("重启副本集失败", e); - } - })); - } - return JsonMessage.getString(200, "上传成功并重启:" + result); - } - if (afterOpt == AfterOpt.Order_Restart || afterOpt == AfterOpt.Order_Must_Restart) { - boolean restart = this.restart(pim, null, afterOpt); - if (javaCopyItemList != null) { - ThreadUtil.execute(() -> { - // 副本 - for (NodeProjectInfoModel.JavaCopyItem javaCopyItem : javaCopyItemList) { - if (!this.restart(pim, javaCopyItem, afterOpt)) { - return; - } - // 休眠30秒 等待之前项目正常启动 - try { - TimeUnit.SECONDS.sleep(30); - } catch (InterruptedException ignored) { - } - } - }); - } - } - } - return JsonMessage.getString(200, "上传成功"); - } - - - private boolean restart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem, AfterOpt afterOpt) { - try { - String result = consoleService.execCommand(ConsoleCommandOp.restart, nodeProjectInfoModel, javaCopyItem); - int pid = AbstractProjectCommander.parsePid(result); - if (pid <= 0) { - // 完整重启,不再继续剩余的节点项目 - return afterOpt != AfterOpt.Order_Must_Restart; - } - return true; - } catch (Exception e) { - DefaultSystemLog.getLog().error("重复失败", e); - // 完整重启,不再继续剩余的节点项目 - return afterOpt != AfterOpt.Order_Must_Restart; - } - } - - @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String deleteFile(String filename, String type, String levelName) { - NodeProjectInfoModel pim = getProjectInfoModel(); - if ("clear".equalsIgnoreCase(type)) { - // 清空文件 - File file = new File(pim.allLib()); - if (FileUtil.clean(file)) { - return JsonMessage.getString(200, "清除成功"); - } - boolean run = AbstractProjectCommander.getInstance().isRun(pim, null); - Assert.state(!run, "文件被占用,请先停止项目"); - return JsonMessage.getString(500, "删除失败:" + file.getAbsolutePath()); - } else { - // 删除文件 - String fileName = pathSafe(filename); - if (StrUtil.isEmpty(fileName)) { - return JsonMessage.getString(405, "非法操作"); - } - File file; - if (StrUtil.isEmpty(levelName)) { - file = FileUtil.file(pim.allLib(), fileName); - } else { - file = FileUtil.file(pim.allLib(), levelName, fileName); - } - if (file.exists()) { - if (FileUtil.del(file)) { - return JsonMessage.getString(200, "删除成功"); - } - } else { - return JsonMessage.getString(404, "文件不存在"); - } - return JsonMessage.getString(500, "删除失败"); - } - } - - /** - * 读取文件内容 (只能处理文本文件) - * - * @param filePath 相对项目文件的文件夹 - * @param filename 读取的文件名 - * @return json - */ - @PostMapping(value = "read_file", produces = MediaType.APPLICATION_JSON_VALUE) - public String readFile(String filePath, String filename) { - NodeProjectInfoModel pim = getProjectInfoModel(); - filePath = StrUtil.emptyToDefault(filePath, File.separator); - // 判断文件后缀 - AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); - Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename); - File file = FileUtil.file(pim.allLib(), filePath, filename); - String ymlString = FileUtil.readString(file, charset); - return JsonMessage.getString(200, "", ymlString); - } - - /** - * 保存文件内容 (只能处理文本文件) - * - * @param filePath 相对项目文件的文件夹 - * @param filename 读取的文件名 - * @param fileText 文件内容 - * @return json - */ - @PostMapping(value = "update_config_file", produces = MediaType.APPLICATION_JSON_VALUE) - public String updateConfigFile(String filePath, String filename, String fileText) { - NodeProjectInfoModel pim = getProjectInfoModel(); - filePath = StrUtil.emptyToDefault(filePath, File.separator); - // 判断文件后缀 - AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); - Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename); - FileUtil.writeString(fileText, FileUtil.file(pim.allLib(), filePath, filename), charset); - return JsonMessage.getString(200, "文件写入成功"); - } - - - /** - * 将执行文件下载到客户端 本地 - * - * @param id 项目id - * @param filename 文件名 - * @param levelName 文件夹名 - * @return 正常情况返回文件流,非正在返回 text plan - */ - @GetMapping(value = "download", produces = MediaType.APPLICATION_JSON_VALUE) - public String download(String id, String filename, String levelName) { - String safeFileName = pathSafe(filename); - if (StrUtil.isEmpty(safeFileName)) { - return JsonMessage.getString(405, "非法操作"); - } - try { - NodeProjectInfoModel pim = projectInfoService.getItem(id); - File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); - if (file.isDirectory()) { - return "暂不支持下载文件夹"; - } - ServletUtil.write(getResponse(), file); - } catch (Exception e) { - DefaultSystemLog.getLog().error("下载文件异常", e); - } - return "下载失败。请刷新页面后重试"; - } - - /** - * 下载远程文件 - * - * @param id 项目id - * @param url 远程 url 地址 - * @param levelName 保存的文件夹 - * @param unzip 是否为压缩包、true 将自动解压 - * @return json - */ - @PostMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE) - public String remoteDownload(String id, String url, String levelName, String unzip) { - if (StrUtil.isEmpty(url)) { - return JsonMessage.getString(405, "请输入正确的远程地址"); - } - AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); - Set allowRemoteDownloadHost = whitelist.getAllowRemoteDownloadHost(); - Assert.state(CollUtil.isNotEmpty(allowRemoteDownloadHost), "还没有配置运行的远程地址"); - List collect = allowRemoteDownloadHost.stream().filter(s -> StrUtil.startWith(url, s)).collect(Collectors.toList()); - Assert.state(CollUtil.isNotEmpty(collect), "不允许下载当前地址的文件"); - try { - NodeProjectInfoModel pim = projectInfoService.getItem(id); - File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR)); - File downloadFile = HttpUtil.downloadFileFromUrl(url, file); - if (BooleanUtil.toBoolean(unzip)) { - // 需要解压文件 - try { - CompressionFileUtil.unCompress(file, downloadFile); - } finally { - if (!FileUtil.del(downloadFile)) { - DefaultSystemLog.getLog().error("删除文件失败:" + file.getPath()); - } - } - } - return JsonMessage.getString(200, "下载成功文件大小:" + FileUtil.readableFileSize(downloadFile)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("下载远程文件异常", e); - return JsonMessage.getString(500, "下载远程文件失败:" + e.getMessage()); - } - } - -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectListController.java b/modules/agent/src/main/java/io/jpom/controller/manage/ProjectListController.java deleted file mode 100644 index e60bd0fb25..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectListController.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage; - -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 管理的信息获取接口 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -@RequestMapping(value = "/manage/") -public class ProjectListController extends BaseAgentController { - - /** - * 获取项目的信息 - * - * @param id id - * @return item - * @see NodeProjectInfoModel - */ - @RequestMapping(value = "getProjectItem", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProjectItem(String id) { - NodeProjectInfoModel nodeProjectInfoModel = projectInfoService.getItem(id); - if (nodeProjectInfoModel != null) { - // 返回实际执行的命令 - String command = AbstractProjectCommander.getInstance().buildCommand(nodeProjectInfoModel, null); - nodeProjectInfoModel.setRunCommand(command); - } - return JsonMessage.getString(200, "", nodeProjectInfoModel); - } - - /** - * 程序项目信息 - * - * @return json - */ - @RequestMapping(value = "getProjectInfo", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProjectInfo() { - try { - // 查询数据 - List nodeProjectInfoModels = projectInfoService.list(); - return JsonMessage.getString(200, "查询成功!", nodeProjectInfoModels); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - return JsonMessage.getString(500, "查询异常:" + e.getMessage()); - } - } - - /** - * 展示项目页面 - */ - @RequestMapping(value = "project_copy_list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String projectCopyList(String id) { - NodeProjectInfoModel nodeProjectInfoModel = projectInfoService.getItem(id); - Assert.notNull(nodeProjectInfoModel, "没有对应项目"); - - List javaCopyItemList = nodeProjectInfoModel.getJavaCopyItemList(); - Assert.notEmpty(javaCopyItemList, "对应项目没有副本集"); - JSONArray array = new JSONArray(); - for (NodeProjectInfoModel.JavaCopyItem javaCopyItem : javaCopyItemList) { - JSONObject object = javaCopyItem.toJson(); - boolean run = AbstractProjectCommander.getInstance().isRun(nodeProjectInfoModel, javaCopyItem); - object.put("status", run); - array.add(object); - } - return JsonMessage.getString(200, "", array); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectRecoverControl.java b/modules/agent/src/main/java/io/jpom/controller/manage/ProjectRecoverControl.java deleted file mode 100644 index 39abf1491d..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectRecoverControl.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseJpomController; -import io.jpom.model.data.ProjectRecoverModel; -import io.jpom.service.manage.ProjectRecoverService; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.IOException; -import java.util.List; - -/** - * 回收站管理 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -@RequestMapping(value = "/manage/recover") -public class ProjectRecoverControl extends BaseJpomController { - @Resource - private ProjectRecoverService projectRecoverService; - - @RequestMapping(value = "list_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String projectInfo() { - List projectInfoModels = projectRecoverService.list(); - return JsonMessage.getString(200, "", projectInfoModels); - } - - @RequestMapping(value = "item_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String project(String id) throws IOException { - if (StrUtil.isEmpty(id)) { - return JsonMessage.getString(400, "项目id错误"); - } - ProjectRecoverModel item = projectRecoverService.getItem(id); - return JsonMessage.getString(200, "", item); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectStatusController.java b/modules/agent/src/main/java/io/jpom/controller/manage/ProjectStatusController.java deleted file mode 100644 index e9c48cdbe1..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/ProjectStatusController.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.manage.ConsoleService; -import io.jpom.socket.ConsoleCommandOp; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 项目文件管理 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@RestController -@RequestMapping(value = "/manage/") -public class ProjectStatusController extends BaseAgentController { - - private final ConsoleService consoleService; - - public ProjectStatusController(ConsoleService consoleService) { - this.consoleService = consoleService; - } - - - /** - * 获取项目的进程id - * - * @param id 项目id - * @return json - */ - @RequestMapping(value = "getProjectStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProjectStatus(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确")) String id, String getCopy) { - NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); - Assert.notNull(nodeProjectInfoModel, "项目id不存在"); - int pid = 0; - try { - pid = AbstractProjectCommander.getInstance().getPid(id); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取项目pid 失败", e); - } - JSONObject jsonObject = new JSONObject(); - jsonObject.put("pId", pid); - // - if (StrUtil.isNotEmpty(getCopy)) { - List javaCopyItemList = nodeProjectInfoModel.getJavaCopyItemList(); - JSONArray copys = new JSONArray(); - if (javaCopyItemList != null) { - for (NodeProjectInfoModel.JavaCopyItem javaCopyItem : javaCopyItemList) { - JSONObject jsonObject1 = new JSONObject(); - jsonObject1.put("copyId", javaCopyItem.getId()); - boolean run = AbstractProjectCommander.getInstance().isRun(nodeProjectInfoModel, javaCopyItem); - jsonObject1.put("status", run); - copys.add(jsonObject1); - } - } - jsonObject.put("copys", copys); - } - return JsonMessage.getString(200, "", jsonObject); - } - - /** - * 获取项目的运行端口 - * - * @param ids ids - * @return obj - */ - @RequestMapping(value = "getProjectPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProjectPort(String ids) { - Assert.hasText(ids, "没有要获取的信息"); - JSONArray jsonArray = JSONArray.parseArray(ids); - JSONObject jsonObject = new JSONObject(); - JSONObject itemObj; - for (Object object : jsonArray) { - String item = object.toString(); - int pid; - try { - pid = AbstractProjectCommander.getInstance().getPid(item); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取端口错误", e); - continue; - } - if (pid <= 0) { - continue; - } - itemObj = new JSONObject(); - String port = AbstractProjectCommander.getInstance().getMainPort(pid); - itemObj.put("port", port); - itemObj.put("pid", pid); - jsonObject.put(item, itemObj); - } - return JsonMessage.getString(200, "", jsonObject); - } - - - /** - * 获取项目的运行端口 - * - * @param id 项目id - * @param copyIds 副本 ids ["aa","ss"] - * @return obj - */ - @RequestMapping(value = "getProjectCopyPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProjectPort(String id, String copyIds) { - if (StrUtil.isEmpty(copyIds) || StrUtil.isEmpty(id)) { - return JsonMessage.getString(400, ""); - } - NodeProjectInfoModel nodeProjectInfoModel = getProjectInfoModel(); - - JSONArray jsonArray = JSONArray.parseArray(copyIds); - JSONObject jsonObject = new JSONObject(); - JSONObject itemObj; - for (Object object : jsonArray) { - String item = object.toString(); - NodeProjectInfoModel.JavaCopyItem copyItem = nodeProjectInfoModel.findCopyItem(item); - int pid; - try { - pid = AbstractProjectCommander.getInstance().getPid(copyItem.getTagId()); - if (pid <= 0) { - continue; - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取端口错误", e); - continue; - } - itemObj = new JSONObject(); - String port = AbstractProjectCommander.getInstance().getMainPort(pid); - itemObj.put("port", port); - itemObj.put("pid", pid); - jsonObject.put(item, itemObj); - } - return JsonMessage.getString(200, "", jsonObject); - } - - @RequestMapping(value = "restart", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String restart(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确")) String id, String copyId) { - NodeProjectInfoModel item = projectInfoService.getItem(id); - Assert.notNull(item, "没有找到对应的项目"); - NodeProjectInfoModel.JavaCopyItem copyItem = item.findCopyItem(copyId); - - String result; - try { - result = consoleService.execCommand(ConsoleCommandOp.restart, item, copyItem); - boolean status = AbstractProjectCommander.getInstance().isRun(item, copyItem); - if (status) { - return JsonMessage.getString(200, result); - } - return JsonMessage.getString(201, "重启项目失败:" + result); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取项目pid 失败", e); - result = "error:" + e.getMessage(); - return JsonMessage.getString(500, "重启项目异常:" + result); - } - } - - - @RequestMapping(value = "stop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String stop(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确")) String id, String copyId) { - NodeProjectInfoModel item = projectInfoService.getItem(id); - Assert.notNull(item, "没有找到对应的项目"); - NodeProjectInfoModel.JavaCopyItem copyItem = item.findCopyItem(copyId); - - String result; - try { - result = consoleService.execCommand(ConsoleCommandOp.stop, item, copyItem); - boolean status = AbstractProjectCommander.getInstance().isRun(item, copyItem); - if (!status) { - return JsonMessage.getString(200, result); - } - return JsonMessage.getString(201, "关闭项目失败:" + result); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取项目pid 失败", e); - result = "error:" + e.getMessage(); - return JsonMessage.getString(500, "关闭项目异常:" + result); - } - } - - - @RequestMapping(value = "start", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String start(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确")) String id, String copyId) { - NodeProjectInfoModel item = projectInfoService.getItem(id); - Assert.notNull(item, "没有找到对应的项目"); - NodeProjectInfoModel.JavaCopyItem copyItem = item.findCopyItem(copyId); - String result; - try { - result = consoleService.execCommand(ConsoleCommandOp.start, item, copyItem); - boolean status = AbstractProjectCommander.getInstance().isRun(item, copyItem); - if (status) { - return JsonMessage.getString(200, result); - } - return JsonMessage.getString(201, "启动项目失败:" + result); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取项目pid 失败", e); - result = "error:" + e.getMessage(); - return JsonMessage.getString(500, "启动项目异常:" + result); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/manage/log/LogBackController.java b/modules/agent/src/main/java/io/jpom/controller/manage/log/LogBackController.java deleted file mode 100644 index 2fa8de45e4..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/manage/log/LogBackController.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.manage.log; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.util.FileUtils; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletResponse; -import java.io.File; - -/** - * @author jiangzeyin - * @date 2019/4/17 - */ -@RestController -@RequestMapping(value = "manage/log") -public class LogBackController extends BaseAgentController { - - @RequestMapping(value = "logSize", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String logSize(String id, String copyId) { - NodeProjectInfoModel nodeProjectInfoModel = getProjectInfoModel(); - JSONObject jsonObject = new JSONObject(); - // - NodeProjectInfoModel.JavaCopyItem copyItem = nodeProjectInfoModel.findCopyItem(copyId); - //获取日志备份路径 - File logBack = copyItem == null ? nodeProjectInfoModel.getLogBack() : nodeProjectInfoModel.getLogBack(copyItem); - boolean logBackBool = logBack.exists() && logBack.isDirectory(); - jsonObject.put("logBack", logBackBool); - String info = projectInfoService.getLogSize(nodeProjectInfoModel, copyItem); - jsonObject.put("logSize", info); - return JsonMessage.getString(200, "", jsonObject); - } - - @RequestMapping(value = "resetLog", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String resetLog(String copyId) { - NodeProjectInfoModel pim = getProjectInfoModel(); - NodeProjectInfoModel.JavaCopyItem copyItem = pim.findCopyItem(copyId); - try { - String msg = AbstractProjectCommander.getInstance().backLog(pim, copyItem); - if (msg.contains("ok")) { - return JsonMessage.getString(200, "重置成功"); - } - return JsonMessage.getString(201, "重置失败:" + msg); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - return JsonMessage.getString(500, "重置日志失败"); - } - } - - @RequestMapping(value = "logBack_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String clear(String name, String copyId) { - name = pathSafe(name); - if (StrUtil.isEmpty(name)) { - return JsonMessage.getString(405, "非法操作:" + name); - } - NodeProjectInfoModel pim = getProjectInfoModel(); - NodeProjectInfoModel.JavaCopyItem copyItem = pim.findCopyItem(copyId); - File logBack = copyItem == null ? pim.getLogBack() : pim.getLogBack(copyItem); - if (logBack.exists() && logBack.isDirectory()) { - logBack = FileUtil.file(logBack, name); - if (logBack.exists()) { - FileUtil.del(logBack); - return JsonMessage.getString(200, "删除成功"); - } - return JsonMessage.getString(500, "没有对应文件"); - } else { - return JsonMessage.getString(500, "没有对应文件夹"); - } - } - - @RequestMapping(value = "logBack_download", method = RequestMethod.GET) - public String download(String key, String copyId) { - key = pathSafe(key); - if (StrUtil.isEmpty(key)) { - return JsonMessage.getString(405, "非法操作"); - } - try { - NodeProjectInfoModel pim = getProjectInfoModel(); - NodeProjectInfoModel.JavaCopyItem copyItem = pim.findCopyItem(copyId); - File logBack = copyItem == null ? pim.getLogBack() : pim.getLogBack(copyItem); - if (logBack.exists() && logBack.isDirectory()) { - logBack = FileUtil.file(logBack, key); - ServletUtil.write(getResponse(), logBack); - } else { - return "没有对应文件"; - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("下载文件异常", e); - } - return "下载失败。请刷新页面后重试"; - } - - @RequestMapping(value = "logBack", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String console(String copyId) { - // 查询项目路径 - NodeProjectInfoModel pim = getProjectInfoModel(); - NodeProjectInfoModel.JavaCopyItem copyItem = pim.findCopyItem(copyId); - JSONObject jsonObject = new JSONObject(); - - File logBack = copyItem == null ? pim.getLogBack() : pim.getLogBack(copyItem); - if (logBack.exists() && logBack.isDirectory()) { - File[] filesAll = logBack.listFiles(); - if (filesAll != null) { - JSONArray jsonArray = FileUtils.parseInfo(filesAll, true, null); - jsonObject.put("array", jsonArray); - } - } - jsonObject.put("id", pim.getId()); - jsonObject.put("logPath", copyItem == null ? pim.getLog() : pim.getLog(copyItem)); - jsonObject.put("logBackPath", logBack.getAbsolutePath()); - return JsonMessage.getString(200, "", jsonObject); - } - - @RequestMapping(value = "export.html", method = RequestMethod.GET) - @ResponseBody - public String export(String copyId) { - NodeProjectInfoModel pim = getProjectInfoModel(); - NodeProjectInfoModel.JavaCopyItem copyItem = pim.findCopyItem(copyId); - File file = copyItem == null ? new File(pim.getLog()) : pim.getLog(copyItem); - if (!file.exists()) { - return JsonMessage.getString(400, "没有日志文件:" + file.getPath()); - } - HttpServletResponse response = getResponse(); - ServletUtil.write(response, file); - return JsonMessage.getString(200, ""); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/monitor/InternalController.java b/modules/agent/src/main/java/io/jpom/controller/monitor/InternalController.java deleted file mode 100644 index bd670052de..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/monitor/InternalController.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.monitor; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.system.AgentConfigBean; -import io.jpom.util.CommandUtil; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletResponse; -import java.io.File; - -/** - * 内存查看 - * - * @author Administrator - */ -@RestController -@RequestMapping(value = "/manage/") -public class InternalController extends BaseAgentController { - - /** - * 获取内存信息 - * - * @param tag 程序运行标识 - * @return json - * @throws Exception 异常 - */ - @RequestMapping(value = "internal_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getInternal(String tag, String copyId) throws Exception { -// String tagId = ProjectInfoModel.JavaCopyItem.getTagId(tag, copyId); -// int pid = AbstractProjectCommander.getInstance().getPid(tagId); -// if (pid <= 0) { -// return JsonMessage.getString(400, ""); -// } -// JSONObject jsonObject = new JSONObject(); -// ProcessModel item = AbstractSystemCommander.getInstance().getPidInfo(pid); -// jsonObject.put("process", item); -// JSONObject beanMem = getBeanMem(tagId); -// jsonObject.put("beanMem", beanMem); -// //获取端口信息 -// List netstatModels = AbstractProjectCommander.getInstance().listNetstat(pid, false); -// jsonObject.put("netstat", netstatModels); -// return JsonMessage.getString(200, "", jsonObject); - return JsonMessage.getString(404, "功能已经下架"); - } - - /** - * 查询监控线程列表 - * - * @param tag 程序运行标识 - * @return json - * @throws Exception 异常 - */ - @RequestMapping(value = "threadInfos", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getThreadInfos(String tag, String copyId) throws Exception { -// int limit = getParameterInt("limit", 10); -// int page = getParameterInt("page", 1); -// -// ThreadMXBean bean = JvmUtil.getThreadMXBean(ProjectInfoModel.JavaCopyItem.getTagId(tag, copyId)); -// if (bean == null) { -// return JsonMessage.getString(400, "未获取到对应信息"); -// } -// //启用线程争用监视 -// bean.setThreadContentionMonitoringEnabled(true); -// ThreadInfo[] threadInfos = bean.dumpAllThreads(false, false); -// if (threadInfos == null || threadInfos.length <= 0) { -// return JsonMessage.getString(404, "没有获取到任何线程"); -// } -// -// JSONArray array = new JSONArray(); -// int index = PageUtil.getStart(page, limit); -// int len = limit + index; -// int length = threadInfos.length; -// if (len > length) { -// len = length; -// } -// for (int i = index; i < len; i++) { -// ThreadInfo threadInfo = threadInfos[i]; -// Thread.State threadState = threadInfo.getThreadState(); -// JSONObject object = new JSONObject(); -// object.put("id", threadInfo.getThreadId()); -// object.put("name", threadInfo.getThreadName()); -// object.put("status", threadState); -// object.put("waitedCount", threadInfo.getWaitedCount()); -// object.put("waitedTime", threadInfo.getWaitedTime()); -// object.put("blockedCount", threadInfo.getBlockedCount()); -// object.put("blockedTime", threadInfo.getBlockedTime()); -// object.put("isInNative", threadInfo.isInNative()); -// object.put("isSuspended", threadInfo.isSuspended()); -// array.add(object); -// } -// JSONObject object = new JSONObject(); -// object.put("count", length); -// object.put("data", array); -// return JsonMessage.getString(200, "", object); - return JsonMessage.getString(404, "功能已经下架"); - } - -// /** -// * 获取jvm内存 -// * -// * @param tag tag -// * @return JSONObject -// */ -// private JSONObject getBeanMem(String tag) { -// try { -// MemoryMXBean bean = JvmUtil.getMemoryMXBean(tag); -// if (bean == null) { -// return null; -// } -// JSONObject jsonObject = new JSONObject(); -// jsonObject.put("mount", bean.getObjectPendingFinalizationCount()); -// //堆内存 -// MemoryUsage memory = bean.getHeapMemoryUsage(); -// //非堆内存 -// MemoryUsage nonHeapMemoryUsage = bean.getNonHeapMemoryUsage(); -// long used = memory.getUsed(); -// long max = memory.getMax(); -// //未定义 -// if (-1 == max) { -// max = memory.getCommitted(); -// } -// //计算使用内存占最大内存的百分比 -// double v = new BigDecimal(used).divide(new BigDecimal(max), 2, BigDecimal.ROUND_HALF_UP).doubleValue() * 100; -// jsonObject.put("heapUsed", FileUtil.readableFileSize(used)); -// jsonObject.put("heapProportion", String.format("%.2f", v) + "%"); -// jsonObject.put("heapCommitted", FileUtil.readableFileSize(memory.getCommitted())); -// long nonUsed = nonHeapMemoryUsage.getUsed(); -// long nonMax = nonHeapMemoryUsage.getMax(); -// long nonCommitted = nonHeapMemoryUsage.getCommitted(); -// if (-1 == nonMax) { -// nonMax = nonCommitted; -// } -// jsonObject.put("nonHeapUsed", FileUtil.readableFileSize(nonUsed)); -// double proportion = new BigDecimal(nonUsed).divide(new BigDecimal(nonMax), 2, BigDecimal.ROUND_HALF_UP).doubleValue() * 100; -// jsonObject.put("nonHeapProportion", String.format("%.2f", proportion) + "%"); -// jsonObject.put("nonHeapCommitted", FileUtil.readableFileSize(nonCommitted)); -// return jsonObject; -// } catch (Exception e) { -// DefaultSystemLog.getLog().error(e.getMessage(), e); -// } -// return null; -// } - - - /** - * 导出堆栈信息 - * - * @param tag 程序运行标识 - * @return json - */ - @RequestMapping(value = "internal_stack", method = RequestMethod.GET) - @ResponseBody - public String stack(String tag, String copyId) { - tag = NodeProjectInfoModel.JavaCopyItem.getTagId(tag, copyId); - // - String fileName = AgentConfigBean.getInstance().getTempPathName() + StrUtil.SLASH + tag + "_java_cpu.txt"; - fileName = FileUtil.normalize(fileName); - try { - int pid = AbstractProjectCommander.getInstance().getPid(tag); - if (pid <= 0) { - return JsonMessage.getString(400, "未运行"); - } - String command = String.format("jstack -F %s >> %s ", pid, fileName); - CommandUtil.execSystemCommand(command); - downLoad(getResponse(), fileName); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); -// getResponse().sendRedirect("internal?tag=" + tag); - } - return JsonMessage.getString(200, ""); - } - - /** - * 导出内存信息 - * - * @param tag 程序运行标识 - * @return json - */ - @RequestMapping(value = "internal_ram", method = RequestMethod.GET) - @ResponseBody - public String ram(String tag, String copyId) { - tag = NodeProjectInfoModel.JavaCopyItem.getTagId(tag, copyId); - // - String fileName = AgentConfigBean.getInstance().getTempPathName() + StrUtil.SLASH + tag + "_java_ram.txt"; - fileName = FileUtil.normalize(fileName); - try { - int pid = AbstractProjectCommander.getInstance().getPid(tag); - if (pid <= 0) { - return JsonMessage.getString(400, "未运行"); - } - String command = String.format("jmap -histo:live %s >> %s", pid, fileName); - CommandUtil.execSystemCommand(command); - downLoad(getResponse(), fileName); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); -// getResponse().sendRedirect("internal?tag=" + tag); - } - return JsonMessage.getString(200, ""); - } - - /** - * 下载文件 - * - * @param response response - * @param fileName 文件名字 - */ - private void downLoad(HttpServletResponse response, String fileName) { - //获取项目根路径 - File file = new File(fileName); - ServletUtil.write(response, file); - FileUtil.del(file); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/script/ScriptController.java b/modules/agent/src/main/java/io/jpom/controller/script/ScriptController.java deleted file mode 100644 index d7bec3fe7a..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/script/ScriptController.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.script; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HtmlUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import io.jpom.JpomApplication; -import io.jpom.common.BaseAgentController; -import io.jpom.model.data.ScriptModel; -import io.jpom.service.script.ScriptServer; -import io.jpom.system.AgentConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; - -/** - * 脚本管理 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -@RestController -@RequestMapping(value = "/script") -public class ScriptController extends BaseAgentController { - @Resource - private ScriptServer scriptServer; - - @RequestMapping(value = "list.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String list() { - return JsonMessage.getString(200, "", scriptServer.list()); - } - - @RequestMapping(value = "item.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String item(String id) { - return JsonMessage.getString(200, "", scriptServer.getItem(id)); - } - - @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String save(ScriptModel scriptModel, String type) { - if (scriptModel == null) { - return JsonMessage.getString(405, "没有数据"); - } - if (StrUtil.isEmpty(scriptModel.getContext())) { - return JsonMessage.getString(405, "内容为空"); - } - // - scriptModel.setContext(HtmlUtil.unescape(scriptModel.getContext())); - ScriptModel eModel = scriptServer.getItem(scriptModel.getId()); - if ("add".equalsIgnoreCase(type)) { - if (eModel != null) { - return JsonMessage.getString(405, "id已经存在啦"); - } - scriptModel.setId(IdUtil.fastSimpleUUID()); - File file = scriptModel.getFile(true); - if (file.exists() || file.isDirectory()) { - return JsonMessage.getString(405, "当地id路径文件已经存在来,请修改"); - } - scriptServer.addItem(scriptModel); - return JsonMessage.getString(200, "添加成功"); - } - if (eModel == null) { - return JsonMessage.getString(405, "对应数据不存在"); - } - eModel.setName(scriptModel.getName()); - eModel.setContext(scriptModel.getContext()); - scriptServer.updateItem(eModel); - return JsonMessage.getString(200, "修改成功"); - } - - @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String del(String id) { - scriptServer.deleteItem(id); - return JsonMessage.getString(200, "删除成功"); - } - - @RequestMapping(value = "upload.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String upload() throws IOException { - MultipartFileBuilder multipartFileBuilder = createMultipart() - .addFieldName("file").setFileExt("bat", "sh"); - multipartFileBuilder.setSavePath(AgentConfigBean.getInstance().getTempPathName()); - multipartFileBuilder.setUseOriginalFilename(true); - String path = multipartFileBuilder.save(); - File file = FileUtil.file(path); - String context = FileUtil.readString(path, JpomApplication.getCharset()); - if (StrUtil.isEmpty(context)) { - return JsonMessage.getString(405, "脚本内容为空"); - } - String id = file.getName(); - ScriptModel eModel = scriptServer.getItem(id); - if (eModel != null) { - return JsonMessage.getString(405, "对应脚本模板已经存在啦"); - } - eModel = new ScriptModel(); - eModel.setId(id); - eModel.setName(id); - eModel.setContext(context); - file = eModel.getFile(true); - if (file.exists() || file.isDirectory()) { - return JsonMessage.getString(405, "当地id路径文件已经存在来,请修改"); - } - scriptServer.addItem(eModel); - return JsonMessage.getString(200, "导入成功"); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/AgentCacheManageController.java b/modules/agent/src/main/java/io/jpom/controller/system/AgentCacheManageController.java deleted file mode 100644 index b078987484..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/AgentCacheManageController.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.socket.AgentFileTailWatcher; -import io.jpom.system.ConfigBean; -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.io.File; - -/** - * 缓存管理 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@RestController -@RequestMapping(value = "system") -public class AgentCacheManageController extends BaseAgentController { - - /** - * 缓存信息 - * - * @return json - */ - @RequestMapping(value = "cache", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String cache() { - JSONObject jsonObject = new JSONObject(); - // - File file = ConfigBean.getInstance().getTempPath(); - String fileSize = FileUtil.readableFileSize(FileUtil.size(file)); - jsonObject.put("fileSize", fileSize); - // - jsonObject.put("pidName", AbstractProjectCommander.PID_JPOM_NAME.size()); - jsonObject.put("pidPort", AbstractProjectCommander.PID_PORT.size()); - - int oneLineCount = AgentFileTailWatcher.getOneLineCount(); - jsonObject.put("readFileOnLineCount", oneLineCount); - // - return JsonMessage.getString(200, "ok", jsonObject); - } - - /** - * 清空缓存 - * - * @param type 缓存类型 - * @return json - */ - @RequestMapping(value = "clearCache", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String clearCache(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "类型错误") String type) { - switch (type) { - case "pidPort": - AbstractProjectCommander.PID_PORT.clear(); - break; - case "pidName": - AbstractProjectCommander.PID_JPOM_NAME.clear(); - break; - case "fileSize": - boolean clean = FileUtil.clean(ConfigBean.getInstance().getTempPath()); - if (!clean) { - return JsonMessage.getString(504, "清空文件缓存失败"); - } - break; - default: - return JsonMessage.getString(405, "没有对应类型:" + type); - - } - return JsonMessage.getString(200, "清空成功"); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/CertificateController.java b/modules/agent/src/main/java/io/jpom/controller/system/CertificateController.java deleted file mode 100644 index 7024ae5006..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/CertificateController.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.ZipUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.model.data.CertModel; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.service.system.CertService; -import io.jpom.system.AgentConfigBean; -import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Enumeration; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * 证书管理 - * - * @author Arno - */ -@RestController -@RequestMapping(value = "/system/certificate") -public class CertificateController extends BaseAgentController { - - @Resource - private CertService certService; - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - - - /** - * 保存证书 - * - * @return json - */ - @RequestMapping(value = "/saveCertificate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String saveCertificate() { - String data = getParameter("data"); - JSONObject jsonObject = JSONObject.parseObject(data); - String type = jsonObject.getString("type"); - String id = jsonObject.getString("id"); - try { - CertModel certModel; - if ("add".equalsIgnoreCase(type)) { - if (certService.getItem(id) != null) { - return JsonMessage.getString(405, "证书id已经存在啦"); - } - certModel = new CertModel(); - String error = getCertModel(certModel, jsonObject); - if (error != null) { - return error; - } - if (!hasFile()) { - return JsonMessage.getString(405, "请选择证书包文件"); - } - error = getCertFile(certModel, true); - if (error != null) { - return error; - } - certService.addItem(certModel); - } else { - certModel = certService.getItem(id); - if (certModel == null) { - return JsonMessage.getString(404, "没有找到对应证书文件"); - } - String name = jsonObject.getString("name"); - if (StrUtil.isEmpty(name)) { - return JsonMessage.getString(400, "请填写证书名称"); - } - certModel.setName(name); - if (ServletFileUpload.isMultipartContent(getRequest()) && hasFile()) { - String error = getCertFile(certModel, false); - if (error != null) { - return error; - } - } - certService.updateItem(certModel); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("证书文件", e); - return JsonMessage.getString(400, "解析证书文件失败:" + e.getMessage()); - } - return JsonMessage.getString(200, "提交成功"); - } - - - /** - * 获取证书信息 - * - * @param certModel 实体 - * @param jsonObject json对象 - * @return 错误消息 - */ - private String getCertModel(CertModel certModel, JSONObject jsonObject) { - String id = jsonObject.getString("id"); - String path = jsonObject.getString("path"); - String name = jsonObject.getString("name"); - if (StrUtil.isEmpty(id)) { - return JsonMessage.getString(400, "请填写证书id"); - } - if (Validator.isChinese(id)) { - return JsonMessage.getString(400, "证书id不能使用中文"); - } - if (StrUtil.isEmpty(name)) { - return JsonMessage.getString(400, "请填写证书名称"); - } - if (!whitelistDirectoryService.checkCertificateDirectory(path)) { - return JsonMessage.getString(400, "请选择正确的项目路径,或者还没有配置白名单"); - } - certModel.setId(id); - certModel.setWhitePath(path); - certModel.setName(name); - return null; - } - - private Object getUpdateFileInfo(CertModel certModel, String certPath) throws IOException { - String pemPath = null, keyPath = null; - String path = AgentConfigBean.getInstance().getTempPathName(); - try (ZipFile zipFile = new ZipFile(certPath)) { - Enumeration zipEntryEnumeration = zipFile.entries(); - while (zipEntryEnumeration.hasMoreElements()) { - ZipEntry zipEntry = zipEntryEnumeration.nextElement(); - if (zipEntry.isDirectory()) { - continue; - } - String keyName = zipEntry.getName(); - // pem、cer、crt - if (pemPath == null && (StrUtil.endWith(keyName, ".pem", true) || - StrUtil.endWith(keyName, ".cer", true) || - StrUtil.endWith(keyName, ".crt", true))) { - String eNmae = FileUtil.extName(keyName); - CertModel.Type type = CertModel.Type.valueOf(eNmae.toLowerCase()); - String filePathItem = String.format("%s/%s/%s", path, certModel.getId(), keyName); - InputStream inputStream = zipFile.getInputStream(zipEntry); - FileUtil.writeFromStream(inputStream, filePathItem); - certModel.setType(type); - pemPath = filePathItem; - } - // - if (keyPath == null && StrUtil.endWith(keyName, ".key", true)) { - String filePathItem = String.format("%s/%s/%s", path, certModel.getId(), keyName); - InputStream inputStream = zipFile.getInputStream(zipEntry); - FileUtil.writeFromStream(inputStream, filePathItem); - keyPath = filePathItem; - } - if (pemPath != null && keyPath != null) { - break; - } - } - if (pemPath == null || keyPath == null) { - return JsonMessage.getString(405, "证书包中文件不完整,需要包含key、pem"); - } - JSONObject jsonObject = CertModel.decodeCert(pemPath, keyPath); - if (jsonObject == null) { - return JsonMessage.getString(405, "解析证书失败"); - } - return jsonObject; - } - } - - private String getCertFile(CertModel certModel, boolean add) throws IOException { - String certPath = null; - try { - String path = AgentConfigBean.getInstance().getTempPathName(); - MultipartFileBuilder cert = createMultipart().addFieldName("file").setSavePath(path); - certPath = cert.save(); - Object val = getUpdateFileInfo(certModel, certPath); - if (val instanceof String) { - return val.toString(); - } - JSONObject jsonObject = (JSONObject) val; - String domain = jsonObject.getString("domain"); - if (add) { - List array = certService.list(); - if (array != null) { - for (CertModel certModel1 : array) { - if (StrUtil.emptyToDefault(domain, "").equals(certModel1.getDomain())) { - return JsonMessage.getString(405, "证书的域名已经存在啦"); - } - } - } - } else { - if (!StrUtil.emptyToDefault(domain, "").equals(certModel.getDomain())) { - return JsonMessage.getString(405, "新证书的域名不一致"); - } - } - // 移动位置 - String temporary = certModel.getWhitePath() + StrUtil.SLASH + certModel.getId() + StrUtil.SLASH; - File pemFile = FileUtil.file(temporary + certModel.getId() + "." + certModel.getType().name()); - File keyFile = FileUtil.file(temporary + certModel.getId() + ".key"); - if (add) { - if (pemFile.exists()) { - return JsonMessage.getString(405, pemFile.getAbsolutePath() + " 已经被占用啦"); - } - if (keyFile.exists()) { - return JsonMessage.getString(405, keyFile.getAbsolutePath() + " 已经被占用啦"); - } - } - String pemPath = jsonObject.getString("pemPath"); - String keyPath = jsonObject.getString("keyPath"); - FileUtil.move(FileUtil.file(pemPath), pemFile, true); - FileUtil.move(FileUtil.file(keyPath), keyFile, true); - certModel.setCert(pemFile.getAbsolutePath()); - certModel.setKey(keyFile.getAbsolutePath()); - // - certModel.setDomain(domain); - certModel.setExpirationTime(jsonObject.getLongValue("expirationTime")); - certModel.setEffectiveTime(jsonObject.getLongValue("effectiveTime")); - } finally { - if (certPath != null) { - FileUtil.del(certPath); - } - } - return null; - } - - - /** - * 证书列表 - * - * @return json - */ - @RequestMapping(value = "/getCertList", produces = MediaType.APPLICATION_JSON_VALUE) - public String getCertList() { - List array = certService.list(); - return JsonMessage.getString(200, "", array); - } - - /** - * 删除证书 - * - * @param id id - * @return json - */ - @RequestMapping(value = "/delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String delete(String id) { - if (StrUtil.isEmpty(id)) { - return JsonMessage.getString(400, "删除失败"); - } - certService.deleteItem(id); - return JsonMessage.getString(200, "删除成功"); - } - - - /** - * 导出证书 - * - * @param id 项目id - * @return 结果 - */ - @RequestMapping(value = "/export", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public String export(String id) { - CertModel item = certService.getItem(id); - if (null == item) { - return JsonMessage.getString(400, "导出失败"); - } - String parent = FileUtil.file(item.getCert()).getParent(); - File zip = ZipUtil.zip(parent); - ServletUtil.write(getResponse(), zip); - FileUtil.del(zip); - return JsonMessage.getString(400, "导出成功"); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/LogManageController.java b/modules/agent/src/main/java/io/jpom/controller/system/LogManageController.java deleted file mode 100644 index e897746f0a..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/LogManageController.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.BaseAgentController; -import io.jpom.system.WebAopLog; -import io.jpom.util.LayuiTreeUtil; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.util.concurrent.TimeUnit; - -/** - * 系统日志管理 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@RestController -@RequestMapping(value = "system") -public class LogManageController extends BaseAgentController { - - - @RequestMapping(value = "log_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String logData() { - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - JSONArray data = LayuiTreeUtil.getTreeData(webAopLog.getPropertyValue()); - return JsonMessage.getString(200, "", data); - } - - - @RequestMapping(value = "log_del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String logData(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "path错误") String path) { - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - File file = FileUtil.file(webAopLog.getPropertyValue(), path); - // 判断修改时间 - long modified = file.lastModified(); - if (System.currentTimeMillis() - modified < TimeUnit.DAYS.toMillis(1)) { - return JsonMessage.getString(405, "不能删除当天的日志"); - } - if (FileUtil.del(file)) { - return JsonMessage.getString(200, "删除成功"); - } - return JsonMessage.getString(500, "删除失败"); - } - - - @RequestMapping(value = "log_download", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public void logDownload(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "path错误") String path) { - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - File file = FileUtil.file(webAopLog.getPropertyValue(), path); - if (file.isFile()) { - ServletUtil.write(getResponse(), file); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/NginxController.java b/modules/agent/src/main/java/io/jpom/controller/system/NginxController.java deleted file mode 100644 index 6c2da87c3e..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/NginxController.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.github.odiszapc.nginxparser.NgxBlock; -import com.github.odiszapc.nginxparser.NgxConfig; -import com.github.odiszapc.nginxparser.NgxEntry; -import com.github.odiszapc.nginxparser.NgxParam; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractSystemCommander; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.service.system.NginxService; -import io.jpom.util.CommandUtil; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -/** - * nginx 列表 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@RestController -@RequestMapping("/system/nginx") -public class NginxController extends BaseAgentController { - - @Resource - private NginxService nginxService; - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - - /** - * 配置列表 - * - * @return json - */ - @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String list(String whitePath, String name) { - boolean checkNgxDirectory = whitelistDirectoryService.checkNgxDirectory(whitePath); - Assert.state(checkNgxDirectory, "文件路径错误,非白名单路径"); - if (StrUtil.isEmpty(name)) { - name = StrUtil.SLASH; - } - String newName = pathSafe(name); - JSONArray array = nginxService.list(whitePath, newName); - return JsonMessage.getString(200, "", array); - } - - /** - * nginx列表 - */ - @RequestMapping(value = "tree.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String tree() { - JSONArray array = nginxService.tree(); - return JsonMessage.getString(200, "", array); - } - - /** - * 获取配置文件信息页面 - * - * @param path 白名单路径 - * @param name 名称 - * @return 页面 - */ - @RequestMapping(value = "item_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String itemData(String path, String name) { - boolean ngxDirectory = whitelistDirectoryService.checkNgxDirectory(path); - Assert.state(ngxDirectory, "文件路径错误,非白名单路径"); - - File file = FileUtil.file(path, name); - Assert.state(FileUtil.isFile(file), "对应对配置文件不存在"); - JSONObject jsonObject = new JSONObject(); - String string = FileUtil.readUtf8String(file); - jsonObject.put("context", string); - String rName = StringUtil.delStartPath(file, path, true); - // nginxService.paresName(path, file.getAbsolutePath()) - jsonObject.put("name", rName); - jsonObject.put("whitePath", path); - return JsonMessage.getString(200, "", jsonObject); -// setAttribute("data", jsonObject); - } - - /** - * 新增或修改配置 - * - * @param name 文件名 - * @param whitePath 白名单路径 - * @param genre 操作类型 - * @return json - */ - @RequestMapping(value = "updateNgx", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String updateNgx(String name, String whitePath, String genre, String context) { - Assert.hasText(name, "请填写文件名"); - Assert.state(name.endsWith(".conf"), "文件后缀必须为\".conf\""); - // - boolean ngxDirectory = whitelistDirectoryService.checkNgxDirectory(whitePath); - Assert.state(ngxDirectory, "请选择正确的白名单"); - //nginx文件 - File file = FileUtil.file(whitePath, name); - if ("add".equals(genre) && file.exists()) { - return JsonMessage.getString(400, "该文件已存在"); - } - //String context = getUnescapeParameter("context"); - if (StrUtil.isEmpty(context)) { - return JsonMessage.getString(400, "请填写配置信息"); - } - InputStream inputStream = new ByteArrayInputStream(context.getBytes()); - try { - NgxConfig conf = NgxConfig.read(inputStream); - List list = conf.findAll(NgxBlock.class, "server"); - // 取消 nginx 内容必须检测 @jzy 2021-09-11 - // if (list == null || list.size() <= 0) { - // return JsonMessage.getString(404, "内容解析为空"); - // } - for (NgxEntry ngxEntry : list) { - NgxBlock ngxBlock = (NgxBlock) ngxEntry; - // 检查日志路径 - NgxParam accessLog = ngxBlock.findParam("access_log"); - if (accessLog != null) { - FileUtil.mkParentDirs(accessLog.getValue()); - } - accessLog = ngxBlock.findParam("error_log"); - if (accessLog != null) { - FileUtil.mkParentDirs(accessLog.getValue()); - } - // 检查证书文件 - NgxParam sslCertificate = ngxBlock.findParam("ssl_certificate"); - if (sslCertificate != null && !FileUtil.exist(sslCertificate.getValue())) { - return JsonMessage.getString(404, "证书文件ssl_certificate,不存在"); - } - NgxParam sslCertificateKey = ngxBlock.findParam("ssl_certificate_key"); - if (sslCertificateKey != null && !FileUtil.exist(sslCertificateKey.getValue())) { - return JsonMessage.getString(404, "证书文件ssl_certificate_key,不存在"); - } - if (!checkRootRole(ngxBlock)) { - return JsonMessage.getString(405, "非系统管理员,不能配置静态资源代理"); - } - } - } catch (IOException e) { - DefaultSystemLog.getLog().error("解析失败", e); - return JsonMessage.getString(500, "解析失败"); - } - try { - FileUtil.writeString(context, file, CharsetUtil.UTF_8); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - return JsonMessage.getString(400, "操作失败:" + e.getMessage()); - } - String msg = this.reloadNginx(); - return JsonMessage.getString(200, "提交成功" + msg); - } - - private String reloadNginx() { - String serviceName = nginxService.getServiceName(); - try { - String format = StrUtil.format("{} -s reload", serviceName); - String msg = CommandUtil.execSystemCommand(format); - if (StrUtil.isNotEmpty(msg)) { - DefaultSystemLog.getLog().info(msg); - return "(" + msg + ")"; - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("reload nginx error", e); - } - return StrUtil.EMPTY; - } - - /** - * 权限检查 防止非系统管理员配置静态资源访问 - * - * @param ngxBlock 代码片段 - * @return false 不正确 - */ - private boolean checkRootRole(NgxBlock ngxBlock) { -// UserModel userModel = getUser(); - // List locationAll = ngxBlock.findAll(NgxBlock.class, "location"); - // if (locationAll != null) { - // for (NgxEntry ngxEntry1 : locationAll) { - // NgxBlock ngxBlock1 = (NgxBlock) ngxEntry1; - // NgxParam locationMain = ngxBlock1.findParam("root"); - // if (locationMain == null) { - // locationMain = ngxBlock1.findParam("alias"); - // } - // - // } - // } - return true; - } - - /** - * 删除配置 - * - * @param path 文件路径 - * @param name 文件名 - * @return json - */ - @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String delete(String path, String name) { - if (!whitelistDirectoryService.checkNgxDirectory(path)) { - return JsonMessage.getString(400, "非法操作"); - } - String safePath = pathSafe(path); - String safeName = pathSafe(name); - if (StrUtil.isEmpty(safeName)) { - return JsonMessage.getString(400, "删除失败,请正常操作"); - } - File file = FileUtil.file(safePath, safeName); - try { - FileUtil.rename(file, file.getName() + "_back", false, true); - } catch (Exception e) { - DefaultSystemLog.getLog().error("删除nginx", e); - return JsonMessage.getString(400, "删除失败:" + e.getMessage()); - } - String msg = this.reloadNginx(); - return JsonMessage.getString(200, "删除成功" + msg); - } - - /** - * 获取nginx状态 - * - * @return json - */ - @RequestMapping(value = "status", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String status() { - String name = nginxService.getServiceName(); - if (StrUtil.isEmpty(name)) { - return JsonMessage.getString(500, "服务名错误"); - } - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", name); - boolean serviceStatus = AbstractSystemCommander.getInstance().getServiceStatus(name); - jsonObject.put("status", serviceStatus); - return JsonMessage.getString(200, "", jsonObject); - } - - /** - * 修改nginx配置 - * - * @param name 服务名 - * @return json - */ - @RequestMapping(value = "updateConf", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String updateConf(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "服务名称错误") String name) { - JSONObject ngxConf = nginxService.getNgxConf(); - ngxConf.put("name", name); - nginxService.save(ngxConf); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 获取配置信息 - * - * @return json - */ - @RequestMapping(value = "config", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String config() { - JSONObject ngxConf = nginxService.getNgxConf(); - return JsonMessage.getString(200, "", ngxConf); - } - - /** - * 启动nginx - * - * @return json - */ - @RequestMapping(value = "open", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String open() { - String name = nginxService.getServiceName(); - String result = AbstractSystemCommander.getInstance().startService(name); - return JsonMessage.getString(200, "nginx服务已启动 " + result); - } - - /** - * 关闭nginx - * - * @return json - */ - @RequestMapping(value = "close", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String close() { - String name = nginxService.getServiceName(); - String result = AbstractSystemCommander.getInstance().stopService(name); - return JsonMessage.getString(200, result); - } - - /** - * 重新加载 - * - * @return json - */ - @RequestMapping(value = "reload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String reload() { - String name = nginxService.getServiceName(); - String checkResult = StrUtil.EMPTY; - if (SystemUtil.getOsInfo().isLinux()) { - checkResult = CommandUtil.execSystemCommand(StrUtil.format("{} -t", name)); - } else if (SystemUtil.getOsInfo().isWindows()) { - checkResult = CommandUtil.execSystemCommand("sc qc " + name); - List strings = StrSplitter.splitTrim(checkResult, "\n", true); - //服务路径 - File file = null; - for (String str : strings) { - str = str.toUpperCase().trim(); - if (str.startsWith("BINARY_PATH_NAME")) { - String path = str.substring(str.indexOf(":") + 1).replace("\"", "").trim(); - file = FileUtil.file(path).getParentFile(); - break; - } - } - checkResult = CommandUtil.execSystemCommand("nginx -t", file); - } - if (StrUtil.isNotEmpty(checkResult) && !StrUtil.containsIgnoreCase(checkResult, "successful")) { - return JsonMessage.getString(400, checkResult); - } - String reloadMsg = this.reloadNginx(); - return JsonMessage.getString(200, "重新加载成功:" + reloadMsg, checkResult); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/SystemConfigController.java b/modules/agent/src/main/java/io/jpom/controller/system/SystemConfigController.java deleted file mode 100644 index 938d31489a..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/SystemConfigController.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.common.BaseAgentController; -import io.jpom.common.Const; -import io.jpom.common.JpomManifest; -import io.jpom.system.ExtConfigBean; -import org.springframework.boot.env.YamlPropertySourceLoader; -import org.springframework.core.io.ByteArrayResource; -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.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 系统配置 - * - * @author bwcx_jzy - * @date 2019/08/08 - */ -@RestController -@RequestMapping(value = "system") -public class SystemConfigController extends BaseAgentController { - - @RequestMapping(value = "getConfig.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String config() throws IOException { - String content = IoUtil.read(ExtConfigBean.getResource().getInputStream(), CharsetUtil.CHARSET_UTF_8); - JSONObject json = new JSONObject(); - json.put("content", content); - return JsonMessage.getString(200, "ok", json); - } - - @RequestMapping(value = "save_config.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String saveConfig(String content, String restart) { - if (StrUtil.isEmpty(content)) { - return JsonMessage.getString(405, "内容不能为空"); - } - try { - YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); - // @author hjk 前端编辑器允许使用tab键,并设定为2个空格,再转换为yml时要把tab键换成2个空格 - ByteArrayResource resource = new ByteArrayResource(content.replace("\t", " ").getBytes(StandardCharsets.UTF_8)); - yamlPropertySourceLoader.load("test", resource); - } catch (Exception e) { - DefaultSystemLog.getLog().warn("内容格式错误,请检查修正", e); - return JsonMessage.getString(500, "内容格式错误,请检查修正:" + e.getMessage()); - } - if (JpomManifest.getInstance().isDebug()) { - return JsonMessage.getString(405, "调试模式下不支持在线修改,请到resources目录下的bin目录修改extConfig.yml"); - } - File resourceFile = ExtConfigBean.getResourceFile(); - FileUtil.writeString(content, resourceFile, CharsetUtil.CHARSET_UTF_8); - - if (Convert.toBool(restart, false)) { - // 重启 - ThreadUtil.execute(() -> { - ThreadUtil.sleep(2000); - JpomApplication.restart(); - }); - return JsonMessage.getString(200, Const.UPGRADE_MSG); - } - return JsonMessage.getString(200, "修改成功"); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/SystemUpdateController.java b/modules/agent/src/main/java/io/jpom/controller/system/SystemUpdateController.java deleted file mode 100644 index 39f359bc69..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/SystemUpdateController.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import io.jpom.JpomApplication; -import io.jpom.common.*; -import io.jpom.system.AgentConfigBean; -import io.jpom.system.ConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Objects; - -/** - * 在线升级 - * - * @author bwcx_jzy - * @date 2019/7/22 - */ -@RestController -@RequestMapping(value = "system") -public class SystemUpdateController extends BaseAgentController { - - @PostMapping(value = "uploadJar.json", produces = MediaType.APPLICATION_JSON_VALUE) - public String uploadJar() throws IOException { - // - Objects.requireNonNull(JpomManifest.getScriptFile()); - MultipartFileBuilder multipartFileBuilder = createMultipart(); - String absolutePath = AgentConfigBean.getInstance().getTempPath().getAbsolutePath(); - multipartFileBuilder - .setFileExt("jar", "zip") - .addFieldName("file") - .setUseOriginalFilename(true) - .setSavePath(absolutePath); - String path = multipartFileBuilder.save(); - // 解析压缩包 - File file = JpomManifest.zipFileFind(path, Type.Agent, absolutePath); - path = FileUtil.getAbsolutePath(file); - // 基础检查 - JsonMessage error = JpomManifest.checkJpomJar(path, Type.Agent); - if (error.getCode() != HttpStatus.HTTP_OK) { - return error.toString(); - } - String version = error.getMsg(); - JpomManifest.releaseJar(path, version); - // - JpomApplication.restart(); - return JsonMessage.getString(200, Const.UPGRADE_MSG); - } - - @PostMapping(value = "change_log", produces = MediaType.APPLICATION_JSON_VALUE) - public String changeLog() { - // - URL resource = ResourceUtil.getResource("CHANGELOG.md"); - String log = StrUtil.EMPTY; - if (resource != null) { - InputStream stream = URLUtil.getStream(resource); - log = IoUtil.readUtf8(stream); - } - return JsonMessage.getString(200, "", log); - } - - /** - * 检查是否存在新版本 - * - * @return json - * @see RemoteVersion - */ - @PostMapping(value = "check_version.json", produces = MediaType.APPLICATION_JSON_VALUE) - public String checkVersion() { - RemoteVersion remoteVersion = RemoteVersion.loadRemoteInfo(); - return JsonMessage.getString(200, "", remoteVersion); - } - - /** - * 远程下载升级 - * - * @return json - * @see RemoteVersion - */ - @PostMapping(value = "remote_upgrade.json", produces = MediaType.APPLICATION_JSON_VALUE) - public String upgrade() throws IOException { - RemoteVersion.upgrade(ConfigBean.getInstance().getTempPath().getAbsolutePath()); - return JsonMessage.getString(200, Const.UPGRADE_MSG); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/system/WhitelistDirectoryController.java b/modules/agent/src/main/java/io/jpom/controller/system/WhitelistDirectoryController.java deleted file mode 100644 index 14d97b6317..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/system/WhitelistDirectoryController.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.RegexPool; -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.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseJpomController; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.system.AgentExtConfigBean; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.util.List; - -/** - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -@RequestMapping(value = "/system") -public class WhitelistDirectoryController extends BaseJpomController { - - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - - @RequestMapping(value = "whitelistDirectory_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String whiteListDirectoryData() { - AgentWhitelist agentWhitelist = whitelistDirectoryService.getWhitelist(); - return JsonMessage.getString(200, "", agentWhitelist); - } - - - @RequestMapping(value = "whitelistDirectory_submit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String whitelistDirectorySubmit(String project, String certificate, String nginx, String allowEditSuffix, String allowRemoteDownloadHost) { - if (StrUtil.isEmpty(project)) { - return JsonMessage.getString(401, "项目路径白名单不能为空"); - } - List list = AgentWhitelist.parseToList(project, true, "项目路径白名单不能为空"); - // - List certificateList = AgentWhitelist.parseToList(certificate, "证书路径白名单不能为空"); - List nList = AgentWhitelist.parseToList(nginx, "nginx路径白名单不能为空"); - List allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "允许编辑的文件后缀不能为空"); - List allowRemoteDownloadHostList = AgentWhitelist.parseToList(allowRemoteDownloadHost, "允许远程下载的 host 不能配置为空"); - return save(list, certificateList, nList, allowEditSuffixList, allowRemoteDownloadHostList).toString(); - } -// -// private JsonMessage save(String project, List certificate, List nginx, List allowEditSuffixList) { -// -// return save(list, certificate, nginx); -// } - - - private JsonMessage save(List projects, - List certificate, - List nginx, - List allowEditSuffixList, - List allowRemoteDownloadHostList) { - List projectArray; - { - projectArray = AgentWhitelist.covertToArray(projects, "项目路径白名单不能位于Jpom目录下"); - - String error = findStartsWith(projectArray, 0); - if (error != null) { - return new JsonMessage<>(401, "白名单目录中不能存在包含关系:" + error); - } - } - List certificateArray = null; - if (certificate != null && !certificate.isEmpty()) { - certificateArray = AgentWhitelist.covertToArray(certificate, "证书路径白名单不能位于Jpom目录下"); - - String error = findStartsWith(certificateArray, 0); - if (error != null) { - return new JsonMessage<>(401, "证书目录中不能存在包含关系:" + error); - } - } - List nginxArray = null; - if (nginx != null && !nginx.isEmpty()) { - nginxArray = AgentWhitelist.covertToArray(nginx, "nginx路径白名单不能位于Jpom目录下"); - - String error = findStartsWith(nginxArray, 0); - if (error != null) { - return new JsonMessage<>(401, "nginx目录中不能存在包含关系:" + error); - } - } - // - if (CollUtil.isNotEmpty(allowEditSuffixList)) { - for (String s : allowEditSuffixList) { - List split = StrUtil.split(s, StrUtil.AT); - if (split.size() > 1) { - String last = CollUtil.getLast(split); - try { - CharsetUtil.charset(last); - } catch (Exception e) { - throw new IllegalArgumentException("配置的字符编码格式不合法:" + s); - } - } - } - } - if (CollUtil.isNotEmpty(allowRemoteDownloadHostList)) { - for (String s : allowRemoteDownloadHostList) { - Assert.state(ReUtil.isMatch(RegexPool.URL_HTTP, s), "配置的远程地址不规范,请重新填写:" + s); - } - } - - AgentWhitelist agentWhitelist = whitelistDirectoryService.getWhitelist(); - - agentWhitelist.setProject(projectArray); - agentWhitelist.setCertificate(certificateArray); - agentWhitelist.setNginx(nginxArray); - agentWhitelist.setAllowEditSuffix(allowEditSuffixList); - agentWhitelist.setAllowRemoteDownloadHost(allowRemoteDownloadHostList == null ? null : CollUtil.newHashSet(allowRemoteDownloadHostList)); - whitelistDirectoryService.saveWhitelistDirectory(agentWhitelist); - return new JsonMessage<>(200, "保存成功"); - } - - /** - * 检查白名单包含关系 - * - * @param jsonArray 要检查的对象 - * @param start 检查的坐标 - * @return null 正常 - */ - private String findStartsWith(List jsonArray, int start) { - if (jsonArray == null || !AgentExtConfigBean.getInstance().whitelistDirectoryCheckStartsWith) { - 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; - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatEditController.java b/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatEditController.java deleted file mode 100644 index 28207bf750..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatEditController.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.tomcat; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.crypto.SecureUtil; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseAgentController; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.service.manage.TomcatEditService; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Objects; - -/** - * tomcat 编辑 - * - * @author bwcx_jzy - * @date 2019/7/21 - */ -@RestController -@RequestMapping(value = "/tomcat/") -public class TomcatEditController extends BaseAgentController { - @Resource - private TomcatEditService tomcatEditService; - - - /** - * 列出所有的tomcat - * - * @return Tomcat列表 - */ - @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String list() { - // 查询tomcat列表 - List tomcatInfoModels = tomcatEditService.list(); - return JsonMessage.getString(200, "查询成功", tomcatInfoModels); - } - - /** - * 根据Id查询Tomcat信息 - * - * @param id Tomcat的主键 - * @return 操作结果 - */ - @RequestMapping(value = "getItem", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getItem(String id) { - // 查询tomcat列表 - return JsonMessage.getString(200, "查询成功", tomcatEditService.getItem(id)); - } - - - /** - * 添加Tomcat - * - * @param tomcatInfoModel Tomcat信息 - * @return 操作结果 - */ - @RequestMapping(value = "add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String add(TomcatInfoModel tomcatInfoModel) { - // 根据Tomcat名称查询tomcat是否已经存在 - String name = tomcatInfoModel.getName(); - TomcatInfoModel tomcatInfoModelTemp = tomcatEditService.getItemByName(name); - if (tomcatInfoModelTemp != null) { - return JsonMessage.getString(401, "名称已经存在,请使用其他名称!"); - } - tomcatInfoModel.setId(SecureUtil.md5(DateUtil.now())); - tomcatInfoModel.setCreator(getUserName()); - - // 设置tomcat路径,去除多余的符号 - tomcatInfoModel.setPath(FileUtil.normalize(tomcatInfoModel.getPath())); - Objects.requireNonNull(tomcatInfoModel.pathAndCheck()); - tomcatEditService.addItem(tomcatInfoModel); - tomcatInfoModel.initTomcat(); - return JsonMessage.getString(200, "保存成功"); - } - - - /** - * 修改Tomcat信息 - * - * @param tomcatInfoModel Tomcat信息 - * @return 操作结果 - */ - @RequestMapping(value = "update", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String update(TomcatInfoModel tomcatInfoModel) { - // 根据Tomcat名称查询tomcat是否已经存在 - String name = tomcatInfoModel.getName(); - TomcatInfoModel tomcatInfoModelTemp = tomcatEditService.getItemByName(name); - if (tomcatInfoModelTemp != null && !tomcatInfoModelTemp.getId().equals(tomcatInfoModel.getId())) { - return JsonMessage.getString(401, "名称已经存在,请使用其他名称!"); - } - - tomcatInfoModel.setModifyUser(getUserName()); - // 设置tomcat路径,去除多余的符号 - tomcatInfoModel.setPath(FileUtil.normalize(tomcatInfoModel.getPath())); - Objects.requireNonNull(tomcatInfoModel.pathAndCheck()); - tomcatEditService.updateItem(tomcatInfoModel); - tomcatInfoModel.initTomcat(); - return JsonMessage.getString(200, "修改成功"); - - } - - - /** - * 删除tomcat - * - * @param id tomcat id - * @return 操作结果 - */ - @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String delete(String id) { - tomcatEditService.deleteItem(id); - return JsonMessage.getString(200, "删除成功"); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatManageController.java b/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatManageController.java deleted file mode 100644 index f2fee83fb7..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatManageController.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.tomcat; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseAgentController; -import io.jpom.common.commander.AbstractTomcatCommander; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.service.manage.TomcatEditService; -import io.jpom.service.manage.TomcatManageService; -import io.jpom.socket.AgentFileTailWatcher; -import io.jpom.util.LayuiTreeUtil; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * @author lf - */ -@RestController -@RequestMapping(value = "/tomcat/") -public class TomcatManageController extends BaseAgentController { - - @Resource - private TomcatEditService tomcatEditService; - @Resource - private TomcatManageService tomcatManageService; - - /** - * 列出所有的tomcat项目列表 - * - * @param id 项目id - * @return Tomcat下的项目列表 - */ - @RequestMapping(value = "getTomcatProjectList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getTomcatProjectList(String id) { - JSONArray array = tomcatManageService.getTomcatProjectList(id); - return JsonMessage.getString(200, "查询成功", array); - } - - /** - * 查询tomcat状态 - * - * @param id tomcat的id - * @return tomcat运行状态 - */ - @RequestMapping(value = "getTomcatStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getStatus(String id) { - return JsonMessage.getString(200, "查询成功", tomcatManageService.getTomcatStatus(id)); - } - - - /** - * 启动tomcat - * - * @param id tomcat id - * @return 操作结果 - */ - @RequestMapping(value = "start", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String start(String id) { - // 查询tomcat信息 - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - - String result = AbstractTomcatCommander.getInstance().execCmd(tomcatInfoModel, "start"); - String msg = "启动成功"; - if ("stopped".equals(result)) { - msg = "启动失败"; - } - return JsonMessage.getString(200, msg, result); - } - - - /** - * 删除tomcat - * - * @param id tomcat id - * @return 操作结果 - */ - @RequestMapping(value = "stop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String stop(String id) { - // 查询tomcat信息 - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - - String result = AbstractTomcatCommander.getInstance().execCmd(tomcatInfoModel, "stop"); - String msg = "停止成功"; - if ("started".equals(result)) { - msg = "停止失败"; - } - return JsonMessage.getString(200, msg, result); - } - - /** - * 重启tomcat - * - * @param id tomcat id - * @return 操作结果 - */ - @RequestMapping(value = "restart", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String restart(String id) { - // 查询tomcat信息 - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - - String stopResult = AbstractTomcatCommander.getInstance().execCmd(tomcatInfoModel, "stop"); - String startResult = AbstractTomcatCommander.getInstance().execCmd(tomcatInfoModel, "start"); - return JsonMessage.getString(200, StrUtil.format("重启成功 {} {}", stopResult, startResult)); - } - - /** - * tomcat项目管理 - * - * @param id tomcat id - * @param path 项目路径 - * @param op 执行的操作 - * @return 操作结果 - */ - @RequestMapping(value = "tomcatProjectManage", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String tomcatProjectManage(String id, String path, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "操作项不对") String op) { - - TomcatOp tomcatOp = TomcatOp.valueOf(op); - return tomcatManageService.tomcatProjectManage(id, path, tomcatOp).toString(); - } - - /** - * 获取项目文件列表 - * - * @param id tomcat id - * @param path 项目路径 - * @return 文件列表 - */ - @RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getFileList(String id, String path) { - // 查询项目路径 - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - if (tomcatInfoModel == null) { - return JsonMessage.getString(500, "查询失败:项目不存在"); - } - if (StrUtil.isEmptyOrUndefined(path)) { - return JsonMessage.getString(500, "path value error"); - } - String appBasePath = tomcatInfoModel.getAppBase(); - File fileDir = FileUtil.file(appBasePath, FileUtil.normalize(path)); - if (!fileDir.exists()) { - return JsonMessage.getString(500, "目录不存在"); - } - File[] filesAll = fileDir.listFiles(); - if (filesAll == null) { - return JsonMessage.getString(500, "目录是空"); - } - - // JSONArray arrayFile = FileUtils.parseInfo(filesAll, false, appBasePath); - JSONArray arrayFile = new JSONArray(); - JSONArray arrayDir = new JSONArray(); - for (File file : filesAll) { - JSONObject jsonObject = new JSONObject(); - String parentPath = StringUtil.delStartPath(file, appBasePath, false); - jsonObject.put("parentPath", parentPath); - jsonObject.put("filename", file.getName()); - long mTime = file.lastModified(); - jsonObject.put("modifyTimeLong", mTime); - jsonObject.put("modifyTime", DateUtil.date(mTime).toString()); - - if (file.isDirectory()) { - jsonObject.put("isDirectory", true); - long sizeFile = FileUtil.size(file); - jsonObject.put("fileSize", FileUtil.readableFileSize(sizeFile)); - arrayDir.add(jsonObject); - } else { - jsonObject.put("fileSize", FileUtil.readableFileSize(file.length())); - arrayFile.add(jsonObject); - } - } - - JSONArray resultArray = new JSONArray(); - resultArray.addAll(arrayDir); - resultArray.addAll(arrayFile); - return JsonMessage.getString(200, "查询成功", resultArray); - } - - - /** - * 上传文件 - * - * @param id tomcat id - * @param path 文件路径 - * @return 操作结果 - */ - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String upload(String id, String path) { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - - MultipartFileBuilder multipartFileBuilder = createMultipart() - .addFieldName("file"); - - File dir = new File(tomcatInfoModel.getAppBase().concat(FileUtil.normalize(path))); - - multipartFileBuilder.setSavePath(dir.getAbsolutePath()) - .setUseOriginalFilename(true); - // 保存 - try { - multipartFileBuilder.save(); - } catch (IOException e) { - return JsonMessage.getString(500, "上传异常"); - } - - return JsonMessage.getString(200, "上传成功"); - } - - /** - * 上传war文件 - * - * @param id tomcat id - * @return 操作结果 - */ - @RequestMapping(value = "uploadWar", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String uploadWar(String id) { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - - MultipartFileBuilder multipartFileBuilder = createMultipart() - .addFieldName("file"); - - File dir = new File(tomcatInfoModel.getAppBase()); - - multipartFileBuilder.setSavePath(dir.getAbsolutePath()) - .setUseOriginalFilename(true); - // 保存 - try { - multipartFileBuilder.save(); - } catch (IOException e) { - return JsonMessage.getString(500, "上传异常"); - } - - return JsonMessage.getString(200, "上传成功"); - } - - - /** - * 删除文件 - * - * @param id tomcat id - * @param filename 文件名 - * @param path tomcat路径 - * @return 操作结果 - */ - @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String deleteFile(String id, String path, String filename) { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - if (tomcatInfoModel == null) { - return JsonMessage.getString(500, "tomcat不存在"); - } - File file; - if ("_tomcat_log".equals(path)) { - //删除日志文件 - file = FileUtil.file(tomcatInfoModel.getPath(), "logs", filename); - // 判断修改时间 - long modified = file.lastModified(); - if (System.currentTimeMillis() - modified < TimeUnit.DAYS.toMillis(1)) { - return JsonMessage.getString(405, "不能删除当天的日志"); - } - // 判断最后修改日期 - AgentFileTailWatcher.offlineFile(file); - } else { - file = FileUtil.file(tomcatInfoModel.getAppBase(), path, filename); - } - if (file.exists()) { - if (file.delete()) { - return JsonMessage.getString(200, "删除成功"); - } else { - return JsonMessage.getString(500, "删除失败"); - } - } else { - return JsonMessage.getString(404, "文件不存在"); - } - } - - /** - * 下载文件 - * - * @param id tomcat id - * @param filename 文件名 - * @param path tomcat路径 - * @return 操作结果 - */ - @RequestMapping(value = "download", method = RequestMethod.GET) - public String download(String id, String path, String filename) { - filename = FileUtil.normalize(filename); - path = FileUtil.normalize(path); - try { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - File file; - //下载日志文件 - if ("_tomcat_log".equals(path)) { - file = FileUtil.file(tomcatInfoModel.getPath(), "logs", filename); - } else { - file = FileUtil.file(tomcatInfoModel.getAppBase(), path, filename); - } - if (file.isDirectory()) { - return "暂不支持下载文件夹"; - } - ServletUtil.write(getResponse(), file); - } catch (Exception e) { - DefaultSystemLog.getLog().error("下载文件异常", e); - } - return "下载失败。请刷新页面后重试"; - } - - /** - * 获取tomcat 日志列表 - * - * @param id tomcat id - * @return json - */ - @RequestMapping(value = "logList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String logList(String id) { - TomcatInfoModel item = tomcatEditService.getItem(id); - if (item == null) { - return JsonMessage.getString(300, "没有对应数据"); - } - JSONArray jsonArray = LayuiTreeUtil.getTreeData(FileUtil.file(item.pathAndCheck(), "logs").getAbsolutePath()); - return JsonMessage.getString(200, "", jsonArray); - } -} diff --git a/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatOp.java b/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatOp.java deleted file mode 100644 index b9a3ea7d40..0000000000 --- a/modules/agent/src/main/java/io/jpom/controller/tomcat/TomcatOp.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.tomcat; - -/** - * tomcat操作 - * - * @author bwcx_jzy - * @date 2019/7/22 - **/ -public enum TomcatOp { - /** - * 启动 - */ - start, - stop, - reload, - /** - * 删除发布 - */ - undeploy -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/CertModel.java b/modules/agent/src/main/java/io/jpom/model/data/CertModel.java deleted file mode 100644 index c2c82d2be4..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/CertModel.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.KeyUtil; -import cn.hutool.crypto.PemUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.RSA; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.BaseModel; -import io.jpom.service.system.CertService; -import io.jpom.system.JpomRuntimeException; - -import java.io.InputStream; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.Date; - -/** - * 证书实体 - * - * @author Arno - */ -public class CertModel extends BaseModel { - - private static final String KEY = "Jpom 管理系统"; - /** - * 证书文件 - */ - private String cert; - /** - * 私钥 - */ - private String key; - /** - * 证书到期时间 - */ - private long expirationTime; - /** - * 证书生效日期 - */ - private long effectiveTime; - /** - * 绑定域名 - */ - private String domain; - /** - * 白名单路径 - */ - private String whitePath; - private Type type; - - public Type getType() { - return type; - } - - public void setType(Type type) { - this.type = type; - } - - public String getWhitePath() { - return whitePath; - } - - public void setWhitePath(String whitePath) { - this.whitePath = whitePath; - } - - public String getCert() { - return cert; - } - - public void setCert(String cert) { - this.cert = cert; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public long getExpirationTime() { - this.convertInfo(); - return expirationTime; - } - - public void setExpirationTime(long expirationTime) { - this.expirationTime = expirationTime; - } - - public String getDomain() { - this.convertInfo(); - return domain; - } - - /** - * 兼容手动添加的证书文件 - */ - private void convertInfo() { - if (!StrUtil.isEmpty(domain)) { - return; - } - JSONObject jsonObject = decodeCert(getCert(), getKey()); - if (jsonObject != null) { - // 获取信息 - this.setDomain(jsonObject.getString("domain")); - this.setExpirationTime(jsonObject.getLongValue("expirationTime")); - this.setEffectiveTime(jsonObject.getLongValue("effectiveTime")); - - // 数据持久化到文件中 - CertService certService = SpringUtil.getBean(CertService.class); - certService.updateItem(this); - } - } - - public void setDomain(String domain) { - this.domain = domain; - } - - public long getEffectiveTime() { - this.convertInfo(); - return effectiveTime; - } - - public void setEffectiveTime(long effectiveTime) { - this.effectiveTime = effectiveTime; - } - - /** - * 解析证书 - * - * @param key zip里面文件 - * @param file 证书文件 - * @return 处理后的json - */ - public static JSONObject decodeCert(String file, String key) { - if (file == null) { - return null; - } - if (!FileUtil.exist(file)) { - return null; - } - InputStream inputStream = null; - try { - inputStream = ResourceUtil.getStream(key); - PrivateKey privateKey = PemUtil.readPemPrivateKey(inputStream); - IoUtil.close(inputStream); - inputStream = ResourceUtil.getStream(file); - PublicKey publicKey = PemUtil.readPemPublicKey(inputStream); - IoUtil.close(inputStream); - RSA rsa = new RSA(privateKey, publicKey); - String encryptStr = rsa.encryptBase64(KEY, KeyType.PublicKey); - String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); - if (!KEY.equals(decryptStr)) { - throw new JpomRuntimeException("证书和私钥证书不匹配"); - } - } finally { - IoUtil.close(inputStream); - } - try { - inputStream = ResourceUtil.getStream(file); - // 创建证书对象 - X509Certificate oCert = (X509Certificate) KeyUtil.readX509Certificate(inputStream); - //到期时间 - Date expirationTime = oCert.getNotAfter(); - //生效日期 - Date effectiveTime = oCert.getNotBefore(); - //域名 - String name = oCert.getSubjectDN().getName(); - int i = name.indexOf("="); - String domain = name.substring(i + 1); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("expirationTime", expirationTime.getTime()); - jsonObject.put("effectiveTime", effectiveTime.getTime()); - jsonObject.put("domain", domain); - jsonObject.put("pemPath", file); - jsonObject.put("keyPath", key); - return jsonObject; - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - } finally { - IoUtil.close(inputStream); - } - return null; - } - - /** - * 证书类型 - */ - public enum Type { - /** - * - */ - pem, - /** - * Windows - */ - cer, - /** - * Linux - */ - crt - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/JdkInfoModel.java b/modules/agent/src/main/java/io/jpom/model/data/JdkInfoModel.java deleted file mode 100644 index b869bfee70..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/JdkInfoModel.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseModel; - -/** - * jdk 信息 - * - * @author bwcx_jzy - * @date 2019/10/29 - */ -public class JdkInfoModel extends BaseModel { - - /** - * jdk 路径 - */ - private String path; - - /** - * jdk 版本 - */ - private String version; - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/NodeProjectInfoModel.java b/modules/agent/src/main/java/io/jpom/model/data/NodeProjectInfoModel.java deleted file mode 100644 index a9c818dd49..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/NodeProjectInfoModel.java +++ /dev/null @@ -1,621 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.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.StrUtil; -import cn.hutool.http.HtmlUtil; -import cn.jiangzeyin.common.request.XssFilter; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.BaseJsonModel; -import io.jpom.model.BaseModel; -import io.jpom.model.RunMode; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.system.JpomRuntimeException; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * 项目配置信息实体 - * - * @author jiangzeyin - */ -public class NodeProjectInfoModel extends BaseModel { - // /** -// * 分组 -// */ -// private String group; - private String mainClass; - private String lib; - /** - * 白名单目录 - */ - private String whitelistDirectory; - private String log; - - /** - * 日志目录 - */ - private String logPath; - /** - * jvm 参数 - */ - private String jvm; - /** - * java main 方法参数 - */ - private String args; - - private List javaCopyItemList; - /** - * WebHooks - */ - private String token; - private boolean status; - private String createTime; - private String modifyTime; - private String jdkId; - /** - * lib 目录当前文件状态 - */ - private String useLibDesc; - /** - * 当前运行lib 状态 - */ - private String runLibDesc; - /** - * 最后修改人 - */ - private String modifyUser; - - private RunMode runMode; - /** - * 节点分发项目,不允许在项目管理中编辑 - */ - private boolean outGivingProject; - /** - * 实际运行的命令 - */ - private String runCommand; - - /** - * -Djava.ext.dirs=lib -cp conf:run.jar - * 填写【lib:conf】 - */ - private String javaExtDirsCp; - - private String workspaceId; - - /** - * 项目自动启动 - */ - private Boolean autoStart; - - public String getWorkspaceId() { - return workspaceId; - } - - public void setWorkspaceId(String workspaceId) { - this.workspaceId = workspaceId; - } - - public List getJavaCopyItemList() { - return javaCopyItemList; - } - - public void setJavaCopyItemList(List javaCopyItemList) { - this.javaCopyItemList = javaCopyItemList; - } - - public String getJavaExtDirsCp() { - return StrUtil.emptyToDefault(javaExtDirsCp, StrUtil.EMPTY); - } - - public void setJavaExtDirsCp(String javaExtDirsCp) { - this.javaExtDirsCp = javaExtDirsCp; - } - - public String getRunCommand() { - return runCommand; - } - - public void setRunCommand(String runCommand) { - this.runCommand = runCommand; - } - - public boolean isOutGivingProject() { - return outGivingProject; - } - - public void setOutGivingProject(boolean outGivingProject) { - this.outGivingProject = outGivingProject; - } - - public RunMode getRunMode() { - if (runMode == null) { - return RunMode.ClassPath; - } - return runMode; - } - - public void setRunMode(RunMode runMode) { - this.runMode = runMode; - } - - public String getModifyUser() { - if (StrUtil.isEmpty(modifyUser)) { - return StrUtil.DASHED; - } - return modifyUser; - } - - public void setModifyUser(String modifyUser) { - this.modifyUser = modifyUser; - } - - public void setStatus(boolean status) { - this.status = status; - } - - public String getRunLibDesc() { - return runLibDesc; - } - - public void setRunLibDesc(String runLibDesc) { - this.runLibDesc = runLibDesc; - } - - public String getUseLibDesc() { - return useLibDesc; - } - - public void setUseLibDesc(String useLibDesc) { - this.useLibDesc = useLibDesc; - } - - public String getModifyTime() { - return modifyTime; - } - - /** - * 修改时间 - * - * @param modifyTime time - */ - public void setModifyTime(String modifyTime) { - this.modifyTime = modifyTime; - } - - public String getJvm() { - String s = StrUtil.emptyToDefault(jvm, StrUtil.EMPTY); - if (XssFilter.isXSS()) { - s = HtmlUtil.unescape(s); - } - return s; - } - - public void setJvm(String jvm) { - if (XssFilter.isXSS()) { - this.jvm = HtmlUtil.unescape(jvm); - } else { - this.jvm = jvm; - } - } - - public Boolean getAutoStart() { - return autoStart; - } - - public void setAutoStart(Boolean autoStart) { - this.autoStart = autoStart; - } - - // -// public String getGroup() { -// if (StrUtil.isEmpty(group)) { -// return "默认"; -// } -// return group; -// } -// -// public void setGroup(String group) { -// this.group = group; -// } - - public String getMainClass() { - return StrUtil.emptyToDefault(mainClass, StrUtil.EMPTY); - } - - private void repairWhitelist() { - if (StrUtil.isEmpty(whitelistDirectory) && StrUtil.isEmpty(lib)) { - throw new JpomRuntimeException("当前项目lib数据异常"); - } - if (StrUtil.isNotEmpty(whitelistDirectory)) { - return; - } - WhitelistDirectoryService whitelistDirectoryService = SpringUtil.getBean(WhitelistDirectoryService.class); - List project = whitelistDirectoryService.getWhitelist().getProject(); - for (String path : project) { - if (lib.startsWith(path)) { - String itemWhitelistDirectory = lib.substring(0, path.length()); - lib = lib.substring(path.length()); - setWhitelistDirectory(itemWhitelistDirectory); - setLib(lib); - } - } - } - - public String getWhitelistDirectory() { - this.repairWhitelist(); - if (StrUtil.isEmpty(whitelistDirectory)) { - throw new JpomRuntimeException("修护白名单数据异常"); - } - return whitelistDirectory; - } - - public void setWhitelistDirectory(String whitelistDirectory) { - this.whitelistDirectory = whitelistDirectory; - } - - public void setMainClass(String mainClass) { - this.mainClass = mainClass; - } - - public String getLib() { - this.repairWhitelist(); - return lib; - } - - public String allLib() { - return FileUtil.file(getWhitelistDirectory(), getLib()).getAbsolutePath(); - } - - /** - * 获取项目文件中的所有jar 文件 - * - * @param nodeProjectInfoModel 项目 - * @return list - */ - public static List listJars(NodeProjectInfoModel nodeProjectInfoModel) { - File fileLib = new File(nodeProjectInfoModel.allLib()); - File[] files = fileLib.listFiles(); - List files1 = new ArrayList<>(); - if (files != null) { - for (File file : files) { - if (!file.isFile()) { - continue; - } - if (nodeProjectInfoModel.getRunMode() == RunMode.ClassPath || nodeProjectInfoModel.getRunMode() == RunMode.Jar) { - if (!StrUtil.endWith(file.getName(), FileUtil.JAR_FILE_EXT, true)) { - continue; - } - } else if (nodeProjectInfoModel.getRunMode() == RunMode.JarWar) { - if (!StrUtil.endWith(file.getName(), "war", true)) { - continue; - } - } - files1.add(file); - } - } - return files1; - } - - /** - * 拼接java 执行的jar路径 - * - * @param nodeProjectInfoModel 项目 - * @return classpath 或者 jar - */ - public static String getClassPathLib(NodeProjectInfoModel nodeProjectInfoModel) { - List files = listJars(nodeProjectInfoModel); - if (files.size() <= 0) { - 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 void setLib(String lib) { - this.lib = lib; - } - - - public String getLogPath() { - return StrUtil.emptyToDefault(this.logPath, StrUtil.EMPTY); - } - - public void setLogPath(String logPath) { - this.logPath = logPath; - } - - 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); - } - - /** - * 副本的控制台日志文件 - * - * @param javaCopyItem 副本信息 - * @return file - */ - public File getLog(JavaCopyItem javaCopyItem) { - File file = FileUtil.file(getLog()); - return FileUtil.file(file.getParentFile(), getId() + "_" + javaCopyItem.getId() + ".log"); - } - - public String getAbsoluteLog(JavaCopyItem javaCopyItem) { - File file = javaCopyItem == null ? new File(getLog()) : getLog(javaCopyItem); - // auto create dir - FileUtil.mkParentDirs(file); - return file.getAbsolutePath(); - } - - public File getLogBack() { - return new File(getLog() + "_back"); - } - - public File getLogBack(JavaCopyItem javaCopyItem) { - return new File(getLog(javaCopyItem) + "_back"); - } - - public void setLog(String log) { - this.log = log; - } - - /** - * 默认 - * - * @return url token - */ - public String getToken() { - // 兼容旧数据 - if ("no".equalsIgnoreCase(this.token)) { - return ""; - } - return StrUtil.emptyToDefault(token, StrUtil.EMPTY); - } - - public void setToken(String token) { - this.token = token; - } - - public String getCreateTime() { - return createTime; - } - - public void setCreateTime(String createTime) { - this.createTime = createTime; - } - - public String getArgs() { - String s = StrUtil.emptyToDefault(args, StrUtil.EMPTY); - if (XssFilter.isXSS()) { - s = HtmlUtil.unescape(s); - } - return s; - } - - public void setArgs(String args) { - if (XssFilter.isXSS()) { - this.args = HtmlUtil.unescape(args); - } else { - this.args = args; - } - } - - public String getJdkId() { - return jdkId; - } - - public void setJdkId(String jdkId) { - this.jdkId = jdkId; - } - - - public JavaCopyItem findCopyItem(String copyId) { - if (StrUtil.isEmpty(copyId)) { - return null; - } - List javaCopyItemList = getJavaCopyItemList(); - if (CollUtil.isEmpty(javaCopyItemList)) { - return null; - } - Optional first = javaCopyItemList.stream().filter(javaCopyItem -> StrUtil.equals(javaCopyItem.getId(), copyId)).findFirst(); - return first.orElse(null); - } - - public boolean removeCopyItem(String copyId) { - - if (StrUtil.isEmpty(copyId) || CollUtil.isEmpty(javaCopyItemList)) { - return true; - } - int size = javaCopyItemList.size(); - List collect = javaCopyItemList.stream().filter(javaCopyItem -> !StrUtil.equals(javaCopyItem.getId(), copyId)).collect(Collectors.toList()); - if (size - 1 == collect.size()) { - this.javaCopyItemList = collect; - return true; - } else { - return false; - } - } - - public static class JavaCopyItem extends BaseJsonModel { - /** - * 父级项目id - */ - private String parendId; - /** - * id - */ - private String id; - - /** - * jvm 参数 - */ - private String jvm; - /** - * java main 方法参数 - */ - private String args; - - private String modifyTime; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getTagId() { - return getTagId(parendId, id); - } - - /** - * 创建进程标记 - * - * @param id - * @param copyId - * @return - */ - public static String getTagId(String id, String copyId) { - if (StrUtil.isEmpty(copyId)) { - return id; - } - return StrUtil.format("{}:{}", id, copyId); - } - - public String getModifyTime() { - return modifyTime; - } - - public void setModifyTime(String modifyTime) { - this.modifyTime = modifyTime; - } - - public String getParendId() { - return parendId; - } - - public void setParendId(String parendId) { - this.parendId = parendId; - } - - public String getJvm() { - if (XssFilter.isXSS()) { - return HtmlUtil.unescape(jvm); - } - return jvm; - } - - public void setJvm(String jvm) { - if (XssFilter.isXSS()) { - this.jvm = HtmlUtil.unescape(jvm); - } else { - this.jvm = jvm; - } - } - - public String getArgs() { - if (XssFilter.isXSS()) { - return HtmlUtil.unescape(args); - } - return args; - } - - public void setArgs(String args) { - if (XssFilter.isXSS()) { - this.args = HtmlUtil.unescape(args); - } else { - this.args = args; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JavaCopyItem that = (JavaCopyItem) o; - return Objects.equals(parendId, that.parendId) && - Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(parendId, id, jvm, args, modifyTime); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/ProjectRecoverModel.java b/modules/agent/src/main/java/io/jpom/model/data/ProjectRecoverModel.java deleted file mode 100644 index 2405581cf1..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/ProjectRecoverModel.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.lang.ObjectId; -import io.jpom.model.BaseModel; - -/** - * 项目回收记录实体 - * - * @author jiangzeyin - */ -public class ProjectRecoverModel extends BaseModel { - /** - * 删除人 - */ - private String delUser; - /** - * 删除时间 - */ - private String delTime; - /** - * 删除的对应项目信息 - */ - private NodeProjectInfoModel nodeProjectInfoModel; - - public ProjectRecoverModel(NodeProjectInfoModel nodeProjectInfoModel) { - this.nodeProjectInfoModel = nodeProjectInfoModel; - // 生成操作id - setId(ObjectId.next()); - } - - public ProjectRecoverModel() { - } - - public NodeProjectInfoModel getProjectInfoModel() { - return nodeProjectInfoModel; - } - - public void setProjectInfoModel(NodeProjectInfoModel nodeProjectInfoModel) { - this.nodeProjectInfoModel = nodeProjectInfoModel; - } - - public String getDelUser() { - return delUser; - } - - public void setDelUser(String delUser) { - this.delUser = delUser; - } - - public String getDelTime() { - return delTime; - } - - public void setDelTime(String delTime) { - this.delTime = delTime; - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/ScriptModel.java b/modules/agent/src/main/java/io/jpom/model/data/ScriptModel.java deleted file mode 100644 index 25a0e3bcd7..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/ScriptModel.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.JpomApplication; -import io.jpom.model.BaseModel; -import io.jpom.system.AgentConfigBean; -import io.jpom.util.CommandUtil; - -import java.io.File; - -/** - * 脚本模板 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -public class ScriptModel extends BaseModel { - - /** - * 最后执行人员 - */ - private String lastRunUser; - /** - * 最后修改时间 - */ - private String modifyTime; - /** - * 脚本内容 - */ - private String context; - - public String getLastRunUser() { - return StrUtil.emptyToDefault(lastRunUser, StrUtil.DASHED); - } - - public void setLastRunUser(String lastRunUser) { - this.lastRunUser = lastRunUser; - } - - public String getModifyTime() { - return modifyTime; - } - - public void setModifyTime(String modifyTime) { - this.modifyTime = modifyTime; - } - - public String getContext() { - return context; - } - - public void setContext(String context) { - this.context = context; - } - - public File getFile(boolean get) { - if (StrUtil.isEmpty(getId())) { - throw new IllegalArgumentException("id 为空"); - } - File path = AgentConfigBean.getInstance().getScriptPath(); - return FileUtil.file(path, getId(), "script." + CommandUtil.SUFFIX); - } - - public File logFile() { - if (StrUtil.isEmpty(getId())) { - throw new IllegalArgumentException("id 为空"); - } - File path = AgentConfigBean.getInstance().getScriptPath(); - File logFile; - int count = 0; - do { - String now = DateTime.now().toString(DatePattern.PURE_DATETIME_PATTERN); - logFile = FileUtil.file(path, getId(), "log", now + count + ".log"); - count++; - } while (FileUtil.exist(logFile)); - return logFile; - } - - public void saveFile() { - File file = getFile(true); - FileUtil.writeString(getContext(), file, JpomApplication.getCharset()); -// // 添加权限 -// if (SystemUtil.getOsInfo().isLinux()) { -// CommandUtil.execCommand("chmod 755 " + FileUtil.getAbsolutePath(file)); -// } - } - - /** - * 读取文件信息 - */ - public void readFileTime() { - File file = getFile(true); - long lastModified = file.lastModified(); - setModifyTime(DateUtil.date(lastModified).toString()); - - } - - public void readFileContext() { - File file = getFile(true); - if (FileUtil.exist(file)) { - // - String context = FileUtil.readString(file, JpomApplication.getCharset()); - setContext(context); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/TomcatInfoModel.java b/modules/agent/src/main/java/io/jpom/model/data/TomcatInfoModel.java deleted file mode 100644 index ce8ef32b53..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/TomcatInfoModel.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.ZipUtil; -import io.jpom.model.BaseModel; -import io.jpom.system.JpomRuntimeException; - -import java.io.File; -import java.io.InputStream; - -/** - * tomcat 对象实体 - * - * @author lf - */ -public class TomcatInfoModel extends BaseModel { - - private String path; - private int port; - private int status; - private String appBase; - private String creator; - private String createTime; - private String modifyUser; - private String modifyTime; - - public String getPath() { - if (path == null) { - return null; - } - return FileUtil.normalize(path + StrUtil.SLASH); - } - - /** - * 检测路径是否正确 - * - * @return path - */ - public String pathAndCheck() { - String path = getPath(); - if (path == null) { - return null; - } - if (isTomcatRoot(path)) { - return path; - } - throw new RuntimeException(String.format("没有在路径:%s 下检测到Tomcat", path)); - } - - public void setPath(String path) { - this.path = path; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - - public String getAppBase() { - if (StrUtil.isEmpty(appBase)) { - return FileUtil.normalize(path + "/webapps/"); - } - return FileUtil.normalize(appBase + StrUtil.SLASH); - } - - public void setAppBase(String appBase) { - this.appBase = appBase; - } - - public String getCreator() { - return creator; - } - - public void setCreator(String creator) { - this.creator = creator; - } - - public String getCreateTime() { - return createTime; - } - - public void setCreateTime(String createTime) { - this.createTime = createTime; - } - - public String getModifyUser() { - return modifyUser; - } - - public void setModifyUser(String modifyUser) { - this.modifyUser = modifyUser; - } - - public String getModifyTime() { - return modifyTime; - } - - public void setModifyTime(String modifyTime) { - this.modifyTime = modifyTime; - } - - - /** - * 判断是否是Tomcat的根路径 - * - * @return 返回是否是Tomcat根路径 - */ - private static boolean isTomcatRoot(String path) { - File file = new File(path); - if (!file.exists()) { - return false; - } - if (file.isFile()) { - return false; - } - File[] files = file.listFiles(); - if (files == null) { - return false; - } - // 判断该目录下是否 - for (File child : files) { - if ("bin".equals(child.getName()) && child.isDirectory()) { - File[] binFiles = child.listFiles(); - if (binFiles == null) { - return false; - } - for (File binChild : binFiles) { - if ("bootstrap.jar".equals(binChild.getName()) && binChild.isFile()) { - return true; - } - } - } - } - return false; - } - - /** - * 初始化 - */ - public void initTomcat() { - String tomcatPath = pathAndCheck(); - String appBase = getAppBase(); - if (StrUtil.isEmpty(appBase) || StrUtil.SLASH.equals(appBase)) { - File webapps = FileUtil.file(tomcatPath, "webapps"); - setAppBase(webapps.getAbsolutePath()); - } else { - String path = FileUtil.normalize(appBase); - if (FileUtil.isAbsolutePath(path)) { - // appBase如:/project/、D:/project/ - setAppBase(path); - } else { - // appBase填写的是对相路径如:project/dir - File webapps = FileUtil.file(tomcatPath, path); - setAppBase(webapps.getAbsolutePath()); - } - } - InputStream inputStream = ResourceUtil.getStream("classpath:/bin/jpomAgent.zip"); - if (inputStream == null) { - throw new JpomRuntimeException("jpomAgent.zip不存在"); - } - // 解压代理工具到tomcat的appBase目录下 - ZipUtil.unzip(inputStream, new File(getAppBase()), CharsetUtil.CHARSET_UTF_8); - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/data/UploadFileModel.java b/modules/agent/src/main/java/io/jpom/model/data/UploadFileModel.java deleted file mode 100644 index 84e4981036..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/data/UploadFileModel.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.model.BaseModel; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * @author lf - */ -public class UploadFileModel extends BaseModel { - private long size = 0; - private long completeSize = 0; - private String savePath; - private String version; - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public long getCompleteSize() { - return completeSize; - } - - public void setCompleteSize(long completeSize) { - this.completeSize = completeSize; - } - - public String getSavePath() { - return savePath; - } - - public void setSavePath(String savePath) { - this.savePath = savePath; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public void save(byte[] data) { - this.completeSize += data.length; - File file = new File(this.getFilePath()); - FileUtil.mkParentDirs(file); - try (FileOutputStream fileOutputStream = new FileOutputStream(file, true)) { - fileOutputStream.write(data); - fileOutputStream.flush(); - } catch (IOException e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - } - } - - public String getFilePath() { - return savePath + StrUtil.SLASH + getName(); - } - - public void remove() { - FileUtil.del(this.getFilePath()); - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/system/NetstatModel.java b/modules/agent/src/main/java/io/jpom/model/system/NetstatModel.java deleted file mode 100644 index 9bde889f9d..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/system/NetstatModel.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.system; - -import cn.hutool.core.util.StrUtil; -import io.jpom.model.BaseJsonModel; - -/** - * 网络端口信息实体 - * - * @author jiangzeyin - * @date 2019/4/10 - */ -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; - } -} diff --git a/modules/agent/src/main/java/io/jpom/model/system/ProcessModel.java b/modules/agent/src/main/java/io/jpom/model/system/ProcessModel.java deleted file mode 100644 index 846aecfdd9..0000000000 --- a/modules/agent/src/main/java/io/jpom/model/system/ProcessModel.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.system; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.BaseJsonModel; - -import java.io.IOException; - -/** - * 进程信息实体 - * - * @author jiangzeyin - * @date 2019/4/15 - */ -public class ProcessModel extends BaseJsonModel { - /** - * 进程id - */ - private int pid; - /** - * 进程名 - */ - private String command; - /** - * 运行状态 - */ - private String status; - /** - * 进程仍然在使用的,没被交换出物理内存部分的大小 - */ - private String res; - /** - * 所有者 - */ - private String user; - /** - * 时间总计 - */ - private String time; - /** - * 优先级 - */ - private String pr = StrUtil.DASHED; - /** - * nice值 - */ - private String ni = StrUtil.DASHED; - /** - * 虚拟内存 - */ - private String virt = StrUtil.DASHED; - /** - * 共享内存大小 - */ - private String shr = StrUtil.DASHED; - /** - * 内存比重 - */ - private String mem = StrUtil.DASHED; - /** - * cpu比重 - */ - private String cpu = StrUtil.DASHED; - /** - * 端口 - */ - private String port = StrUtil.DASHED; - /** - * Jpom 项目名称 - */ - private String jpomName = StrUtil.DASHED; - - public String getPort() { - return port; - } - - public void setPort(String port) { - this.port = port; - } - - public String getJpomName() { - return jpomName; - } - - public void setJpomName(String jpomName) { - this.jpomName = jpomName; - } - - public int getPid() { - return pid; - } - - public void setPid(int pid) { - this.pid = pid; - if (pid > 0) { - String port = AbstractProjectCommander.getInstance().getMainPort(pid); - this.setPort(port); - // - try { - String jpomName = AbstractProjectCommander.getInstance().getJpomNameByPid(pid); - this.setJpomName(jpomName); - } catch (IOException e) { - DefaultSystemLog.getLog().error("解析进程失败", e); - } - } - } - - public String getCommand() { - return command; - } - - public void setCommand(String command) { - this.command = command; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public String getRes() { - return res; - } - - public void setRes(String res) { - this.res = res; - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - - public String getPr() { - return pr; - } - - public void setPr(String pr) { - this.pr = pr; - } - - public String getNi() { - return ni; - } - - public void setNi(String ni) { - this.ni = ni; - } - - public String getVirt() { - return virt; - } - - public void setVirt(String virt) { - this.virt = virt; - } - - public String getShr() { - return shr; - } - - public void setShr(String shr) { - this.shr = shr; - } - - public String getMem() { - return mem; - } - - public void setMem(String mem) { - this.mem = mem; - } - - public String getCpu() { - return cpu; - } - - public void setCpu(String cpu) { - this.cpu = cpu; - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/WhitelistDirectoryService.java b/modules/agent/src/main/java/io/jpom/service/WhitelistDirectoryService.java deleted file mode 100644 index b91f04234d..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/WhitelistDirectoryService.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service; - -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseDataService; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.system.AgentConfigBean; -import io.jpom.util.JsonFileUtil; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; - -/** - * 白名单服务 - * - * @author jiangzeyin - * @date 2019/2/28 - */ -@Service -public class WhitelistDirectoryService extends BaseDataService { - - /** - * 获取白名单信息配置、如何没有配置或者配置错误将返回新对象 - * - * @return AgentWhitelist - */ - public AgentWhitelist getWhitelist() { - try { - JSONObject jsonObject = getJSONObject(AgentConfigBean.WHITELIST_DIRECTORY); - if (jsonObject == null) { - return new AgentWhitelist(); - } - return jsonObject.toJavaObject(AgentWhitelist.class); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - } - return new AgentWhitelist(); - } - - /** - * 单项添加白名单 - * - * @param item 白名单 - */ - public void addProjectWhiteList(String item) { - AgentWhitelist agentWhitelist = getWhitelist(); - List project = agentWhitelist.getProject(); - if (project == null) { - project = new ArrayList<>(); - } - project.add(item); - saveWhitelistDirectory(agentWhitelist); - } - - public boolean isInstalled() { - AgentWhitelist agentWhitelist = getWhitelist(); - List project = agentWhitelist.getProject(); - return project != null && !project.isEmpty(); - } - - private List getNgxDirectory() { - AgentWhitelist agentWhitelist = getWhitelist(); - return agentWhitelist.getNginx(); - } - - public boolean checkProjectDirectory(String path) { - AgentWhitelist agentWhitelist = getWhitelist(); - - List list = agentWhitelist.getProject(); - return AgentWhitelist.checkPath(list, path); - } - - public boolean checkNgxDirectory(String path) { - List list = getNgxDirectory(); - return AgentWhitelist.checkPath(list, path); - } - - private List getCertificateDirectory() { - AgentWhitelist agentWhitelist = getWhitelist(); - - return agentWhitelist.getCertificate(); - } - - public boolean checkCertificateDirectory(String path) { - List list = getCertificateDirectory(); - if (list == null) { - return false; - } - return AgentWhitelist.checkPath(list, path); - } - - /** - * 保存白名单 - * - * @param jsonObject 实体 - */ - public void saveWhitelistDirectory(AgentWhitelist jsonObject) { - String path = getDataFilePath(AgentConfigBean.WHITELIST_DIRECTORY); - JsonFileUtil.saveJson(path, jsonObject.toJson()); - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/ConsoleService.java b/modules/agent/src/main/java/io/jpom/service/manage/ConsoleService.java deleted file mode 100644 index b5ca0ff7bd..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/ConsoleService.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; - -import cn.hutool.core.date.DateUtil; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.socket.ConsoleCommandOp; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; - -/** - * 控制台 - * Created by jiangzeyin on 2018/9/28. - * - * @author jiangzeyin - */ -@Service -public class ConsoleService { - @Resource - private ProjectInfoService projectInfoService; - - /** - * 执行shell命令 - * - * @param consoleCommandOp 执行的操作 - * @param nodeProjectInfoModel 项目信息 - * @param copyItem 副本信息 - * @return 执行结果 - * @throws Exception 异常 - */ - public String execCommand(ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem copyItem) throws Exception { - String result; - AbstractProjectCommander abstractProjectCommander = AbstractProjectCommander.getInstance(); - // 执行命令 - switch (consoleCommandOp) { - case restart: - result = abstractProjectCommander.restart(nodeProjectInfoModel, copyItem); - break; - case start: - result = abstractProjectCommander.start(nodeProjectInfoModel, copyItem); - break; - case stop: - result = abstractProjectCommander.stop(nodeProjectInfoModel, copyItem); - break; - case status: { - result = abstractProjectCommander.status(nodeProjectInfoModel, copyItem); - 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()); - // - if (copyItem != null) { - NodeProjectInfoModel.JavaCopyItem copyItem1 = modify.findCopyItem(copyItem.getId()); - copyItem1.setModifyTime(DateUtil.now()); - } - modify.setRunLibDesc(nodeProjectInfoModel.getUseLibDesc()); - try { - projectInfoService.updateItem(modify); - } catch (Exception ignored) { - } - } - return result; - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/JdkInfoService.java b/modules/agent/src/main/java/io/jpom/service/manage/JdkInfoService.java deleted file mode 100644 index 864297e084..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/JdkInfoService.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.JavaInfo; -import cn.hutool.system.JavaRuntimeInfo; -import io.jpom.common.BaseOperService; -import io.jpom.model.data.JdkInfoModel; -import io.jpom.system.AgentConfigBean; -import org.springframework.stereotype.Service; - -/** - * jdk 管理 - * - * @author Arno - * @date 2019/11/20 - */ -@Service -public class JdkInfoService extends BaseOperService { - - public JdkInfoService() { - super(AgentConfigBean.JDK_CONF); - } - - /** - * 使用中的jdk - * - * @return JdkInfoModel - */ - private JdkInfoModel getDefaultJdk() { - JavaRuntimeInfo info = new JavaRuntimeInfo(); - String homeDir = info.getHomeDir(); - String version = new JavaInfo().getVersion(); - if (StrUtil.isEmpty(homeDir) || StrUtil.isEmpty(version)) { - return null; - } - String path = FileUtil.normalize(homeDir.replace("jre", "")); - JdkInfoModel jdkInfoModel = new JdkInfoModel(); - jdkInfoModel.setId(IdUtil.fastUUID()); - jdkInfoModel.setVersion(version); - jdkInfoModel.setPath(path); - return jdkInfoModel; - } - -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/ProjectInfoService.java b/modules/agent/src/main/java/io/jpom/service/manage/ProjectInfoService.java deleted file mode 100644 index 4667ef1f7e..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/ProjectInfoService.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.BaseAgentController; -import io.jpom.common.BaseOperService; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.model.data.ProjectRecoverModel; -import io.jpom.system.AgentConfigBean; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.io.File; - -/** - * 项目管理 - * - * @author jiangzeyin - */ -@Service -public class ProjectInfoService extends BaseOperService { - @Resource - private ProjectRecoverService projectRecoverService; - - public ProjectInfoService() { - super(AgentConfigBean.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; -// } - - - /** - * 删除项目 - * - * @param id 项目 - */ - @Override - public void deleteItem(String id) { - NodeProjectInfoModel projectInfo = getItem(id); - String userId = BaseAgentController.getNowUserName(); - super.deleteItem(id); - // 添加回收记录 - ProjectRecoverModel projectRecoverModel = new ProjectRecoverModel(projectInfo); - projectRecoverModel.setDelUser(userId); - projectRecoverService.addItem(projectRecoverModel); - } - - /** - * 修改项目信息 - * - * @param projectInfo 项目信息 - */ - @Override - public void updateItem(NodeProjectInfoModel projectInfo) { - projectInfo.setModifyTime(DateUtil.now()); - String userName = BaseAgentController.getNowUserName(); - if (!StrUtil.DASHED.equals(userName)) { - projectInfo.setModifyUser(userName); - } - super.updateItem(projectInfo); - } - - @Override - public void addItem(NodeProjectInfoModel nodeProjectInfoModel) { - nodeProjectInfoModel.setCreateTime(DateUtil.now()); - super.addItem(nodeProjectInfoModel); - } - - /** - * 查看项目控制台日志文件大小 - * - * @param nodeProjectInfoModel 项目 - * @param copyItem 副本 - * @return 文件大小 - */ - public String getLogSize(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem copyItem) { - if (nodeProjectInfoModel == null) { - return null; - } - File file = copyItem == null ? new File(nodeProjectInfoModel.getLog()) : nodeProjectInfoModel.getLog(copyItem); - if (file.exists()) { - long fileSize = file.length(); - if (fileSize <= 0) { - return null; - } - return FileUtil.readableFileSize(fileSize); - } - return null; - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/ProjectRecoverService.java b/modules/agent/src/main/java/io/jpom/service/manage/ProjectRecoverService.java deleted file mode 100644 index 68ad2336ee..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/ProjectRecoverService.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; - -import cn.hutool.core.date.DateUtil; -import io.jpom.common.BaseOperService; -import io.jpom.model.data.ProjectRecoverModel; -import io.jpom.system.AgentConfigBean; -import org.springframework.stereotype.Service; - -/** - * 项目管理 - * - * @author jiangzeyin - */ -@Service -public class ProjectRecoverService extends BaseOperService { - - public ProjectRecoverService() { - super(AgentConfigBean.PROJECT_RECOVER); - } - - - /** - * 保存项目信息 - * - * @param projectInfo 项目 - */ - @Override - public void addItem(ProjectRecoverModel projectInfo) { - projectInfo.setDelTime(DateUtil.now()); - // 保存 - super.addItem(projectInfo); - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/TomcatEditService.java b/modules/agent/src/main/java/io/jpom/service/manage/TomcatEditService.java deleted file mode 100644 index 8e93fc136e..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/TomcatEditService.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; - -import cn.hutool.core.date.DateUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseOperService; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.system.AgentConfigBean; -import org.springframework.stereotype.Service; - -/** - * @author lf - */ -@Service -public class TomcatEditService extends BaseOperService { - - - public TomcatEditService() { - super(AgentConfigBean.TOMCAT); - } - - - /** - * 根据tomcat名称查询tomcat信息 - * - * @param name tomcat的名称 - * @return tomcat信息 - */ - public TomcatInfoModel getItemByName(String name) { - JSONObject allTomcat = getJSONObject(AgentConfigBean.TOMCAT); - - if (allTomcat == null) { - return null; - } - - JSONObject tomcat = null; - for (String key : allTomcat.keySet()) { - JSONObject object = allTomcat.getJSONObject(key); - if (name.equals(object.getString("name"))) { - tomcat = object; - break; - } - } - - return JSONObject.toJavaObject(tomcat, TomcatInfoModel.class); - } - - /** - * 添加Tomcat - * - * @param tomcatInfoModel tomcat信息 - */ - @Override - public void addItem(TomcatInfoModel tomcatInfoModel) { - tomcatInfoModel.setCreateTime(DateUtil.now()); - super.addItem(tomcatInfoModel); - } - - - /** - * 修改tomcat信息 - * - * @param tomcatInfoModel tomcat信息 - */ - @Override - public void updateItem(TomcatInfoModel tomcatInfoModel) { - tomcatInfoModel.setModifyTime(DateUtil.now()); - super.updateItem(tomcatInfoModel); - - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/TomcatManageService.java b/modules/agent/src/main/java/io/jpom/service/manage/TomcatManageService.java deleted file mode 100644 index c25ad3f2f4..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/TomcatManageService.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.controller.tomcat.TomcatOp; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.system.JpomRuntimeException; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; - -/** - * @author bwcx_jzy - * @date 2019/7/21 - */ -@Service -public class TomcatManageService { - - @Resource - private TomcatEditService tomcatEditService; - - /** - * 查询tomcat状态 - * - * @param id tomcat的id - * @return tomcat状态0表示未运行,1表示运行中 - */ - public int getTomcatStatus(String id) { - int result = 0; - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - String url = String.format("http://127.0.0.1:%d/", tomcatInfoModel.getPort()); - HttpRequest httpRequest = new HttpRequest(url); - // 设置超时时间为3秒 - httpRequest.setConnectionTimeout(3000); - try { - HttpResponse httpResponse = httpRequest.execute(); - result = 1; - } catch (Exception ignored) { - } - - return result; - } - - /** - * 查询tomcat的项目列表 - * - * @param id tomcat的id - * @return tomcat的项目列表 - */ - public JSONArray getTomcatProjectList(String id) { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - String body = tomcatCmd(tomcatInfoModel, "text/list"); - - String[] result = body.replace(StrUtil.CRLF, "$") - .replace("\n", "$") - .split("\\$"); - - JSONArray jsonArray = new JSONArray(); - - for (int i = 1; i < result.length; i++) { - String str = result[i]; - JSONObject jsonObject = new JSONObject(); - String[] strs = str.split(":"); - if (strs[0].endsWith("jpomAgent")) { - continue; - } - - jsonObject.put("path", StrUtil.SLASH.equals(strs[0]) ? "/ROOT" : strs[0]); - jsonObject.put("status", strs[1]); - jsonObject.put("session", strs[2]); - - jsonArray.add(jsonObject); - } - - return jsonArray; - } - - /** - * 访问tomcat Url - * - * @param tomcatInfoModel tomcat信息 - * @param cmd 命令 - * @return 访问结果 - */ - private String tomcatCmd(TomcatInfoModel tomcatInfoModel, String cmd) { - String url = String.format("http://127.0.0.1:%d/jpomAgent/%s", tomcatInfoModel.getPort(), cmd); - HttpRequest httpRequest = new HttpRequest(url); - // 设置超时时间为3秒 - httpRequest.setConnectionTimeout(3000); - String body = ""; - - try { - HttpResponse httpResponse = httpRequest.execute(); - if (httpResponse.isOk()) { - body = httpResponse.body(); - } - if (httpResponse.getStatus() == HttpStatus.HTTP_NOT_FOUND) { - // 没有插件 - tomcatInfoModel.initTomcat(); - throw new JpomRuntimeException("tomcat 未初始化,已经重新初始化请稍后再试"); - } - } catch (JpomRuntimeException jpom) { - throw jpom; - } catch (Exception ignored) { - } - - return body; - } - - /** - * tomcat项目管理 - * - * @param id tomcat id - * @param path 项目路径 - * @param tomcatOp 执行的操作 start=启动项目 stop=停止项目 relaod=重启项目 - * @return 操作结果 - */ - public JsonMessage tomcatProjectManage(String id, String path, TomcatOp tomcatOp) { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(id); - String result = tomcatCmd(tomcatInfoModel, String.format("text/%s?path=%s", tomcatOp.name(), path)); - - if (result.startsWith("OK")) { - return new JsonMessage(200, "操作成功"); - } else { - return new JsonMessage(500, "操作失败:" + result); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/manage/package-info.java b/modules/agent/src/main/java/io/jpom/service/manage/package-info.java deleted file mode 100644 index 52bef96375..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/manage/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.manage; \ No newline at end of file diff --git a/modules/agent/src/main/java/io/jpom/service/script/ScriptServer.java b/modules/agent/src/main/java/io/jpom/service/script/ScriptServer.java deleted file mode 100644 index 33cb07166c..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/script/ScriptServer.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.script; - -import cn.hutool.core.io.FileUtil; -import io.jpom.common.BaseOperService; -import io.jpom.model.data.ScriptModel; -import io.jpom.system.AgentConfigBean; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * 脚本模板管理 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -@Service -public class ScriptServer extends BaseOperService { - - public ScriptServer() { - super(AgentConfigBean.SCRIPT); - } - - @Override - public List list() { - List scriptModels = super.list(); - if (scriptModels == null) { - return null; - } - // 读取文件内容 - scriptModels.forEach(ScriptModel::readFileTime); - return scriptModels; - } - - @Override - public ScriptModel getItem(String id) { - ScriptModel scriptModel = super.getItem(id); - if (scriptModel != null) { - scriptModel.readFileContext(); - } - return scriptModel; - } - - @Override - public void addItem(ScriptModel scriptModel) { - super.addItem(scriptModel); - scriptModel.saveFile(); - } - - @Override - public void updateItem(ScriptModel scriptModel) { - super.updateItem(scriptModel); - scriptModel.saveFile(); - } - - @Override - public void deleteItem(String id) { - ScriptModel scriptModel = getItem(id); - if (scriptModel != null) { - FileUtil.del(scriptModel.getFile(true).getParentFile()); - } - super.deleteItem(id); - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/system/CertService.java b/modules/agent/src/main/java/io/jpom/service/system/CertService.java deleted file mode 100644 index 1afd4ed4b6..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/system/CertService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.BaseOperService; -import io.jpom.model.data.CertModel; -import io.jpom.system.AgentConfigBean; -import org.springframework.stereotype.Service; - -import java.io.File; - -/** - * @author Arno - */ -@Service -public class CertService extends BaseOperService { - - public CertService() { - super(AgentConfigBean.CERT); - } - - - /** - * 删除证书 - * - * @param id id - */ - @Override - public void deleteItem(String id) { - CertModel certModel = getItem(id); - if (certModel == null) { - return; - } - String keyPath = certModel.getCert(); - super.deleteItem(id); - if (StrUtil.isNotEmpty(keyPath)) { - // 删除证书文件 - File parentFile = FileUtil.file(keyPath).getParentFile(); - FileUtil.del(parentFile); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/service/system/NginxService.java b/modules/agent/src/main/java/io/jpom/service/system/NginxService.java deleted file mode 100644 index 5aaf6973b1..0000000000 --- a/modules/agent/src/main/java/io/jpom/service/system/NginxService.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.github.odiszapc.nginxparser.NgxBlock; -import com.github.odiszapc.nginxparser.NgxConfig; -import com.github.odiszapc.nginxparser.NgxEntry; -import com.github.odiszapc.nginxparser.NgxParam; -import io.jpom.common.BaseDataService; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.service.WhitelistDirectoryService; -import io.jpom.system.AgentConfigBean; -import io.jpom.util.JsonFileUtil; -import io.jpom.util.StringUtil; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.List; - -/** - * @author Arno - */ -@Service -public class NginxService extends BaseDataService { - - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - - public JSONArray list(String whitePath, String fileName) { - AgentWhitelist agentWhitelist = whitelistDirectoryService.getWhitelist(); - if (agentWhitelist == null) { - return null; - } - List ngxDirectory = agentWhitelist.getNginx(); - if (ngxDirectory == null) { - return null; - } - File normalize = FileUtil.file(whitePath, fileName); - File[] files = FileUtil.ls(normalize.getAbsolutePath()); - if (files == null || files.length <= 0) { - return null; - } - JSONArray array = new JSONArray(); - for (File file : files) { - String name = file.getName(); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("path", whitePath); - long time = file.lastModified(); - jsonObject.put("time", DateUtil.date(time).toString()); - jsonObject.put("name", name); - jsonObject.put("relativePath", FileUtil.normalize(fileName + StrUtil.SLASH + name)); - if (file.isDirectory()) { - continue; -// if (FileUtil.isEmpty(file)) { -// continue; -// } -// jsonObject.put("name", name + "【文件夹】"); -// jsonObject.put("isDirectory", true); - } else { - if (!name.endsWith(".conf")) { - continue; - } - try { - NgxConfig config = NgxConfig.read(file.getPath()); - List server = config.findAll(NgxBlock.class, "server"); - JSONObject data = findSeverName(server); - if (data != null) { - jsonObject.putAll(data); - } - } catch (IOException e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - } - } - array.add(jsonObject); - } - return array; - } - - /** - * 获取nginx树型图列表 - * - * @return JSONArray - */ - public JSONArray tree() { - JSONArray treeArray = new JSONArray(); - AgentWhitelist agentWhitelist = whitelistDirectoryService.getWhitelist(); - if (agentWhitelist == null) { - return treeArray; - } - List ngxDirectory = agentWhitelist.getNginx(); - if (ngxDirectory == null) { - return treeArray; - } - for (String str : ngxDirectory) { - JSONObject object = addChild(str, ""); - if (object != null) { - object.put("title", str); - //object.put("spread", true); - treeArray.add(object); - } - } - return treeArray; - } - - /** - * 扫描目录下所有nginx配置文件 - * - * @param whitePath 白名单路径 - * @param fileName 文件路径 - */ - private JSONObject addChild(String whitePath, String fileName) { - File parentFile = StrUtil.isNotEmpty(fileName) ? FileUtil.file(whitePath, fileName) : FileUtil.file(whitePath); - if (!FileUtil.isDirectory(parentFile)) { - return null; - } - String absolutePath = parentFile.getAbsolutePath(); - File[] files = FileUtil.ls(absolutePath); - if (files == null || files.length <= 0) { - return null; - } - JSONObject object = new JSONObject(); - String parentName = parentFile.getName(); - object.put("title", parentName); - object.put("whitePath", whitePath); - object.put("path", fileName); - JSONArray array = new JSONArray(); - for (File file : files) { - String name = StringUtil.delStartPath(file, whitePath, true); - if (file.isDirectory()) { - if (FileUtil.isEmpty(file)) { - continue; - } - JSONObject child = addChild(whitePath, name); - if (child != null) { - array.add(child); - } - } else { -// String fName = file.getName(); -// if (fName.endsWith(".conf")) { -// JSONObject child = new JSONObject(); -// child.put("title", fName); -// child.put("whitePath", whitePath); -// child.put("path", name); -// array.add(child); -// } - } - } - object.put("children", array); - return object; - } - - /** - * 获取域名 - * - * @param server server块 - * @return 域名 - */ - private JSONObject findSeverName(List server) { - if (null == server) { - return null; - } - JSONObject jsonObject = new JSONObject(); - HashSet serverNames = new HashSet<>(); - HashSet location = new HashSet<>(); - HashSet listen = new HashSet<>(); - for (NgxEntry ngxEntry : server) { - - NgxBlock ngxBlock = (NgxBlock) ngxEntry; - NgxParam serverName = ngxBlock.findParam("server_name"); - if (null != serverName) { - serverNames.add(serverName.getValue()); - } - List locationAll = ngxBlock.findAll(NgxBlock.class, "location"); - if (locationAll != null) { - locationAll.forEach(ngxEntry1 -> { - NgxBlock ngxBlock1 = (NgxBlock) ngxEntry1; - if (!StrUtil.SLASH.equals(ngxBlock1.getValue())) { - return; - } - NgxParam locationMain = ngxBlock1.findParam("proxy_pass"); - if (locationMain == null) { - locationMain = ngxBlock1.findParam("root"); - } - if (locationMain == null) { - locationMain = ngxBlock1.findParam("alias"); - } - if (locationMain == null) { - return; - } - location.add(locationMain.getValue()); - }); - } - // 监听的端口 - NgxParam listenParm = ngxBlock.findParam("listen"); - if (listenParm != null) { - listen.add(listenParm.getValue()); - } - } - jsonObject.put("serverCount", server.size()); - jsonObject.put("server_name", CollUtil.join(serverNames, StrUtil.COMMA)); - jsonObject.put("location", CollUtil.join(location, StrUtil.COMMA)); - jsonObject.put("listen", CollUtil.join(listen, StrUtil.COMMA)); - return jsonObject; - } - - - /** - * 解析nginx - * - * @param path nginx路径 - */ - public JSONObject getItem(String path) { - JSONObject jsonObject = new JSONObject(); - try { - NgxConfig conf = NgxConfig.read(path); - NgxParam cache = conf.findParam("http", "proxy_cache_path"); - if (cache != null) { - String value = cache.getValue(); - String[] split = value.split(" "); - jsonObject.put("cachePath", split[0].trim()); - String maxSize = split[3]; - String size = maxSize.substring("max_size=".length(), maxSize.length() - 1); - jsonObject.put("cacheSize", size); - String inactive = split[4]; - String time = inactive.substring("inactive=".length(), inactive.length() - 1); - jsonObject.put("inactive", time); - } - List list = conf.findAll(NgxBlock.class, "server"); - if (list == null) { - return jsonObject; - } - boolean main = true; - for (NgxEntry ngxEntry : list) { - NgxBlock block = (NgxBlock) ngxEntry; - NgxParam certificate = block.findParam("ssl_certificate"); - NgxParam key = block.findParam("ssl_certificate_key"); - NgxParam listen = block.findParam("listen"); - NgxParam serverName = block.findParam("server_name"); - NgxParam location = block.findParam("location", "proxy_pass"); - if (certificate != null && main) { - main = false; - jsonObject.put("cert", certificate.getValue()); - jsonObject.put("key", key.getValue()); - jsonObject.put("port", listen.getValue()); - jsonObject.put("domain", serverName.getValue()); - jsonObject.put("location", location.getValue()); - } - NgxParam rewrite = block.findParam("rewrite"); - if (rewrite != null) { - jsonObject.put("convert", true); - } - if (main) { - jsonObject.put("port", listen.getValue()); - jsonObject.put("domain", serverName.getValue()); - if (null != location) { - jsonObject.put("location", location.getValue()); - } - } - - } - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - } - return jsonObject; - } - - /** - * 获取nginx配置 - * name 修改后的服务名 - * status 状态:开启 open/ 关闭close - */ - public JSONObject getNgxConf() { - JSONObject object = getJSONObject(AgentConfigBean.NGINX_CONF); - if (object == null) { - object = new JSONObject(); - object.put("name", "nginx"); - save(object); - } - return object; - } - - - public String getServiceName() { - JSONObject ngxConf = getNgxConf(); - return ngxConf.getString("name"); - } - - public void save(JSONObject object) { - String dataFilePath = getDataFilePath(AgentConfigBean.NGINX_CONF); - JsonFileUtil.saveJson(dataFilePath, object); - } - -} diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java b/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java deleted file mode 100644 index 67720e23e4..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.util.BaseFileTailWatcher; - -import javax.websocket.Session; -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 文件跟随器 - * - * @author jiangzeyin - * @date 2019/3/16 - */ -public class AgentFileTailWatcher extends BaseFileTailWatcher { - private static final ConcurrentHashMap> CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); - - - private AgentFileTailWatcher(File logFile) throws IOException { - super(logFile); - } - - public static int getOneLineCount() { - return CONCURRENT_HASH_MAP.size(); - } - - /** - * 添加文件监听 - * - * @param file 文件 - * @param session 会话 - * @throws IOException 异常 - */ - public static void addWatcher(File file, Session session) throws IOException { - if (!file.exists() || file.isDirectory()) { - DefaultSystemLog.getLog().warn("文件不存在或者是目录:" + file.getPath()); - return; - } - AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.computeIfAbsent(file, s -> { - try { - return new AgentFileTailWatcher<>(file); - } catch (Exception e) { - DefaultSystemLog.getLog().error("创建文件监听失败", e); - return null; - } - }); - if (agentFileTailWatcher == null) { - throw new IOException("加载文件失败:" + file.getPath()); - } - agentFileTailWatcher.add(session, FileUtil.getName(file)); - agentFileTailWatcher.tailWatcherRun.start(); - } - - /** - * 有客户端离线 - * - * @param session 会话 - */ - public static void offline(Session session) { - Collection> collection = CONCURRENT_HASH_MAP.values(); - for (AgentFileTailWatcher agentFileTailWatcher : collection) { - agentFileTailWatcher.socketSessions.removeIf(session::equals); - if (agentFileTailWatcher.socketSessions.isEmpty()) { - agentFileTailWatcher.close(); - } - } - } - - /** - * 关闭文件读取流 - * - * @param fileName 文件名 - */ - public static void offlineFile(File fileName) { - AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); - if (null == agentFileTailWatcher) { - return; - } - Set socketSessions = agentFileTailWatcher.socketSessions; - for (Session socketSession : socketSessions) { - offline(socketSession); - } - agentFileTailWatcher.close(); - } - - /** - * 关闭文件读取流 - * - * @param fileName 文件名 - */ - static void offlineFile(File fileName, Session session) { - AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); - if (null == agentFileTailWatcher) { - return; - } - Set socketSessions = agentFileTailWatcher.socketSessions; - for (Session socketSession : socketSessions) { - if (socketSession.equals(session)) { - offline(socketSession); - break; - } - } - if (agentFileTailWatcher.socketSessions.isEmpty()) { - agentFileTailWatcher.close(); - } - - } - - - /** - * 关闭 - */ - @Override - protected void close() { - super.close(); - // 清理线程记录 - CONCURRENT_HASH_MAP.remove(this.logFile); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java deleted file mode 100644 index e4c16fa59f..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.server.standard.ServerEndpointExporter; - -/** - * 插件端socket 配置 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -@Configuration -public class AgentWebSocketConfig { - - @Bean - public ServerEndpointExporter serverEndpointExporter() { - return new ServerEndpointExporter(); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConsoleHandle.java b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConsoleHandle.java deleted file mode 100644 index 5c4711e0e6..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConsoleHandle.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.manage.ConsoleService; -import io.jpom.service.manage.ProjectInfoService; -import io.jpom.util.SocketSessionUtil; -import org.springframework.stereotype.Component; - -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; -import java.io.File; -import java.io.IOException; - -/** - * 插件端,控制台socket - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@ServerEndpoint(value = "/console") -@Component -public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle { - - private static ProjectInfoService projectInfoService; - - @OnOpen - public void onOpen(Session session) { - try { - if (super.checkAuthorize(session)) { - return; - } - String projectId = super.getParameters(session, "projectId"); - String copyId = super.getParameters(session, "copyId"); - // 判断项目 - if (!JpomApplication.SYSTEM_ID.equals(projectId)) { - if (projectInfoService == null) { - projectInfoService = SpringUtil.getBean(ProjectInfoService.class); - } - NodeProjectInfoModel nodeProjectInfoModel = this.checkProject(projectId, copyId, session); - if (nodeProjectInfoModel == null) { - return; - } - // - SocketSessionUtil.send(session, "连接成功:" + nodeProjectInfoModel.getName()); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("socket 错误", e); - try { - SocketSessionUtil.send(session, JsonMessage.getString(500, "系统错误!")); - session.close(); - } catch (IOException e1) { - DefaultSystemLog.getLog().error(e1.getMessage(), e1); - } - } - } - - /** - * 静默消息不做过多处理 - * - * @param consoleCommandOp 操作 - * @param session 回话 - * @return true - */ - private boolean silentMsg(ConsoleCommandOp consoleCommandOp, Session session) { - if (consoleCommandOp == ConsoleCommandOp.heart) { - return true; - } -// if (consoleCommandOp == ConsoleCommandOp.top) { -// TopManager.addMonitor(session); -// return true; -// } - return false; - } - - private NodeProjectInfoModel checkProject(String projectId, String copyId, Session session) throws IOException { - NodeProjectInfoModel nodeProjectInfoModel = projectInfoService.getItem(projectId); - if (nodeProjectInfoModel == null) { - SocketSessionUtil.send(session, "没有对应项目"); - session.close(); - return null; - } - // 判断副本集 - if (StrUtil.isNotEmpty(copyId)) { - NodeProjectInfoModel.JavaCopyItem copyItem = nodeProjectInfoModel.findCopyItem(copyId); - if (copyItem == null) { - SocketSessionUtil.send(session, "获取项目信息错误,没有对应副本:" + copyId); - session.close(); - return null; - } - } - return nodeProjectInfoModel; - } - - @OnMessage - public void onMessage(String message, Session session) throws Exception { - JSONObject json = JSONObject.parseObject(message); - String op = json.getString("op"); - ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op); - if (silentMsg(consoleCommandOp, session)) { - return; - } - String projectId = json.getString("projectId"); - String copyId = json.getString("copyId"); - NodeProjectInfoModel nodeProjectInfoModel = this.checkProject(projectId, copyId, session); - if (nodeProjectInfoModel == null) { - return; - } - runMsg(consoleCommandOp, session, nodeProjectInfoModel, copyId, json); - } - - private void runMsg(ConsoleCommandOp consoleCommandOp, Session session, NodeProjectInfoModel nodeProjectInfoModel, String copyId, JSONObject reqJson) throws Exception { - ConsoleService consoleService = SpringUtil.getBean(ConsoleService.class); - // - NodeProjectInfoModel.JavaCopyItem copyItem = nodeProjectInfoModel.findCopyItem(copyId); - JSONObject resultData = null; - String strResult; - boolean logUser = false; - try { - // 执行相应命令 - switch (consoleCommandOp) { - case start: - case restart: - logUser = true; - strResult = consoleService.execCommand(consoleCommandOp, nodeProjectInfoModel, copyItem); - if (strResult.contains(AbstractProjectCommander.RUNNING_TAG)) { - resultData = JsonMessage.toJson(200, "操作成功:" + strResult); - } else { - resultData = JsonMessage.toJson(400, strResult); - } - break; - case stop: - logUser = true; - // 停止项目 - strResult = consoleService.execCommand(consoleCommandOp, nodeProjectInfoModel, copyItem); - if (strResult.contains(AbstractProjectCommander.STOP_TAG)) { - resultData = JsonMessage.toJson(200, "操作成功"); - } else { - resultData = JsonMessage.toJson(500, strResult); - } - break; - case status: - // 获取项目状态 - strResult = consoleService.execCommand(consoleCommandOp, nodeProjectInfoModel, copyItem); - if (strResult.contains(AbstractProjectCommander.RUNNING_TAG)) { - resultData = JsonMessage.toJson(200, "运行中", strResult); - } else { - resultData = JsonMessage.toJson(404, "未运行", strResult); - } - break; - case showlog: { - // 进入管理页面后需要实时加载日志 - // 日志文件路径 - File file = copyItem == null ? new File(nodeProjectInfoModel.getLog()) : nodeProjectInfoModel.getLog(copyItem); - try { - AgentFileTailWatcher.addWatcher(file, session); - } catch (IOException io) { - DefaultSystemLog.getLog().error("监听日志变化", io); - SocketSessionUtil.send(session, io.getMessage()); - } - break; - } - default: - resultData = JsonMessage.toJson(404, "不支持的方式:" + consoleCommandOp.name()); - break; - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行命令失败", e); - SocketSessionUtil.send(session, "执行命令失败,详情如下:"); - SocketSessionUtil.send(session, ExceptionUtil.stacktraceToString(e)); - return; - } finally { - if (logUser) { - // 记录操作人 - NodeProjectInfoModel newNodeProjectInfoModel = projectInfoService.getItem(nodeProjectInfoModel.getId()); - String name = getOptUserName(session); - newNodeProjectInfoModel.setModifyUser(name); - projectInfoService.updateItem(newNodeProjectInfoModel); - } - } - // 返回数据 - if (resultData != null) { - reqJson.putAll(resultData); - DefaultSystemLog.getLog().info(reqJson.toString()); - SocketSessionUtil.send(session, reqJson.toString()); - } - } - - @Override - @OnClose - public void onClose(Session session) { - super.onClose(session); - } - - @OnError - @Override - public void onError(Session session, Throwable thr) { - super.onError(session, thr); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketScriptHandle.java b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketScriptHandle.java deleted file mode 100644 index 77bc247e1f..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketScriptHandle.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.data.ScriptModel; -import io.jpom.service.script.ScriptServer; -import io.jpom.util.SocketSessionUtil; -import org.springframework.stereotype.Component; - -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; -import java.io.IOException; - -/** - * 脚本模板socket - * - * @author jiangzeyin - * @date 2019/4/24 - */ -@ServerEndpoint(value = "/script_run") -@Component -public class AgentWebSocketScriptHandle extends BaseAgentWebSocketHandle { - - private ScriptServer scriptServer; - - @OnOpen - public void onOpen(Session session) { - if (scriptServer == null) { - scriptServer = SpringUtil.getBean(ScriptServer.class); - } - try { - if (super.checkAuthorize(session)) { - return; - } - String id = this.getParameters(session, "id"); - if (StrUtil.isEmpty(id)) { - SocketSessionUtil.send(session, "脚本模板未知"); - return; - } - ScriptModel scriptModel = scriptServer.getItem(id); - if (scriptModel == null) { - SocketSessionUtil.send(session, "没有找到对应的脚本模板"); - return; - } - SocketSessionUtil.send(session, "连接成功:" + scriptModel.getName()); - } catch (Exception e) { - DefaultSystemLog.getLog().error("socket 错误", e); - try { - SocketSessionUtil.send(session, JsonMessage.getString(500, "系统错误!")); - session.close(); - } catch (IOException e1) { - DefaultSystemLog.getLog().error(e1.getMessage(), e1); - } - } - } - - @OnMessage - public void onMessage(String message, Session session) throws Exception { - JSONObject json = JSONObject.parseObject(message); - String scriptId = json.getString("scriptId"); - ScriptModel scriptModel = scriptServer.getItem(scriptId); - if (scriptModel == null) { - SocketSessionUtil.send(session, "没有对应脚本模板:" + scriptId); - session.close(); - return; - } - String op = json.getString("op"); - ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op); - switch (consoleCommandOp) { - case start: - String args = json.getString("args"); - ScriptProcessBuilder.addWatcher(scriptModel, args, session); - break; - case stop: - ScriptProcessBuilder.stopRun(scriptModel); - break; - case heart: - default: - return; - } - // 记录操作人 - scriptModel = scriptServer.getItem(scriptId); - String name = getOptUserName(session); - scriptModel.setLastRunUser(name); - scriptServer.updateItem(scriptModel); - json.put("code", 200); - json.put("msg", "执行成功"); - DefaultSystemLog.getLog().info(json.toString()); - SocketSessionUtil.send(session, json.toString()); - } - - - @Override - @OnClose - public void onClose(Session session) { - super.onClose(session); - ScriptProcessBuilder.stopWatcher(session); - } - - @OnError - @Override - public void onError(Session session, Throwable thr) { - super.onError(session, thr); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketTomcatHandle.java b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketTomcatHandle.java deleted file mode 100644 index bc1ebf305f..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketTomcatHandle.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.model.data.TomcatInfoModel; -import io.jpom.service.manage.TomcatEditService; -import io.jpom.system.WebAopLog; -import io.jpom.util.SocketSessionUtil; -import org.springframework.stereotype.Component; - -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; -import java.io.File; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 插件端,控制台socket - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@ServerEndpoint(value = "/tomcat_log") -@Component -public class AgentWebSocketTomcatHandle extends BaseAgentWebSocketHandle { - - private TomcatEditService tomcatEditService; - - private static final Map CACHE_FILE = new ConcurrentHashMap<>(); - - @OnOpen - public void onOpen(Session session) { - try { - if (super.checkAuthorize(session)) { - return; - } - String tomcatId = super.getParameters(session, "tomcatId"); - if (tomcatEditService == null) { - tomcatEditService = SpringUtil.getBean(TomcatEditService.class); - } - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(tomcatId); - if (tomcatInfoModel == null && !JpomApplication.SYSTEM_ID.equalsIgnoreCase(tomcatId)) { - SocketSessionUtil.send(session, "获取tomcat信息错误"); - session.close(); - return; - } - SocketSessionUtil.send(session, "连接成功:" + (tomcatInfoModel == null ? "" : tomcatInfoModel.getName())); - } catch (Exception e) { - DefaultSystemLog.getLog().error("socket 错误", e); - try { - SocketSessionUtil.send(session, JsonMessage.getString(500, "系统错误!")); - session.close(); - } catch (IOException e1) { - DefaultSystemLog.getLog().error(e1.getMessage(), e1); - } - } - } - - @OnMessage - public void onMessage(String message, Session session) throws Exception { - JSONObject json = JSONObject.parseObject(message); - String op = json.getString("op"); - ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op); - if (consoleCommandOp == ConsoleCommandOp.heart) { - return; - } - String tomcatId = json.getString("tomcatId"); - if (JpomApplication.SYSTEM_ID.equalsIgnoreCase(tomcatId)) { - runMsg(session, json); - } else { - TomcatInfoModel tomcatInfoModel = tomcatEditService.getItem(tomcatId); - if (tomcatInfoModel == null) { - SocketSessionUtil.send(session, "没有对应tomcat"); - session.close(); - return; - } - runMsg(session, tomcatInfoModel, json); - } - } - - private void runMsg(Session session, JSONObject reqJson) throws Exception { - try { - String fileName = reqJson.getString("fileName"); - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - // 进入管理页面后需要实时加载日志 - File file = FileUtil.file(webAopLog.getPropertyValue(), fileName); - File file1 = CACHE_FILE.get(session.getId()); - if (file1 != null && !file1.equals(file)) { - // 离线上一个日志 - AgentFileTailWatcher.offlineFile(file, session); - } - try { - AgentFileTailWatcher.addWatcher(file, session); - CACHE_FILE.put(session.getId(), file); - } catch (IOException io) { - DefaultSystemLog.getLog().error("监听日志变化", io); - SocketSessionUtil.send(session, io.getMessage()); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行命令失败", e); - SocketSessionUtil.send(session, "执行命令失败,详情如下:"); - SocketSessionUtil.send(session, ExceptionUtil.stacktraceToString(e)); - } - } - - private void runMsg(Session session, TomcatInfoModel tomcatInfoModel, JSONObject reqJson) throws Exception { - try { - String fileName = reqJson.getString("fileName"); - // 进入管理页面后需要实时加载日志 - File file = FileUtil.file(tomcatInfoModel.getPath(), "logs", fileName); - try { - AgentFileTailWatcher.addWatcher(file, session); - } catch (IOException io) { - DefaultSystemLog.getLog().error("监听日志变化", io); - SocketSessionUtil.send(session, io.getMessage()); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行命令失败", e); - SocketSessionUtil.send(session, "执行命令失败,详情如下:"); - SocketSessionUtil.send(session, ExceptionUtil.stacktraceToString(e)); - } - } - - @Override - @OnClose - public void onClose(Session session) { - super.onClose(session); - } - - @OnError - @Override - public void onError(Session session, Throwable thr) { - super.onError(session, thr); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketUpdateHandle.java b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketUpdateHandle.java deleted file mode 100644 index 1e50a926e4..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketUpdateHandle.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.lang.Tuple; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.common.Const; -import io.jpom.common.JpomManifest; -import io.jpom.common.Type; -import io.jpom.model.AgentFileModel; -import io.jpom.model.WebSocketMessageModel; -import io.jpom.model.data.UploadFileModel; -import io.jpom.system.AgentConfigBean; -import io.jpom.util.SocketSessionUtil; -import org.springframework.stereotype.Component; - -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; -import java.util.HashMap; -import java.util.Map; - -/** - * 在线升级 - * - * @author bwcx_jzy - * @date 2021/8/3 - */ -@ServerEndpoint(value = "/node_update") -@Component -public class AgentWebSocketUpdateHandle extends BaseAgentWebSocketHandle { - - private static final Map UPLOAD_FILE_INFO = new HashMap<>(); - - @OnOpen - public void onOpen(Session session) { - if (super.checkAuthorize(session)) { - return; - } - session.setMaxBinaryMessageBufferSize(1024 * 1024); - // - } - - - @OnMessage - public void onMessage(String message, Session session) throws Exception { - WebSocketMessageModel model = WebSocketMessageModel.getInstance(message); - switch (model.getCommand()) { - case "getVersion": - model.setData(JSONObject.toJSONString(JpomManifest.getInstance())); - break; - case "upload": - AgentFileModel agentFileModel = ((JSONObject) model.getParams()).toJavaObject(AgentFileModel.class); - UploadFileModel uploadFileModel = new UploadFileModel(); - uploadFileModel.setId(model.getNodeId()); - uploadFileModel.setName(agentFileModel.getName()); - uploadFileModel.setSize(agentFileModel.getSize()); - uploadFileModel.setVersion(agentFileModel.getVersion()); - uploadFileModel.setSavePath(AgentConfigBean.getInstance().getTempPath().getAbsolutePath()); - uploadFileModel.remove(); - UPLOAD_FILE_INFO.put(session.getId(), uploadFileModel); - break; - case "restart": - model.setData(restart(session)); - break; - default: - break; - } - SocketSessionUtil.send(session, model.toString()); - //session.sendMessage(new TextMessage(model.toString())); - } - - @OnMessage - public void onMessage(byte[] message, Session session) throws Exception { - UploadFileModel uploadFileModel = UPLOAD_FILE_INFO.get(session.getId()); - uploadFileModel.save(message); - // 更新进度 - WebSocketMessageModel model = new WebSocketMessageModel("updateNode", uploadFileModel.getId()); - model.setData(uploadFileModel); - SocketSessionUtil.send(session, model.toString()); -// session.sendMessage(new TextMessage(model.toString())); - } - - /** - * 重启 - * - * @param session 回话 - * @return 结果 - */ - public String restart(Session session) { - String result = Const.UPGRADE_MSG; - try { - UploadFileModel uploadFile = UPLOAD_FILE_INFO.get(session.getId()); - String filePath = uploadFile.getFilePath(); - JsonMessage error = JpomManifest.checkJpomJar(filePath, Type.Agent); - if (error.getCode() != HttpStatus.HTTP_OK) { - return error.getMsg(); - } - JpomManifest.releaseJar(filePath, uploadFile.getVersion(), true); - JpomApplication.restart(); - } catch (RuntimeException e) { - result = "重启失败" + e.getMessage(); - DefaultSystemLog.getLog().error("重启失败", e); - } - return result; - } - - @Override - @OnClose - public void onClose(Session session) { - super.onClose(session); - UPLOAD_FILE_INFO.remove(session.getId()); - } - - @OnError - @Override - public void onError(Session session, Throwable thr) { - super.onError(session, thr); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/BaseAgentWebSocketHandle.java b/modules/agent/src/main/java/io/jpom/socket/BaseAgentWebSocketHandle.java deleted file mode 100644 index a3ae3df0ab..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/BaseAgentWebSocketHandle.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.system.AgentAuthorize; -import io.jpom.system.ConfigBean; -import io.jpom.util.SocketSessionUtil; - -import javax.websocket.CloseReason; -import javax.websocket.Session; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static javax.websocket.CloseReason.CloseCodes.CANNOT_ACCEPT; - -/** - * 插件端socket 基类 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -public abstract class BaseAgentWebSocketHandle { - - private static final ConcurrentHashMap USER = new ConcurrentHashMap<>(); - - protected String getParameters(Session session, String name) { - Map> pathParameters = session.getRequestParameterMap(); - List strings = pathParameters.get(name); - return CollUtil.join(strings, StrUtil.COMMA); - } - - /** - * 判断授权信息是否正确 - * - * @param session session - * @return true 需要结束回话 - */ - public boolean checkAuthorize(Session session) { - String authorize = this.getParameters(session, ConfigBean.JPOM_AGENT_AUTHORIZE); - boolean ok = AgentAuthorize.getInstance().checkAuthorize(authorize); - if (!ok) { - try { - session.close(new CloseReason(CANNOT_ACCEPT, "授权信息错误")); - } catch (Exception e) { - DefaultSystemLog.getLog().error("socket 错误", e); - } - return true; - } - this.addUser(session, this.getParameters(session, "optUser")); - return false; - } - - /** - * 添加用户监听的 - * - * @param session session - * @param name 用户名 - */ - private void addUser(Session session, String name) { - String optUser = URLUtil.decode(name); - USER.put(session.getId(), optUser); - } - - public void onError(Session session, Throwable thr) { - // java.io.IOException: Broken pipe - try { - SocketSessionUtil.send(session, "服务端发生异常" + ExceptionUtil.stacktraceToString(thr)); - } catch (IOException ignored) { - } - DefaultSystemLog.getLog().error(session.getId() + "socket 异常", thr); - } - - protected String getOptUserName(Session session) { - String name = USER.get(session.getId()); - return StrUtil.emptyToDefault(name, StrUtil.DASHED); - } - - public void onClose(Session session) { - // 清理日志监听 - try { - AgentFileTailWatcher.offline(session); - } catch (Exception e) { - DefaultSystemLog.getLog().error("关闭异常", e); - } - // top - // TopManager.removeMonitor(session); - USER.remove(session.getId()); - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/ScriptProcessBuilder.java b/modules/agent/src/main/java/io/jpom/socket/ScriptProcessBuilder.java deleted file mode 100644 index cf5349cdff..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/ScriptProcessBuilder.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.model.data.ScriptModel; -import io.jpom.util.CommandUtil; -import io.jpom.util.SocketSessionUtil; - -import javax.websocket.Session; -import java.io.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 脚本执行 - * - * @author jiangzeyin - * @date 2019/4/25 - */ -public class ScriptProcessBuilder implements Runnable { - private static final ConcurrentHashMap FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); - - private final ProcessBuilder processBuilder; - private final Set sessions = new HashSet<>(); - private final File logFile; - private final File scriptFile; - private Process process; - private InputStream inputStream; - private InputStream errorInputStream; - - private ScriptProcessBuilder(ScriptModel scriptModel, String args) { - this.logFile = scriptModel.logFile(); - this.scriptFile = scriptModel.getFile(true); - // - String script = FileUtil.getAbsolutePath(scriptFile); - processBuilder = new ProcessBuilder(); - List command = StrUtil.splitTrim(args, StrUtil.SPACE); - command.add(0, script); - if (SystemUtil.getOsInfo().isLinux() || SystemUtil.getOsInfo().isMac()) { - command.add(0, CommandUtil.SUFFIX); - } - DefaultSystemLog.getLog().info(CollUtil.join(command, StrUtil.SPACE)); - processBuilder.command(command); - } - - public static void addWatcher(ScriptModel scriptModel, String args, Session session) { - File file = scriptModel.getFile(true); - ScriptProcessBuilder scriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.computeIfAbsent(file, file1 -> { - ScriptProcessBuilder scriptProcessBuilder1 = new ScriptProcessBuilder(scriptModel, args); - ThreadUtil.execute(scriptProcessBuilder1); - return scriptProcessBuilder1; - }); - if (scriptProcessBuilder.sessions.add(session)) { - if (FileUtil.exist(scriptProcessBuilder.logFile)) { - // 读取之前的信息并发送 - FileUtil.readLines(scriptProcessBuilder.logFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line -> { - try { - SocketSessionUtil.send(session, line); - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - } - }); - } - } - } - - public static void stopWatcher(Session session) { - Collection scriptProcessBuilders = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.values(); - for (ScriptProcessBuilder scriptProcessBuilder : scriptProcessBuilders) { - Set sessions = scriptProcessBuilder.sessions; - sessions.removeIf(session1 -> session1.getId().equals(session.getId())); - } - } - - public static void stopRun(ScriptModel scriptModel) { - File file = scriptModel.getFile(true); - ScriptProcessBuilder scriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.get(file); - if (scriptProcessBuilder != null) { - scriptProcessBuilder.end("停止运行"); - } - } - - @Override - public void run() { - //初始化ProcessBuilder对象 - try { - process = processBuilder.start(); - { - inputStream = process.getInputStream(); - InputStreamReader inputStreamReader = new InputStreamReader(inputStream, JpomApplication.getCharset()); - BufferedReader results = new BufferedReader(inputStreamReader); - IoUtil.readLines(results, (LineHandler) ScriptProcessBuilder.this::handle); - } - { - errorInputStream = process.getErrorStream(); - InputStreamReader inputStreamReader = new InputStreamReader(errorInputStream, JpomApplication.getCharset()); - BufferedReader results = new BufferedReader(inputStreamReader); - IoUtil.readLines(results, (LineHandler) line -> ScriptProcessBuilder.this.handle("ERROR:" + line)); - } - JsonMessage jsonMessage = new JsonMessage<>(200, "执行完毕"); - JSONObject jsonObject = jsonMessage.toJson(); - jsonObject.put("op", ConsoleCommandOp.stop.name()); - this.end(jsonObject.toString()); - } catch (IORuntimeException ignored) { - - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行异常", e); - this.end("执行异常:" + e.getMessage()); - } - } - - /** - * 结束执行 - * - * @param msg 响应的消息 - */ - private void end(String msg) { - if (this.process != null) { - // windows 中不能正常关闭 - this.process.destroy(); - IoUtil.close(inputStream); - IoUtil.close(errorInputStream); - } - Iterator iterator = sessions.iterator(); - while (iterator.hasNext()) { - Session session = iterator.next(); - try { - SocketSessionUtil.send(session, msg); - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - } - iterator.remove(); - } - FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.remove(this.scriptFile); - } - - /** - * 响应 - * - * @param line 信息 - */ - private void handle(String line) { - // 写入文件 - List fileLine = new ArrayList<>(); - fileLine.add(line); - FileUtil.appendLines(fileLine, logFile, CharsetUtil.CHARSET_UTF_8); - Iterator iterator = sessions.iterator(); - while (iterator.hasNext()) { - Session session = iterator.next(); - try { - SocketSessionUtil.send(session, line); - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - iterator.remove(); - } - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/socket/spring/WebSocketConfig.java b/modules/agent/src/main/java/io/jpom/socket/spring/WebSocketConfig.java deleted file mode 100644 index 6634ff0fd6..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/spring/WebSocketConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.spring; -// -//import io.jpom.socket.spring.handler.NodeUpdateHandler; -//import io.jpom.socket.spring.interceptor.NodeUpdateInterceptor; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.web.socket.config.annotation.EnableWebSocket; -//import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -//import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; -// -///** -// * @author lf -// */ -//@Configuration -//@EnableWebSocket -//public class WebSocketConfig implements WebSocketConfigurer { -// private final NodeUpdateInterceptor nodeUpdateInterceptor = new NodeUpdateInterceptor(); -// -// @Override -// public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { -// // 节点升级 -// registry.addHandler(new NodeUpdateHandler(), "/node_update").addInterceptors(nodeUpdateInterceptor); -// } -//} diff --git a/modules/agent/src/main/java/io/jpom/socket/spring/handler/NodeUpdateHandler.java b/modules/agent/src/main/java/io/jpom/socket/spring/handler/NodeUpdateHandler.java deleted file mode 100644 index 950271580a..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/spring/handler/NodeUpdateHandler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.spring.handler; -// -//import cn.jiangzeyin.common.DefaultSystemLog; -//import com.alibaba.fastjson.JSONObject; -//import io.jpom.JpomApplication; -//import io.jpom.common.JpomManifest; -//import io.jpom.model.AgentFileModel; -//import io.jpom.model.WebSocketMessageModel; -//import io.jpom.model.data.UploadFileModel; -//import io.jpom.system.AgentConfigBean; -//import org.springframework.web.socket.BinaryMessage; -//import org.springframework.web.socket.TextMessage; -//import org.springframework.web.socket.WebSocketSession; -//import org.springframework.web.socket.handler.AbstractWebSocketHandler; -// -//import java.util.HashMap; -//import java.util.Map; -// -///** -// * 节点升级websocket处理器 -// * -// * @author lf -// */ -//public class NodeUpdateHandler extends AbstractWebSocketHandler { -// private static final Map UPLOAD_FILE_INFO = new HashMap<>(); -// -// @Override -// public void afterConnectionEstablished(WebSocketSession session) throws Exception { -// // 设置二进制消息的最大长度为1M -// session.setBinaryMessageSizeLimit(1024 * 1024); -// } -// -// @Override -// protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { -// -// } -// -// @Override -// protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { -// UploadFileModel uploadFileModel = UPLOAD_FILE_INFO.get(session.getId()); -// uploadFileModel.save(message.getPayload().array()); -// // 更新进度 -// WebSocketMessageModel model = new WebSocketMessageModel("updateNode", uploadFileModel.getId()); -// model.setData(uploadFileModel); -// session.sendMessage(new TextMessage(model.toString())); -// } -// -// /** -// * 重启 -// * -// * @param session -// * @return -// */ -// public String restart(WebSocketSession session) { -// String result = "重启中"; -// try { -// UploadFileModel uploadFile = UPLOAD_FILE_INFO.get(session.getId()); -// JpomManifest.releaseJar(uploadFile.getFilePath(), uploadFile.getVersion(), true); -// JpomApplication.restart(); -// } catch (RuntimeException e) { -// result = e.getMessage(); -// DefaultSystemLog.getLog().error("重启失败", e); -// } -// return result; -// } -//} diff --git a/modules/agent/src/main/java/io/jpom/socket/spring/interceptor/NodeUpdateInterceptor.java b/modules/agent/src/main/java/io/jpom/socket/spring/interceptor/NodeUpdateInterceptor.java deleted file mode 100644 index dfb63140bf..0000000000 --- a/modules/agent/src/main/java/io/jpom/socket/spring/interceptor/NodeUpdateInterceptor.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.spring.interceptor; -// -//import cn.jiangzeyin.common.DefaultSystemLog; -//import io.jpom.system.AgentAuthorize; -//import org.springframework.http.server.ServerHttpRequest; -//import org.springframework.http.server.ServerHttpResponse; -//import org.springframework.http.server.ServletServerHttpRequest; -//import org.springframework.web.socket.WebSocketHandler; -//import org.springframework.web.socket.server.HandshakeInterceptor; -// -//import javax.servlet.http.HttpServletRequest; -//import java.util.Map; -// -///** -// * @author lf -// */ -//public class NodeUpdateInterceptor implements HandshakeInterceptor { -// @Override -// public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map map) throws Exception { -// if (request instanceof ServletServerHttpRequest) { -// ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; -// HttpServletRequest httpServletRequest = serverHttpRequest.getServletRequest(); -// // 判断用户 -// String name = httpServletRequest.getParameter("name"); -// String password = httpServletRequest.getParameter("password"); -// -// AgentAuthorize authorize = AgentAuthorize.getInstance(); -// return authorize.getAgentName().equals(name) && authorize.getAgentPwd().equals(password); -// } -// return false; -// } -// -// @Override -// public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception exception) { -// if (exception != null) { -// DefaultSystemLog.getLog().error("afterHandshake", exception); -// } -// } -//} diff --git a/modules/agent/src/main/java/io/jpom/system/AgentAuthorize.java b/modules/agent/src/main/java/io/jpom/system/AgentAuthorize.java deleted file mode 100644 index d4403017dd..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/AgentAuthorize.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.system.AgentAutoUser; -import io.jpom.util.JsonFileUtil; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - -/** - * agent 端授权账号信息 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@Configuration -public class AgentAuthorize { - - private static AgentAuthorize agentAuthorize; - /** - * 账号 - */ - @Value("${" + ConfigBean.AUTHORIZE_USER_KEY + "}") - private String agentName; - /** - * 密码 - */ - @Value("${" + ConfigBean.AUTHORIZE_AUTHORIZE_KEY + ":}") - private String agentPwd; - /** - * 授权加密字符串 - */ - private String authorize; - - /** - * 单例 - * - * @return this - */ - public static AgentAuthorize getInstance() { - if (agentAuthorize == null) { - agentAuthorize = SpringUtil.getBean(AgentAuthorize.class); - // 登录名不能为空 - if (StrUtil.isEmpty(agentAuthorize.agentName)) { - throw new JpomRuntimeException("agent 端登录名不能为空"); - } - agentAuthorize.checkPwd(); - // 生成密码授权字符串 - agentAuthorize.authorize = SecureUtil.sha1(agentAuthorize.agentName + "@" + agentAuthorize.agentPwd); - } - return agentAuthorize; - } - - public String getAgentName() { - return agentName; - } - - public String getAgentPwd() { - return agentPwd; - } - - /** - * 判断授权是否正确 - * - * @param authorize 授权 - * @return true 正确 - */ - public boolean checkAuthorize(String authorize) { - return StrUtil.equals(authorize, this.authorize); - } - - /** - * 检查是否配置密码 - */ - private void checkPwd() { - String path = ConfigBean.getInstance().getAgentAutoAuthorizeFile(ConfigBean.getInstance().getDataPath()); - if (StrUtil.isNotEmpty(agentPwd)) { - // 有指定密码 清除旧密码信息 - FileUtil.del(path); - Console.log("已经自定义配置授权信息啦,账号:{}", this.agentName); - return; - } - if (FileUtil.exist(path)) { - // 读取旧密码 - try { - String json = FileUtil.readString(path, CharsetUtil.CHARSET_UTF_8); - AgentAutoUser autoUser = JSONObject.parseObject(json, AgentAutoUser.class); - String oldAgentPwd = autoUser.getAgentPwd(); - if (!StrUtil.equals(autoUser.getAgentName(), this.agentName)) { - throw new JpomRuntimeException("已经存在的登录名和配置的登录名不一致"); - } - if (StrUtil.isNotEmpty(oldAgentPwd)) { - this.agentPwd = oldAgentPwd; - Console.log("已有授权账号:{} 密码:{} 授权信息保存位置:{}", this.agentName, this.agentPwd, FileUtil.getAbsolutePath(path)); - return; - } - } catch (JpomRuntimeException e) { - throw e; - } catch (Exception ignored) { - } - } - this.agentPwd = RandomUtil.randomString(10); - AgentAutoUser autoUser = new AgentAutoUser(); - autoUser.setAgentName(this.agentName); - autoUser.setAgentPwd(this.agentPwd); - // 写入文件中 - JsonFileUtil.saveJson(path, autoUser.toJson()); - Console.log("已经自动生成授权账号:{} 密码:{} 授权信息保存位置:{}", this.agentName, this.agentPwd, FileUtil.getAbsolutePath(path)); - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/AgentConfigBean.java b/modules/agent/src/main/java/io/jpom/system/AgentConfigBean.java deleted file mode 100644 index 89faee7af5..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/AgentConfigBean.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.BaseAgentController; -import org.springframework.context.annotation.Configuration; - -import java.io.File; - -/** - * 插件端配置 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@Configuration -public class AgentConfigBean { - /** - * 白名单文件 - */ - public static final String WHITELIST_DIRECTORY = "whitelistDirectory.json"; - /** - * 项目数据文件 - */ - public static final String PROJECT = "project.json"; - - public static final String TOMCAT = "tomcat.json"; - /** - * 项目回收文件 - */ - public static final String PROJECT_RECOVER = "project_recover.json"; - /** - * 证书文件 - */ - public static final String CERT = "cert.json"; - /** - * 脚本管理数据文件 - */ - public static final String SCRIPT = "script.json"; - /** - * 脚本模板存放路径 - */ - public static final String SCRIPT_DIRECTORY = "script"; - - /** - * Server 端的信息 - */ - public static final String SERVER_ID = "SERVER.json"; - - /** - * nginx配置信息 - */ - public static final String NGINX_CONF = "nginx_conf.json"; - - /** - * jdk列表信息 - */ - public static final String JDK_CONF = "jdk_conf.json"; - - private static AgentConfigBean agentConfigBean; - - /** - * 单利模式 - * - * @return config - */ - public static AgentConfigBean getInstance() { - if (agentConfigBean == null) { - agentConfigBean = SpringUtil.getBean(AgentConfigBean.class); - } - return agentConfigBean; - } - - /** - * 获取当前登录用户的临时文件存储路径,如果没有登录则抛出异常 - * - * @return 文件夹 - */ - public String getTempPathName() { - File file = getTempPath(); - return FileUtil.normalize(file.getPath()); - } - - /** - * 获取当前登录用户的临时文件存储路径,如果没有登录则抛出异常 - * - * @return file - */ - public File getTempPath() { - File file = ConfigBean.getInstance().getTempPath(); - String userName = BaseAgentController.getNowUserName(); - if (StrUtil.isEmpty(userName)) { - throw new JpomRuntimeException("没有登录"); - } - file = new File(file, userName); - FileUtil.mkdir(file); - return file; - } - - /** - * 获取脚本模板路径 - * - * @return file - */ - public File getScriptPath() { - return FileUtil.file(ConfigBean.getInstance().getDataPath(), SCRIPT_DIRECTORY); - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/AgentExtConfigBean.java b/modules/agent/src/main/java/io/jpom/system/AgentExtConfigBean.java deleted file mode 100644 index 3c5c2842ad..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/AgentExtConfigBean.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.net.NetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.ServerOpenApi; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - -/** - * agent 端外部配置 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@Configuration -public class AgentExtConfigBean { - - private static AgentExtConfigBean agentExtConfigBean; - /** - * 白名单路径是否判断包含关系 - */ - @Value("${whitelistDirectory.checkStartsWith:true}") - public boolean whitelistDirectoryCheckStartsWith; - - /** - * 自动备份控制台日志,防止日志文件过大,目前暂只支持linux 不停服备份 如果配置none 则不自动备份 默认10分钟扫描一次 - */ - @Value("${log.autoBackConsoleCron:0 0/10 * * * ?}") - public String autoBackConsoleCron; - /** - * 当文件多大时自动备份 - * - * @see ch.qos.logback.core.util.FileSize - */ - @Value("${log.autoBackSize:50MB}") - public String autoBackSize; - /** - * 控制台日志保存时长单位天 - */ - @Value("${log.saveDays:7}") - private int logSaveDays; - - @Value("${jpom.agent.id:}") - private String agentId; - - @Value("${jpom.agent.url:}") - private String agentUrl; - - @Value("${jpom.server.url:}") - private String serverUrl; - - @Value("${jpom.server.token:}") - private String serverToken; - - /** - * 项目状态禁用 调用jmx获取 - */ - @Value("${project.disableVirtualMachine:false}") - private boolean disableVirtualMachine; - - /** - * 停止项目等待的时长 单位秒,最小为1秒 - */ - @Value("${project.stopWaitTime:10}") - private int stopWaitTime; - - public int getStopWaitTime() { - return stopWaitTime; - } - - public boolean isDisableVirtualMachine() { - return disableVirtualMachine; - } - - public String getAgentId() { - return agentId; - } - - public String getServerUrl() { - return serverUrl; - } - - public String getServerToken() { - return serverToken; - } - - /** - * 获取当前的url - * - * @return 如果没有配置将自动生成:http://+本地IP+端口 - */ - public String getAgentUrl() { - if (StrUtil.isEmpty(agentUrl)) { - String localhostStr = NetUtil.getLocalhostStr(); - int port = ConfigBean.getInstance().getPort(); - agentUrl = String.format("http://%s:%s", localhostStr, port); - } - if (StrUtil.isEmpty(agentUrl)) { - throw new JpomRuntimeException("获取Agent url失败"); - } - return agentUrl; - } - - /** - * 创建请求对象 - * - * @param openApi url - * @return HttpRequest - * @see ServerOpenApi - */ - public HttpRequest createServerRequest(String openApi) { - if (StrUtil.isEmpty(getServerUrl())) { - throw new JpomRuntimeException("请先配置server端url"); - } - if (StrUtil.isEmpty(getServerToken())) { - throw new JpomRuntimeException("请先配置server端Token"); - } - // 加密 - String md5 = SecureUtil.md5(getServerToken()); - md5 = SecureUtil.sha1(md5 + ServerOpenApi.HEAD); - HttpRequest httpRequest = HttpUtil.createPost(String.format("%s%s", serverUrl, openApi)); - httpRequest.header(ServerOpenApi.HEAD, md5); - return httpRequest; - } - - /** - * 配置错误或者没有,默认是7天 - * - * @return int - */ - public int getLogSaveDays() { - if (logSaveDays <= 0) { - return 7; - } - return logSaveDays; - } - - /** - * 是否开启日志备份 - * - * @return 如果表达式配置为none 则不配置,重启也不备份 - */ - public boolean openLogBack() { - String cron = StrUtil.emptyToDefault(autoBackConsoleCron, "none"); - return !"none".equalsIgnoreCase(cron.trim()); - } - - /** - * 单例 - * - * @return this - */ - public static AgentExtConfigBean getInstance() { - if (agentExtConfigBean == null) { - agentExtConfigBean = SpringUtil.getBean(AgentExtConfigBean.class); - } - return agentExtConfigBean; - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/AgentLogbackConfig.java b/modules/agent/src/main/java/io/jpom/system/AgentLogbackConfig.java new file mode 100644 index 0000000000..ee4afc8581 --- /dev/null +++ b/modules/agent/src/main/java/io/jpom/system/AgentLogbackConfig.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package io.jpom.system; + +/** + * @author bwcx_jzy + * @since 2023/3/31 + */ +@Deprecated +public class AgentLogbackConfig extends org.dromara.jpom.system.AgentLogbackConfig { +} diff --git a/modules/agent/src/main/java/io/jpom/system/TopManager.java b/modules/agent/src/main/java/io/jpom/system/TopManager.java deleted file mode 100644 index 870e39fb02..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/TopManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.cache.impl.CacheObj; -import cn.hutool.cache.impl.TimedCache; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.commander.AbstractSystemCommander; - -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.concurrent.TimeUnit; - -/** - * top命令管理,保证整个服务器只获取一个top命令 - * - * @author jiangzeyin - * @date 2018/10/2 - */ -public class TopManager { - /** - * 最近30分钟监控数据 - */ - private static final TimedCache MONITOR_CACHE = new TimedCache<>(TimeUnit.MINUTES.toMillis(30), new LinkedHashMap<>()); - - public static Iterator> get() { - JSONObject topInfo = AbstractSystemCommander.getInstance().getAllMonitor(); - if (topInfo != null) { - DateTime date = DateUtil.date(); - String time = DateUtil.formatTime(date); - topInfo.put("time", time); - topInfo.put("monitorTime", date.getTime()); - MONITOR_CACHE.put(time, topInfo); - } - return MONITOR_CACHE.cacheObjIterator(); - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/init/AutoBackLog.java b/modules/agent/src/main/java/io/jpom/system/init/AutoBackLog.java deleted file mode 100644 index e4d10abe71..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/init/AutoBackLog.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import ch.qos.logback.core.util.FileSize; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.cron.CronUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.manage.ProjectInfoService; -import io.jpom.system.AgentExtConfigBean; -import io.jpom.util.CronUtils; - -import java.io.File; -import java.util.List; - -/** - * 自动备份控制台日志,防止日志文件过大 - * - * @author jiangzeyin - * @date 2019/3/17 - */ -@PreLoadClass -public class AutoBackLog { - - private static final String ID = "auto_back_log"; - private static ProjectInfoService projectInfoService; - - private static FileSize MAX_SIZE; - - @PreLoadMethod - private static void startAutoBackLog() { - if (projectInfoService == null) { - projectInfoService = SpringUtil.getBean(ProjectInfoService.class); - } - // 获取cron 表达式 - String cron = StrUtil.emptyToDefault(AgentExtConfigBean.getInstance().autoBackConsoleCron, "none"); - if ("none".equalsIgnoreCase(cron.trim())) { - //DefaultSystemLog.getLog().info("没有配置自动备份控制台日志表达式"); - //return; - cron = "0 0/10 * * * ?"; - } - String size = StrUtil.emptyToDefault(AgentExtConfigBean.getInstance().autoBackSize, "50MB"); - MAX_SIZE = FileSize.valueOf(size.trim()); - // - CronUtil.schedule(ID, cron, () -> { - try { - List list = projectInfoService.list(); - if (list == null) { - return; - } - list.forEach(projectInfoModel -> { - checkProject(projectInfoModel, null); - // - List javaCopyItemList = projectInfoModel.getJavaCopyItemList(); - if (javaCopyItemList == null) { - return; - } - javaCopyItemList.forEach(javaCopyItem -> checkProject(projectInfoModel, javaCopyItem)); - }); - } catch (Exception e) { - DefaultSystemLog.getLog().error("定时备份日志失败", e); - } - }); - CronUtils.start(); - } - - private static void checkProject(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel.JavaCopyItem javaCopyItem) { - File file = javaCopyItem == null ? new File(nodeProjectInfoModel.getLog()) : nodeProjectInfoModel.getLog(javaCopyItem); - if (!file.exists()) { - return; - } - long len = file.length(); - if (len > MAX_SIZE.getSize()) { - try { - AbstractProjectCommander.getInstance().backLog(nodeProjectInfoModel, javaCopyItem); - } catch (Exception ignored) { - } - } - // 清理过期的文件 - File logFile = javaCopyItem == null ? nodeProjectInfoModel.getLogBack() : nodeProjectInfoModel.getLogBack(javaCopyItem); - 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 = AgentExtConfigBean.getInstance().getLogSaveDays(); - return days > saveDays; - }); - files.forEach(FileUtil::del); - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java b/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java deleted file mode 100644 index 9892b0e099..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.date.DateTime; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.ServerOpenApi; -import io.jpom.system.AgentAuthorize; -import io.jpom.system.AgentConfigBean; -import io.jpom.system.AgentExtConfigBean; -import io.jpom.system.ConfigBean; -import io.jpom.util.JsonFileUtil; - -import java.io.File; -import java.io.FileNotFoundException; -import java.net.URL; - -/** - * 自动注册server 节点 - * - * @author bwcx_jzy - * @date 2019/8/6 - */ -@PreLoadClass -public class AutoRegSeverNode { - - @PreLoadMethod - private static void reg() throws FileNotFoundException { - String agentId = AgentExtConfigBean.getInstance().getAgentId(); - String serverUrl = AgentExtConfigBean.getInstance().getServerUrl(); - if (StrUtil.isEmpty(agentId) || StrUtil.isEmpty(serverUrl)) { - // 如果二者缺一不注册 - return; - } - String oldInstallId = null; - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), AgentConfigBean.SERVER_ID); - JSONObject serverJson = null; - if (file.exists()) { - serverJson = (JSONObject) JsonFileUtil.readJson(file.getAbsolutePath()); - oldInstallId = serverJson.getString("installId"); - } - HttpRequest installRequest = AgentExtConfigBean.getInstance().createServerRequest(ServerOpenApi.INSTALL_ID); - String body1 = installRequest.execute().body(); - JsonMessage jsonMessage = JSON.parseObject(body1, JsonMessage.class); - if (jsonMessage.getCode() != HttpStatus.HTTP_OK) { - DefaultSystemLog.getLog().error("获取Server 安装id失败:" + jsonMessage); - return; - } - String installId = jsonMessage.dataToString(); - boolean eqInstall = StrUtil.equals(oldInstallId, installId); - // - URL url = URLUtil.toUrlForHttp(AgentExtConfigBean.getInstance().getAgentUrl()); - String protocol = url.getProtocol(); - - HttpRequest serverRequest = AgentExtConfigBean.getInstance().createServerRequest(ServerOpenApi.UPDATE_NODE_INFO); - serverRequest.form("id", agentId); - serverRequest.form("name", "节点:" + agentId); - serverRequest.form("openStatus", 1); - serverRequest.form("protocol", protocol); - serverRequest.form("url", url.getHost() + ":" + url.getPort()); - serverRequest.form("loginName", AgentAuthorize.getInstance().getAgentName()); - serverRequest.form("loginPwd", AgentAuthorize.getInstance().getAgentPwd()); - serverRequest.form("type", eqInstall ? "update" : "add"); - String body = serverRequest.execute().body(); - DefaultSystemLog.getLog().info("自动注册Server:" + body); - JsonMessage regJsonMessage = JSON.parseObject(body, JsonMessage.class); - if (regJsonMessage.getCode() == HttpStatus.HTTP_OK) { - if (serverJson == null) { - serverJson = new JSONObject(); - } - if (!eqInstall) { - serverJson.put("installId", installId); - serverJson.put("regTime", DateTime.now().toString()); - } else { - serverJson.put("updateTime", DateTime.now().toString()); - } - JsonFileUtil.saveJson(file.getAbsolutePath(), serverJson); - } else { - DefaultSystemLog.getLog().error("自动注册插件端失败:{}", body); - } - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/init/AutoStartProject.java b/modules/agent/src/main/java/io/jpom/system/init/AutoStartProject.java deleted file mode 100644 index 39ea35fc21..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/init/AutoStartProject.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.commander.AbstractProjectCommander; -import io.jpom.model.data.NodeProjectInfoModel; -import io.jpom.service.manage.ProjectInfoService; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 自动启动项目 - * - * @author bwcx_jzy - * @since 2021/12/10 - */ -@PreLoadClass -public class AutoStartProject { - - @PreLoadMethod - private static void start() { - ProjectInfoService projectInfoService = SpringUtil.getBean(ProjectInfoService.class); - List list = projectInfoService.list(); - if (CollUtil.isEmpty(list)) { - return; - } - list = list.stream().filter(nodeProjectInfoModel -> nodeProjectInfoModel.getAutoStart() != null && nodeProjectInfoModel.getAutoStart()).collect(Collectors.toList()); - List finalList = list; - ThreadUtil.execute(() -> { - AbstractProjectCommander instance = AbstractProjectCommander.getInstance(); - for (NodeProjectInfoModel nodeProjectInfoModel : finalList) { - try { - if (!instance.isRun(nodeProjectInfoModel, null)) { - instance.start(nodeProjectInfoModel, null); - } - List javaCopyItemList = nodeProjectInfoModel.getJavaCopyItemList(); - if (javaCopyItemList != null) { - for (NodeProjectInfoModel.JavaCopyItem javaCopyItem : javaCopyItemList) { - if (!instance.isRun(nodeProjectInfoModel, javaCopyItem)) { - instance.start(nodeProjectInfoModel, javaCopyItem); - } - } - } - } catch (Exception e) { - DefaultSystemLog.getLog().warn("自动启动项目失败:{} {}", nodeProjectInfoModel.getId(), e.getMessage()); - } - } - }); - } -} diff --git a/modules/agent/src/main/java/io/jpom/system/init/CheckAuthorize.java b/modules/agent/src/main/java/io/jpom/system/init/CheckAuthorize.java deleted file mode 100644 index 7438d10d85..0000000000 --- a/modules/agent/src/main/java/io/jpom/system/init/CheckAuthorize.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import io.jpom.common.JpomManifest; -import io.jpom.system.AgentAuthorize; -import io.jpom.system.AgentConfigBean; -import io.jpom.system.ConfigBean; -import io.jpom.system.ExtConfigBean; - -import java.io.File; - -/** - * 检查授权信息 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -@PreLoadClass -public class CheckAuthorize { - - @PreLoadMethod - private static void startAutoBackLog() { - AgentAuthorize.getInstance(); - } - - /** - * 修护脚本模板路径 - */ - @PreLoadMethod - private static void repairScriptPath() { - if (!JpomManifest.getInstance().isDebug()) { - if (StrUtil.compareVersion(JpomManifest.getInstance().getVersion(), "2.4.2") < 0) { - return; - } - } - File oldDir = FileUtil.file(ExtConfigBean.getInstance().getPath(), AgentConfigBean.SCRIPT_DIRECTORY); - if (!oldDir.exists()) { - return; - } - File newDir = FileUtil.file(ConfigBean.getInstance().getDataPath(), AgentConfigBean.SCRIPT_DIRECTORY); - FileUtil.move(oldDir, newDir, true); - } -} diff --git a/modules/agent/src/main/java/io/jpom/util/CompressionFileUtil.java b/modules/agent/src/main/java/io/jpom/util/CompressionFileUtil.java deleted file mode 100644 index b81d4217a8..0000000000 --- a/modules/agent/src/main/java/io/jpom/util/CompressionFileUtil.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.extra.compress.CompressUtil; -import cn.hutool.extra.compress.extractor.Extractor; -import org.apache.commons.compress.compressors.CompressorInputStream; -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; -import org.apache.commons.compress.compressors.bzip2.BZip2Utils; -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; -import org.apache.commons.compress.compressors.gzip.GzipUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.nio.charset.Charset; - -/** - * 压缩文件工具 - * - * @author bwcx_jzy - */ -public class CompressionFileUtil { - - /** - * 解压文件 - * - * @param compressFile 压缩文件 - * @param destDir 解压到的文件夹 - */ - public static void unCompress(File compressFile, File destDir) { - Charset charset = CharsetUtil.CHARSET_GBK; - charset = ObjectUtil.defaultIfNull(charset, CharsetUtil.defaultCharset()); - try { - try (Extractor extractor = CompressUtil.createExtractor(charset, compressFile)) { - extractor.extract(destDir); - } - } catch (Exception e) { - CompressorInputStream compressUtilIn = null; - FileInputStream fileInputStream = null; - try { - fileInputStream = new FileInputStream(compressFile); - compressUtilIn = CompressUtil.getIn(null, fileInputStream); - if (compressUtilIn instanceof BZip2CompressorInputStream) { - File file = FileUtil.file(destDir, BZip2Utils.getUncompressedFilename(compressFile.getName())); - IoUtil.copy(compressUtilIn, new FileOutputStream(file)); - } else if (compressUtilIn instanceof GzipCompressorInputStream) { - File file = FileUtil.file(destDir, GzipUtils.getUncompressedFilename(compressFile.getName())); - IoUtil.copy(compressUtilIn, new FileOutputStream(file)); - } else { - try (Extractor extractor = CompressUtil.createExtractor(charset, compressUtilIn)) { - extractor.extract(destDir); - } - } - } catch (Exception e2) { - // - e2.addSuppressed(e); - // - throw new RuntimeException(e2); - } finally { - IoUtil.close(fileInputStream); - IoUtil.close(compressUtilIn); - } - } - } - - -} diff --git a/modules/agent/src/main/java/io/jpom/util/FileUtils.java b/modules/agent/src/main/java/io/jpom/util/FileUtils.java deleted file mode 100644 index 09129c903c..0000000000 --- a/modules/agent/src/main/java/io/jpom/util/FileUtils.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import java.io.File; - -/** - * 文件工具 - * - * @author jiangzeyin - * @date 2019/4/28 - */ -public class FileUtils { - - private static JSONObject fileToJson(File file) { - JSONObject jsonObject = new JSONObject(6); - if (file.isDirectory()) { - jsonObject.put("isDirectory", true); - long sizeFile = FileUtil.size(file); - jsonObject.put("fileSize", FileUtil.readableFileSize(sizeFile)); - } else { - jsonObject.put("fileSize", FileUtil.readableFileSize(file.length())); - } - jsonObject.put("filename", file.getName()); - long mTime = file.lastModified(); - jsonObject.put("modifyTimeLong", mTime); - jsonObject.put("modifyTime", DateUtil.date(mTime).toString()); - return jsonObject; - } - - /** - * 对文件信息解析排序 - * - * @param files 文件数组 - * @param time 是否安装时间排序 - * @param startPath 开始路径 - * @return 排序后的json - */ - public static JSONArray parseInfo(File[] files, boolean time, String startPath) { - if (files == null) { - return new JSONArray(); - } - int size = files.length; - JSONArray arrayFile = new JSONArray(size); - for (File file : files) { - JSONObject jsonObject = FileUtils.fileToJson(file); - // - if (startPath != null) { - String levelName = StringUtil.delStartPath(file, startPath, false); - jsonObject.put("levelName", levelName); - } - // - arrayFile.add(jsonObject); - } - arrayFile.sort((o1, o2) -> { - JSONObject jsonObject1 = (JSONObject) o1; - JSONObject jsonObject2 = (JSONObject) o2; - if (time) { - return jsonObject2.getLong("modifyTimeLong").compareTo(jsonObject1.getLong("modifyTimeLong")); - } - return jsonObject1.getString("filename").compareTo(jsonObject2.getString("filename")); - }); - final int[] i = {0}; - arrayFile.forEach(o -> { - JSONObject jsonObject = (JSONObject) o; - jsonObject.put("index", ++i[0]); - }); - return arrayFile; - } - - /** - * 判断路径是否满足jdk 条件 - * - * @param path 路径 - * @return 判断存在java文件 - */ - public static boolean isJdkPath(String path) { - String fileName = getJdkJavaPath(path, false); - File newPath = new File(fileName); - return newPath.exists() && newPath.isFile(); - } - - /** - * 获取java 文件路径 - * - * @param path path - * @param w 是否使用javaw - * @return 完整路径 - */ - public static String getJdkJavaPath(String path, boolean w) { - String fileName; - if (SystemUtil.getOsInfo().isWindows()) { - fileName = w ? "javaw.exe" : "java.exe"; - } else { - fileName = w ? "javaw" : "java"; - } - File newPath = FileUtil.file(path, "bin", fileName); - return FileUtil.getAbsolutePath(newPath); - } - - /** - * 获取jdk 版本 - * - * @param path jdk 路径 - * @return 获取成功返回版本号 - */ - public static String getJdkVersion(String path) { - String newPath = getJdkJavaPath(path, false); - if (path.contains(StrUtil.SPACE)) { - newPath = String.format("\"%s\"", newPath); - } - String command = CommandUtil.execSystemCommand(newPath + " -version"); - String[] split = StrUtil.splitToArray(command, StrUtil.LF); - if (split == null || split.length <= 0) { - return null; - } - String[] strings = StrUtil.splitToArray(split[0], "\""); - if (strings == null || strings.length <= 1) { - return null; - } - return strings[1]; - } -} diff --git a/modules/agent/src/main/java/org/dromara/jpom/JpomAgentApplication.java b/modules/agent/src/main/java/org/dromara/jpom/JpomAgentApplication.java new file mode 100644 index 0000000000..9a692d23f5 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/JpomAgentApplication.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.JpomAppType; +import cn.keepbx.jpom.Type; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.AgentStartInit; +import org.springframework.boot.Banner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * jpom 启动类 + * + * @author bwcx_jzy + * @since 2017/9/14. + */ +@SpringBootApplication(scanBasePackages = {"org.dromara.jpom"}) +@ServletComponentScan(basePackages = {"org.dromara.jpom"}) +@Slf4j +@JpomAppType(Type.Agent) +public class JpomAgentApplication { + + /** + * 启动执行 + * + * @param args 参数 + * @throws Exception 异常 + */ + public static void main(String[] args) throws Exception { + long time = SystemClock.now(); + SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(JpomAgentApplication.class); + springApplicationBuilder.bannerMode(Banner.Mode.LOG); + springApplicationBuilder.run(args); + // 自动向服务端推送 + autoPushToServer(args); + log.info("Time-consuming to start this time:{}", DateUtil.formatBetween(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND)); + } + + /** + * 自动推送 插件端信息到服务端 + * + * @param args 参数 + */ + private static void autoPushToServer(String[] args) { + int i = ArrayUtil.indexOf(args, ServerOpenApi.PUSH_NODE_KEY); + if (i == ArrayUtil.INDEX_NOT_FOUND) { + return; + } + String arg = ArrayUtil.get(args, i + 1); + if (StrUtil.isEmpty(arg)) { + log.error("not found auto-push-to-server url"); + return; + } + try { + AgentStartInit autoRegSeverNode = SpringUtil.getBean(AgentStartInit.class); + autoRegSeverNode.autoPushToServer(arg); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.push_registration_to_server_failed.5949"), arg, e); + } + } + +} 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 new file mode 100644 index 0000000000..08af50067e --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/AgentConst.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +/** + * 插件端配置 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +public class AgentConst { + /** + * 授权文件 + */ + public static final String WHITELIST_DIRECTORY = "whitelistDirectory.json"; + /** + * 项目数据文件 + */ + public static final String PROJECT = "project.json"; + /** + * 脚本管理数据文件 + */ + public static final String SCRIPT = "script.json"; + /** + * 脚本管理执行记录数据文件 + */ + public static final String SCRIPT_LOG = "script_log.json"; + /** + * 环境变量列表信息 + */ + public static final String WORKSPACE_ENV_VAR = "workspace_env_var.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 new file mode 100644 index 0000000000..313e24dfca --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/AgentExceptionHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.exception.BaseExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理 + * + * @author bwcx_jzy + * @since 2019/04/17 + */ +@RestControllerAdvice +@Slf4j +public class AgentExceptionHandler extends BaseExceptionHandler { + + +} 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 new file mode 100644 index 0000000000..8e303628f8 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/BaseAgentController.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.extra.servlet.ServletUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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; + +/** + * agent 端 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +public abstract class BaseAgentController extends BaseJpomController { + + @Resource + protected ProjectInfoService projectInfoService; + + + /** + * 获取server 端操作人 + * + * @param request req + * @return name + */ + private static String getUserName(HttpServletRequest request) { + String name = ServletUtil.getHeaderIgnoreCase(request, Const.JPOM_SERVER_USER_NAME); + name = CharsetUtil.convert(name, CharsetUtil.CHARSET_ISO_8859_1, CharsetUtil.CHARSET_UTF_8); + name = StrUtil.emptyToDefault(name, StrUtil.DASHED); + return URLUtil.decode(name, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 获取server 端操作人 + * + * @return name + */ + public static String getNowUserName() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (servletRequestAttributes == null) { + return StrUtil.DASHED; + } + HttpServletRequest request = servletRequestAttributes.getRequest(); + return getUserName(request); + } + + protected String getWorkspaceId() { + return ServletUtil.getHeader(getRequest(), Const.WORKSPACE_ID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 获取拦截器中缓存的项目信息 + * + * @return NodeProjectInfoModel + */ + protected NodeProjectInfoModel getProjectInfoModel() { + String id = getParameter("id"); + NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(id); + Assert.notNull(nodeProjectInfoModel, I18nMessageUtil.get("i18n.get_project_info_failure.ddff") + id); + return nodeProjectInfoModel; + } + + /** + * 根据 项目ID 获取项目信息 + * + * @return NodeProjectInfoModel + */ + protected NodeProjectInfoModel getProjectInfoModel(String id) { + NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(id); + Assert.notNull(nodeProjectInfoModel, I18nMessageUtil.get("i18n.get_project_info_failure.ddff") + id); + return nodeProjectInfoModel; + } + + protected NodeProjectInfoModel tryGetProjectInfoModel() { + String id = getParameter("id"); + return tryGetProjectInfoModel(id); + } + + protected NodeProjectInfoModel tryGetProjectInfoModel(String id) { + if (StrUtil.isNotEmpty(id)) { + return projectInfoService.getItem(id); + } + 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 new file mode 100644 index 0000000000..8089a8811e --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/WebConfigurer.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import org.dromara.jpom.common.interceptor.AuthorizeInterceptor; +import org.dromara.jpom.common.validator.ParameterInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author bwcx_jzy + * @since 2022/12/8 + */ +@Configuration +public class WebConfigurer implements WebMvcConfigurer { + + private final ParameterInterceptor parameterInterceptor; + private final AuthorizeInterceptor authorizeInterceptor; + + public WebConfigurer(ParameterInterceptor parameterInterceptor, + AuthorizeInterceptor authorizeInterceptor) { + this.parameterInterceptor = parameterInterceptor; + this.authorizeInterceptor = authorizeInterceptor; + } + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + + registry.addInterceptor(parameterInterceptor).addPathPatterns("/**"); + registry.addInterceptor(authorizeInterceptor).addPathPatterns("/**"); + } + + +} 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 new file mode 100644 index 0000000000..e5b5a297c4 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractProjectCommander.java @@ -0,0 +1,909 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.unit.DataSizeUtil; +import cn.hutool.core.lang.JarClassLoader; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Tuple; +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.system.SystemUtil; +import cn.keepbx.jpom.plugins.IPlugin; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.ProjectConfig; +import org.dromara.jpom.configuration.ProjectLogConfig; +import org.dromara.jpom.exception.IllegalArgument2Exception; +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.service.manage.ProjectInfoService; +import org.dromara.jpom.service.script.DslScriptServer; +import org.dromara.jpom.socket.AgentFileTailWatcher; +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; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Collectors; + +/** + * 项目命令执行基类 + * + * @author bwcx_jzy + */ +@Slf4j +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"); + + + /** + * 进程Id 获取端口号 + */ + public static final Map> PID_PORT = new SafeConcurrentHashMap<>(); + + 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, + 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; + } + + + //---------------------------------------------------- 基本操作----start + + /** + * 生成可以执行的命令 + * + * @param nodeProjectInfoModel 项目 + * @return null 是条件不足 + */ + 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())) { +// return w ? "javaw" : "java"; +// } +// +// if (item == null) { + return w ? "javaw" : "java"; +// } +// String jdkJavaPath = FileUtils.getJdkJavaPath(item.getPath(), w); +// if (jdkJavaPath.contains(StrUtil.SPACE)) { +// jdkJavaPath = String.format("\"%s\"", jdkJavaPath); +// } +// return jdkJavaPath; + } + + /** + * 启动 + * + * @param nodeProjectInfoModel 项目 + * @param sync dsl 是否同步执行 + * @return 结果 + */ + 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 = originalModel.getRunMode(); + if (runMode == RunMode.Dsl) { + // + DslYmlDto mustDslConfig = originalModel.mustDslConfig(); + try { + dslScriptServer.run(mustDslConfig, ConsoleCommandOp.start, nodeProjectInfoModel, originalModel, sync); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + + } else { + String command = this.buildRunCommand(nodeProjectInfoModel, originalModel); + if (command == null) { + return CommandOpResult.of(false, I18nMessageUtil.get("i18n.no_command_to_execute.340b")); + } + Map env = projectInfoService.getEnv(nodeProjectInfoModel.getWorkspaceId()); + // 执行命令 + I18nThreadUtil.execute(() -> { + try { + File file = projectInfoService.resolveLibFile(nodeProjectInfoModel); + if (SystemUtil.getOsInfo().isWindows()) { + CommandUtil.execSystemCommand(command, file, env); + } else { + CommandUtil.asyncExeLocalCommand(command, file, env); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.command_execution_failed.90ef"), e); + } + }); + } + // + this.loopCheckRun(nodeProjectInfoModel, originalModel, true); + CommandOpResult status = this.status(nodeProjectInfoModel, originalModel); + this.asyncWebHooks(nodeProjectInfoModel, originalModel, "start", "result", status.msgStr()); + return status; + } + +// private T runDsl(NodeProjectInfoModel nodeProjectInfoModel, String opt, BiFunction function) { +// DslYmlDto mustDslConfig = nodeProjectInfoModel.mustDslConfig(); +// DslYmlDto.BaseProcess process = mustDslConfig.getDslProcess(opt); +// return function.apply(process, mustDslConfig); +// } + + /** + * 查询出指定端口信息 + * + * @param pid 进程id + * @param listening 是否只获取检查状态的 + * @return 数组 + */ + protected abstract List listNetstat(int pid, boolean listening); + + + /** + * 停止 + * + * @param nodeProjectInfoModel 项目 + * @param sync dsl 是否同步执行 + * @return 结果 + */ + protected CommandOpResult stop(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, boolean sync) { + RunMode runMode = nodeProjectInfoModel.getRunMode(); + if (runMode == RunMode.File) { + return CommandOpResult.of(true, I18nMessageUtil.get("i18n.file_type_no_stop.00ff")); + } + + Tuple tuple = this.stopBefore(nodeProjectInfoModel, originalModel); + CommandOpResult status = tuple.get(1); + String webHook = tuple.get(0); + if (status.isSuccess()) { + // 运行中 + if (runMode == RunMode.Dsl) { + // + DslYmlDto config = originalModel.mustDslConfig(); + try { + dslScriptServer.run(config, ConsoleCommandOp.stop, nodeProjectInfoModel, originalModel, sync); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + 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, originalModel, status.getPid()).appendMsg(status.getMsgs()).appendMsg(webHook); + } + } + return CommandOpResult.of(true). + appendMsg(status.getMsgs()). + appendMsg(webHook); + } + + /** + * 停止 + * + * @param nodeProjectInfoModel 项目 + * @param pid 进程ID + * @return 结果 + */ + protected abstract CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int pid); + + /** + * 停止之前 + * + * @param nodeProjectInfoModel 项目 + * @return 结果 + */ + private Tuple stopBefore(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) { + String beforeStop = this.webHooks(nodeProjectInfoModel.token(), nodeProjectInfoModel, "beforeStop"); + // 再次查看进程信息 + CommandOpResult result = this.status(nodeProjectInfoModel, originalModel); + if (result.isSuccess()) { + // 端口号缓存 + PID_PORT.remove(result.getPid()); + } + this.asyncWebHooks(nodeProjectInfoModel, originalModel, "stop", "result", result); + return new Tuple(StrUtil.emptyToDefault(beforeStop, StrUtil.EMPTY), result); + } + + /** + * 执行 webhooks 通知 + * + * @param nodeProjectInfoModel 项目信息 + * @param type 类型 + * @param other 其他参数 + */ + public void asyncWebHooks(NodeProjectInfoModel nodeProjectInfoModel, + + String type, Object... other) { + 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 -> + I18nThreadUtil.execute(() -> { + try { + String result = this.webHooks(s, nodeProjectInfoModel, type, other); + Optional.ofNullable(result).ifPresent(s1 -> log.debug(I18nMessageUtil.get("i18n.trigger_result.364e"), 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) { + // 需要执行重载事件 + I18nThreadUtil.execute(() -> { + try { + CommandOpResult reload = this.reload(nodeProjectInfoModel, originalModel); + log.info(I18nMessageUtil.get("i18n.trigger_project_reload_event.a7dc"), reload); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.reload_project_exception.b566"), e); + } + }); + } + } + } + } + } + + /** + * 执行 webhooks 通知 + * + * @param nodeProjectInfoModel 项目信息 + * @param type 类型 + * @param other 其他参数 + * @return 结果 + */ + 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]); + } + try { + Object execute = plugin.execute(token, map); + return Convert.toStr(execute, StrUtil.EMPTY); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + /** + * 重启 + * + * @param nodeProjectInfoModel 项目 + * @return 结果 + */ + protected CommandOpResult restart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) { + RunMode runMode = originalModel.getRunMode(); + if (runMode == RunMode.File) { + return CommandOpResult.of(true, I18nMessageUtil.get("i18n.file_type_no_restart.0977")); + } + this.asyncWebHooks(nodeProjectInfoModel, originalModel, "beforeRestart"); + if (runMode == RunMode.Dsl) { + DslYmlDto dslConfig = originalModel.mustDslConfig(); + String opt = ConsoleCommandOp.restart.name(); + DslYmlDto.BaseProcess dslProcess = dslConfig.tryDslProcess(opt); + if (dslProcess != null) { + // 如果存在自定义 restart 流程 + try { + dslScriptServer.run(dslConfig, ConsoleCommandOp.restart, nodeProjectInfoModel, originalModel, false); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + // 等待 状态成功 + 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, originalModel); + CommandOpResult stopMsg = null; + if (run) { + stopMsg = this.stop(nodeProjectInfoModel, originalModel, true); + } + CommandOpResult startMsg = this.start(nodeProjectInfoModel, originalModel, false); + if (stopMsg != null) { + startMsg.appendMsg(stopMsg.getMsgs()); + } + return startMsg; + } + + /** + * 启动项目前基本检查 + * + * @param nodeProjectInfoModel 项目 + * @return null 检查一切正常 + */ + private String checkStart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) { + CommandOpResult status = this.status(nodeProjectInfoModel, originalModel); + if (status.isSuccess()) { + return I18nMessageUtil.get("i18n.program_already_running.96e1") + status.getPid(); + } + File fileLib = projectInfoService.resolveLibFile(nodeProjectInfoModel); + File[] files = fileLib.listFiles(); + if (files == null || files.length == 0) { + return I18nMessageUtil.get("i18n.no_files_in_project_directory.108e"); + } + // + + RunMode checkRunMode = originalModel.getRunMode(); + if (checkRunMode == RunMode.Dsl) { + // + originalModel.mustDslConfig(); + } else if (checkRunMode == RunMode.ClassPath || checkRunMode == RunMode.JavaExtDirsCp) { + // 判断主类 + String mainClass = originalModel.mainClass(); + try (JarClassLoader jarClassLoader = JarClassLoader.load(fileLib)) { + jarClassLoader.loadClass(mainClass); + } catch (ClassNotFoundException notFound) { + return I18nMessageUtil.get("i18n.no_main_class_found.b001") + mainClass; + } catch (IOException io) { + throw Lombok.sneakyThrow(io); + } + } else if (checkRunMode == RunMode.Jar || checkRunMode == RunMode.JarWar) { + + List fileList = this.listJars(checkRunMode, fileLib); + if (fileList.isEmpty()) { + return String.format(I18nMessageUtil.get("i18n.missing_package_in_root_dir.8bab"), checkRunMode.name(), checkRunMode.name()); + } + File jarFile = fileList.get(0); + String checkJar = checkJar(jarFile); + if (checkJar != null) { + return checkJar; + } + } else { + return I18nMessageUtil.get("i18n.project_type_not_supported_for_startup.7bd1"); + } + // 备份日志 + 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(); + Attributes attributes = manifest.getMainAttributes(); + String mainClass = attributes.getValue(Attributes.Name.MAIN_CLASS); + if (mainClass == null) { + return jarFile.getAbsolutePath() + I18nMessageUtil.get("i18n.main_class_attribute_not_found.93e8"); + } + try (JarClassLoader jarClassLoader = JarClassLoader.load(jarFile)) { + jarClassLoader.loadClass(mainClass); + } catch (ClassNotFoundException notFound) { + return jarFile.getAbsolutePath() + I18nMessageUtil.get("i18n.main_class_not_found.b4b7") + mainClass; + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_jar.a26e"), e); + return jarFile.getAbsolutePath() + I18nMessageUtil.get("i18n.parse_error.da6d") + e.getMessage(); + } + return null; + } + + /** + * 解析是否开启 日志备份功能 + * + * @param nodeProjectInfoModel 项目实体 + * @return true 开启日志备份 + */ + private boolean resolveOpenLogBack(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) { + RunMode runMode = originalModel.getRunMode(); + boolean autoBackToFile = projectLogConfig.isAutoBackupToFile(); + if (runMode == RunMode.Dsl) { + return Optional.ofNullable(originalModel.dslConfig()) + .map(DslYmlDto::getConfig) + .map(DslYmlDto.Config::getAutoBackToFile) + .orElse(autoBackToFile); + } + return autoBackToFile; + } + + /** + * 清空日志信息 + * + * @param nodeProjectInfoModel 项目 + * @return 结果 + */ + public String backLog(NodeProjectInfoModel nodeProjectInfoModel) { + 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"; + } + // 文件内容太少不处理 + + if (file.length() <= BACK_LOG_MIN_SIZE) { + return "ok"; + } + boolean openLogBack = this.resolveOpenLogBack(nodeProjectInfoModel, originalModel); + if (openLogBack) { + // 开启日志备份才移动文件 + 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 = systemCommander.emptyLogFile(file); + if (StrUtil.isNotEmpty(r)) { + log.info(r); + } + // 重新监听 + AgentFileTailWatcher.reWatcher(file); + return "ok"; + } + + /** + * 查询项目状态 + * + * @param nodeProjectInfoModel 项目 + * @return 状态 + */ + 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, I18nMessageUtil.get("i18n.file_type_project_no_running_status.32a2")); + } + if (runMode == RunMode.Dsl) { + DslYmlDto config = originalModel.mustDslConfig(); + List status; + // 提前判断脚本 id,避免填写错误在删除项目检测状态时候异常 + try { + Tuple tuple = dslScriptServer.syncRun(config, ConsoleCommandOp.status, nodeProjectInfoModel, originalModel); + status = tuple.get(1); + } catch (IllegalArgument2Exception argument2Exception) { + log.warn(I18nMessageUtil.get("i18n.execute_dsl_script_exception.0882"), argument2Exception.getMessage()); + status = CollUtil.newArrayList(argument2Exception.getMessage()); + } + + return Optional.ofNullable(status) + .map(strings -> { + File log = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel, originalModel); + FileUtil.appendLines(strings, log, fileCharset); + return strings; + }) + .map(CollUtil::getLast) + // 此流程特意处理 系统日志标准格式 StrUtil.format("{} [{}] - {}", DateUtil.now(), this.action, line); + .map(s -> StrUtil.splitTrim(s, StrPool.DASHED)) + .map(CollUtil::getLast) + .map(CommandOpResult::of) + .orElseGet(() -> CommandOpResult.of(false, STOP_TAG)); + } else { + String tag = nodeProjectInfoModel.getId(); + String statusResult = this.status(tag); + CommandOpResult of = CommandOpResult.of(statusResult); + if (!of.isSuccess()) { + // 只有 java 项目才判断 jps + Assert.state(JvmUtil.jpsNormal, JvmUtil.JPS_ERROR_MSG); + } + return of; + } + } + + /** + * 重新加载 + * + * @param nodeProjectInfoModel 项目 + * @return 结果 + */ + protected CommandOpResult reload(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) { + RunMode runMode = originalModel.getRunMode(); + Assert.state(runMode == RunMode.Dsl, I18nMessageUtil.get("i18n.non_dsl_project_unsupported_operation.5c09")); + DslYmlDto config = originalModel.mustDslConfig(); + CommandOpResult commandOpResult; + // 提前判断脚本 id,避免填写错误在删除项目检测状态时候异常 + try { + Tuple tuple = dslScriptServer.syncRun(config, ConsoleCommandOp.reload, nodeProjectInfoModel, originalModel); + int code = tuple.get(0); + List list = tuple.get(1); + // 如果退出码为 0 认为执行成功 + commandOpResult = CommandOpResult.of(code == 0, list); + } catch (IllegalArgument2Exception argument2Exception) { + log.warn(I18nMessageUtil.get("i18n.execute_dsl_script_exception.0882"), argument2Exception.getMessage()); + commandOpResult = 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; + } + + /** + * 查看状态 + * + * @param tag 运行标识 + * @return 查询结果 + */ + protected String status(String tag) { + String jpsStatus = this.getJpsStatus(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 + * + * @param tag 进程标识 + * @return 运行标识 + */ + private String getJpsStatus(String tag) { + Integer pid = JvmUtil.getPidByTag(tag); + if (pid == null || pid <= 0) { + return AbstractProjectCommander.STOP_TAG; + } + return StrUtil.format("{}:{}", AbstractProjectCommander.RUNNING_TAG, pid); + } + +//---------------------------------------------------- 基本操作----end + + /** + * 获取进程占用的主要端口 + * + * @param pid 进程id + * @return 端口 + */ + 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 = this.listNetstat(pid, true); + if (list == null) { + return StrUtil.DASHED; + } + List ports = new ArrayList<>(); + for (NetstatModel model : list) { + String local = model.getLocal(); + String portStr = getPortFormLocalIp(local); + if (portStr == null) { + continue; + } + // 取最小的端口号 + int minPort = Convert.toInt(portStr, Integer.MAX_VALUE); + if (minPort == Integer.MAX_VALUE) { + continue; + } + ports.add(minPort); + } + if (CollUtil.isEmpty(ports)) { + return StrUtil.DASHED; + } + String allPort = CollUtil.join(ports, StrUtil.COMMA); + // 缓存 + CacheObject.put(PID_PORT, pid, allPort); + return allPort; + } + + /** + * 判断ip 信息是否为本地ip + * + * @param local ip信息 + * @return true 是本地ip + */ + private String getPortFormLocalIp(String local) { + if (StrUtil.isEmpty(local)) { + return null; + } + List ipPort = StrSplitter.splitTrim(local, StrUtil.COLON, true); + if (ipPort.isEmpty()) { + return null; + } + String anObject = ipPort.get(0); + if (StrUtil.equalsAny(anObject, "0.0.0.0", "*") || ipPort.size() == 1) { + // 0.0.0.0:8084 || :::18000 || *:2123 + return ipPort.get(ipPort.size() - 1); + } + return null; + } + + + /** + * 是否正在运行 + * + * @param nodeProjectInfoModel 项目 + * @return true 正在运行 + */ + public boolean isRun(NodeProjectInfoModel 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(); + } + + /*** + * 阻塞检查程序状态 + * @param nodeProjectInfoModel 项目 + * @param status 要检查的状态 + * + * @return 和参数status相反 + */ + protected boolean loopCheckRun(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, boolean status) { + int statusWaitTime = projectConfig.getStatusWaitTime(); + return this.loopCheckRun(nodeProjectInfoModel, originalModel, statusWaitTime, status); + } + + /*** + * 阻塞检查程序状态 + * @param nodeProjectInfoModel 项目 + * @param status 要检查的状态 + * @param waitTime 检查等待时间 + * + * @return 如果和期望一致则返回 true,反之 false + */ + protected boolean loopCheckRun(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int waitTime, boolean status) { + waitTime = Math.max(waitTime, 1); + 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, originalModel) == status) { + // 是期望的结果 + return true; + } + ThreadUtil.sleep(statusDetectionInterval); + } 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 new file mode 100644 index 0000000000..6a9dc9a715 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractSystemCommander.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander; + +import org.dromara.jpom.util.CommandUtil; + +import java.io.File; + +/** + * 系统监控命令 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +public abstract class AbstractSystemCommander implements SystemCommander { + + + /** + * 清空文件内容 + * + * @param file 文件 + * @return 执行结果 + */ + 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); + + /** + * 构建kill 命令 + * + * @param pid 进程编号 + * @return 结束进程命令 + */ + public abstract String buildKill(int pid); + + /** + * kill + * + * @param pid 进程编号 + */ + public String kill(File file, int pid) { + String kill = buildKill(pid); + return CommandUtil.execSystemCommand(kill, file); + } +} 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 new file mode 100644 index 0000000000..465aa4c769 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/BaseUnixProjectCommander.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander; + +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; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * unix + * + * @author bwcx_jzy + * @since 2021/12/17 + */ +@Slf4j +public abstract class BaseUnixProjectCommander extends AbstractProjectCommander { + + public BaseUnixProjectCommander(Charset fileCharset, + SystemCommander systemCommander, + ProjectConfig projectConfig, + DslScriptServer dslScriptServer, + ProjectInfoService projectInfoService) { + super(fileCharset, systemCommander, projectConfig, dslScriptServer, projectInfoService); + } + + @Override + 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, lib), + path, + Optional.ofNullable(originalModel.mainClass()).orElse(StrUtil.EMPTY), + Optional.ofNullable(nodeProjectInfoModel.getArgs()).orElse(StrUtil.EMPTY), + absoluteLog); + } + + @Override + public CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int pid) { + File file = projectInfoService.resolveLibFile(originalModel); + List result = new ArrayList<>(); + boolean success = false; + String kill = systemCommander.kill(file, pid); + result.add(kill); + 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); + try { + CommandUtil.asyncExeLocalCommand(cmd, file, null, true); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + // + if (this.loopCheckRun(nodeProjectInfoModel, originalModel, 5, false)) { + success = true; + } else { + result.add("Kill -9 not completed, kill -9 failed "); + } + } + String tag = nodeProjectInfoModel.getId(); + 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/CacheObject.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/CacheObject.java new file mode 100644 index 0000000000..0316d19edb --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/CacheObject.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander; + +import cn.hutool.core.date.SystemClock; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author bwcx_jzy + * @since 2023/4/6 + */ +public class CacheObject { + + private final T value; + + private final Long enterTime; + + public CacheObject(T value) { + this.value = value; + this.enterTime = SystemClock.now(); + } + + private boolean isExpired() { + return (System.currentTimeMillis() - this.enterTime > TimeUnit.MINUTES.toMillis(10)); + } + + /** + * 添加到缓存对象中 + * + * @param map map + * @param key 缓存的 key + * @param value 缓存的 value + * @param 缓存的 key + * @param 缓存的 value + */ + public static void put(Map> map, K key, V value) { + map.put(key, new CacheObject<>(value)); + int size = map.size(); + if (size > 100) { + // 清空过期的数据 + Iterator>> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + CacheObject nextValue = next.getValue(); + if (nextValue.isExpired()) { + iterator.remove(); + } + } + } + } + + /** + * 获取缓存中的值 + * + * @param map 缓存 map + * @param key 缓存的 key + * @param 缓存的 key + * @return value + */ + public static V get(Map> map, K key) { + CacheObject cacheObject = map.get(key); + if (cacheObject == null || cacheObject.isExpired()) { + map.remove(key); + return null; + } + return cacheObject.value; + } +} 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 new file mode 100644 index 0000000000..ef3cf7ef05 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/CommandOpResult.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander; + +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.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * 命令操作执行结果 + * + * @author bwcx_jzy + * @since 2022/11/30 + */ +@Data +public class CommandOpResult { + + /** + * 是否成功 + */ + private Boolean success; + /** + * 进程id + */ + private Integer pid; + /** + * 多个进程 id + */ + private Integer[] pids; + /** + * 端口 + */ + private String ports; + /** + * 状态消息 + */ + private String statusMsg; + /** + * 执行结果 + */ + 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; + if (StrUtil.startWith(msg, AbstractProjectCommander.RUNNING_TAG)) { + List list = StrUtil.splitTrim(msg, StrUtil.COLON); + String pids = CollUtil.get(list, 1); + pidsArray = StrUtil.splitToInt(pids, StrUtil.COMMA); + // + ports = CollUtil.get(list, 2); + } + int mainPid = ObjectUtil.defaultIfNull(ArrayUtil.get(pidsArray, 0), 0); + CommandOpResult result = of(mainPid > 0, msg); + if (ArrayUtil.length(pidsArray) > 1) { + // 仅有多个进程号,才返回 pids + result.pids = ArrayUtil.wrap(pidsArray); + } + result.pid = mainPid; + result.ports = ports; + result.statusMsg = msg; + return result; + } + + public static CommandOpResult of(boolean success) { + return of(success, (List) null); + } + + public static CommandOpResult of(boolean success, String msg) { + CommandOpResult commandOpResult = new CommandOpResult(); + commandOpResult.success = success; + commandOpResult.appendMsg(msg); + 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; + } + msgs.add(msg); + return this; + } + + public CommandOpResult appendMsg(List 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 0000000000..01ed46b3de --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/Commander.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.common.i18n.I18nMessageUtil; +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(I18nMessageUtil.get("i18n.current_system_is_linux.e377")); + } else if (osInfo.isWindows()) { + // Windows系统 + log.debug(I18nMessageUtil.get("i18n.current_system_is_windows.91d1")); + } else if (osInfo.isMac()) { + log.debug(I18nMessageUtil.get("i18n.current_system_is_mac.0139")); + } else { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.unsupported_item.bcf4") + 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 0000000000..89e68e5ab0 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/ProjectCommander.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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/agent/src/main/java/org/dromara/jpom/common/commander/SystemCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/SystemCommander.java new file mode 100644 index 0000000000..d7468dec9a --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/SystemCommander.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 23/12/29 029 + */ +public interface SystemCommander { + + /** + * 清空文件内容 + * + * @param file 文件 + * @return 执行结果 + */ + String emptyLogFile(File file); + + + /** + * 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 new file mode 100644 index 0000000000..5d2283cc8b --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxProjectCommander.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * linux + * + * @author bwcx_jzy + */ +@Conditional(Commander.Linux.class) +@Service +@Primary +@Slf4j +public class LinuxProjectCommander extends BaseUnixProjectCommander { + + public LinuxProjectCommander(AgentConfig agentConfig, + SystemCommander systemCommander, + DslScriptServer dslScriptServer, + ProjectInfoService projectInfoService) { + super(agentConfig.getProject().getLog().getFileCharset(), systemCommander, agentConfig.getProject(), dslScriptServer, projectInfoService); + } + + @Override + public List listNetstat(int pId, boolean listening) { + String cmd; + if (listening) { + cmd = "netstat -antup | grep " + pId + " |grep \"LISTEN\" | head -20"; + } else { + cmd = "netstat -antup | grep " + pId + " |grep -v \"CLOSE_WAIT\" | head -20"; + } + return this.listNetstat(cmd); + } + + protected List listNetstat(String cmd) { + String result = CommandUtil.execSystemCommand(cmd); + List netList = StrSplitter.splitTrim(result, StrUtil.LF, true); + if (CollUtil.isEmpty(netList)) { + return null; + } + return netList.stream().map(str -> { + List list = StrSplitter.splitTrim(str, " ", true); + if (list.size() < 5) { + return null; + } + NetstatModel netstatModel = new NetstatModel(); + netstatModel.setProtocol(list.get(0)); + netstatModel.setReceive(list.get(1)); + netstatModel.setSend(list.get(2)); + netstatModel.setLocal(list.get(3)); + netstatModel.setForeign(list.get(4)); + if ("tcp".equalsIgnoreCase(netstatModel.getProtocol())) { + netstatModel.setStatus(CollUtil.get(list, 5)); + netstatModel.setName(CollUtil.get(list, 6)); + } else { + netstatModel.setStatus(StrUtil.DASHED); + netstatModel.setName(CollUtil.get(list, 5)); + } + + return netstatModel; + }).filter(Objects::nonNull).collect(Collectors.toList()); + } +} 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 new file mode 100644 index 0000000000..4980c8deb3 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxSystemCommander.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander.impl; + +import cn.hutool.core.util.StrUtil; +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; + +/** + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Slf4j +@Conditional(Commander.Linux.class) +@Service +@Primary +public class LinuxSystemCommander extends AbstractSystemCommander { + + + @Override + public String emptyLogFile(File file) { + return CommandUtil.execSystemCommand("cp /dev/null " + file.getAbsolutePath()); + } + + +// @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) { + return String.format("kill %s", pid); + } + + private String getPs(String serviceName) { + String ps = StrUtil.format("ps -ef | grep -v 'grep' | egrep {}", serviceName); + return CommandUtil.execSystemCommand(ps); + } +} 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 new file mode 100644 index 0000000000..1593fa6118 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsProjectCommander.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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 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.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * MacOSProjectCommander + *

+ * some commands cannot execute success on Mac OS + * + * @author Hotstrip + */ +@Conditional(Commander.Mac.class) +@Service +public class MacOsProjectCommander extends BaseUnixProjectCommander { + + public MacOsProjectCommander(AgentConfig agentConfig, + SystemCommander systemCommander, + DslScriptServer dslScriptServer, + ProjectInfoService projectInfoService) { + super(agentConfig.getProject().getLog().getFileCharset(), systemCommander, agentConfig.getProject(), dslScriptServer, projectInfoService); + } + + @Override + public List listNetstat(int pId, boolean listening) { + String cmd; + if (listening) { + cmd = "lsof -n -P -iTCP -sTCP:LISTEN |grep " + pId + " | head -20"; + } else { + cmd = "lsof -n -P -iTCP -sTCP:CLOSE_WAIT |grep " + pId + " | head -20"; + } + return this.listNetstat(cmd); + } + + protected List listNetstat(String cmd) { + String result = CommandUtil.execSystemCommand(cmd); + List netList = StrSplitter.splitTrim(result, StrUtil.LF, true); + 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 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 new file mode 100644 index 0000000000..289a2c07c0 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsSystemCommander.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander.impl; + +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; + +/** + * @author User + */ +@Slf4j +@Conditional(Commander.Mac.class) +@Service +public class MacOsSystemCommander extends AbstractSystemCommander { + + + @Override + public String emptyLogFile(File file) { + return CommandUtil.execSystemCommand("cp /dev/null " + file.getAbsolutePath()); + } + + +// @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) { + return String.format("kill %s", 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 new file mode 100644 index 0000000000..26990b2bd9 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsProjectCommander.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander.impl; + +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.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.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * windows 版 + * + * @author bwcx_jzy + */ +@Conditional(Commander.Windows.class) +@Service +public class WindowsProjectCommander extends AbstractProjectCommander { + + public WindowsProjectCommander(AgentConfig agentConfig, + SystemCommander systemCommander, + DslScriptServer dslScriptServer, + ProjectInfoService projectInfoService) { + super(agentConfig.getProject().getLog().getFileCharset(), systemCommander, agentConfig.getProject(), dslScriptServer, projectInfoService); + } + + @Override + 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 = 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, lib), + classPath, + Optional.ofNullable(mainClass).orElse(StrUtil.EMPTY), + Optional.ofNullable(args).orElse(StrUtil.EMPTY), + absoluteLog); + } + + @Override + public CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int pid) { + String tag = nodeProjectInfoModel.getId(); + List result = new ArrayList<>(); + boolean success = false; + // 如果正在运行,则执行杀进程命令 + File file = projectInfoService.resolveLibFile(nodeProjectInfoModel); + String kill = systemCommander.kill(file, pid); + result.add(kill); + if (this.loopCheckRun(nodeProjectInfoModel, originalModel, false)) { + success = true; + } else { + result.add("Kill not completed"); + } + return CommandOpResult.of(success, status(tag)).appendMsg(result); + // return status(tag) + StrUtil.SPACE + kill; + } + + @Override + public List listNetstat(int pId, boolean listening) { + String cmd; + if (listening) { + cmd = "netstat -nao -p tcp | findstr \"LISTENING\" | findstr " + pId; + } else { + cmd = "netstat -nao -p tcp | findstr /V \"CLOSE_WAIT\" | findstr " + pId; + } + String result = CommandUtil.execSystemCommand(cmd); + List netList = StrSplitter.splitTrim(result, StrUtil.LF, true); + if (netList == null || netList.isEmpty()) { + return null; + } + List array = new ArrayList<>(); + for (String str : netList) { + List list = StrSplitter.splitTrim(str, " ", true); + if (list.size() < 5) { + continue; + } + NetstatModel netstatModel = new NetstatModel(); + netstatModel.setProtocol(list.get(0)); + netstatModel.setLocal(list.get(1)); + netstatModel.setForeign(list.get(2)); + netstatModel.setStatus(list.get(3)); + netstatModel.setName(list.get(4)); + array.add(netstatModel); + } + 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 new file mode 100644 index 0000000000..a82e64be59 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsSystemCommander.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander.impl; + +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; + +/** + * windows 系统查询命令 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Conditional(Commander.Windows.class) +@Service +public class WindowsSystemCommander extends AbstractSystemCommander { + + @Override + public String emptyLogFile(File file) { + return CommandUtil.execSystemCommand("echo \"\" > " + file.getAbsolutePath()); + } + + +// @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) { + return String.format("taskkill /F /PID %s", 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 new file mode 100644 index 0000000000..226e28ff11 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/AuthorizeInterceptor.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AgentAuthorize; +import org.dromara.jpom.configuration.AgentConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.method.HandlerMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 授权拦截 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +@Configuration +public class AuthorizeInterceptor implements HandlerMethodInterceptor { + + private final AgentAuthorize agentAuthorize; + + public AuthorizeInterceptor(AgentConfig agentConfig) { + this.agentAuthorize = agentConfig.getAuthorize(); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { + NotAuthorize notAuthorize = handlerMethod.getMethodAnnotation(NotAuthorize.class); + if (notAuthorize == null) { + String authorize = ServletUtil.getHeaderIgnoreCase(request, Const.JPOM_AGENT_AUTHORIZE); + if (StrUtil.isEmpty(authorize)) { + this.error(response); + return false; + } + if (!agentAuthorize.checkAuthorize(authorize)) { + this.error(response); + return false; + } + } + return true; + } + + private void error(HttpServletResponse response) { + ServletUtil.write(response, JsonMessage.getString(Const.AUTHORIZE_ERROR, I18nMessageUtil.get("i18n.auth_info_error.c184")), MediaType.APPLICATION_JSON_VALUE); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/DecryptionFilter.java b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/DecryptionFilter.java new file mode 100644 index 0000000000..37843408b4 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/DecryptionFilter.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.ContentType; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.transport.BodyRewritingRequestWrapper; +import org.dromara.jpom.common.transport.MultipartRequestWrapper; +import org.dromara.jpom.common.transport.ParameterRequestWrapper; +import org.dromara.jpom.encrypt.EncryptFactory; +import org.dromara.jpom.encrypt.Encryptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; + +/** + * @author loyal.f + * @since 2023/3/13 + */ +@Configuration +@Slf4j +@Order(1) +public class DecryptionFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + String transportEncryption = request.getHeader("transport-encryption"); + // 兼容没有没有传入 + Encryptor encryptor; + try { + encryptor = EncryptFactory.createEncryptor(Convert.toInt(transportEncryption, 0)); + } catch (NoSuchAlgorithmException e) { + log.error(I18nMessageUtil.get("i18n.get_decrypt_distribution_failure.4feb"), e); + chain.doFilter(servletRequest, response); + return; + } + log.debug(I18nMessageUtil.get("i18n.request_needs_decoding.d4d7"), encryptor.name()); + String contentType = request.getContentType(); + if (ContentType.isDefault(contentType)) { + // 普通表单 + HttpServletRequestWrapper wrapper = new ParameterRequestWrapper(request, encryptor); + chain.doFilter(wrapper, response); + } else if (StrUtil.startWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)) { + String body = ServletUtil.getBody(request); + String temp; + try { + temp = encryptor.decrypt(body); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.decode_failure.822e"), e); + temp = body; + } + BodyRewritingRequestWrapper requestWrapper = new BodyRewritingRequestWrapper(request, temp.getBytes(StandardCharsets.UTF_8)); + chain.doFilter(requestWrapper, response); + } else if (ServletFileUpload.isMultipartContent(request)) { + // 文件上传 + HttpServletRequestWrapper wrapper = new MultipartRequestWrapper(request, encryptor); + chain.doFilter(wrapper, response); + } else { + log.warn(I18nMessageUtil.get("i18n.request_type_not_supported_for_decoding.ea2e"), contentType); + chain.doFilter(servletRequest, response); + } + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/NotAuthorize.java b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/NotAuthorize.java new file mode 100644 index 0000000000..65aef5229b --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/NotAuthorize.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import java.lang.annotation.*; + +/** + * 不需要授权 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +@Documented +@Target(ElementType.METHOD) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface NotAuthorize { +} + diff --git a/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentAuthorize.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentAuthorize.java new file mode 100644 index 0000000000..239d3a7fbf --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentAuthorize.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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 java.io.File; + +/** + * agent 端授权账号信息 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +@Slf4j +@Data +@ConfigurationProperties("jpom.authorize") +public class AgentAuthorize { + /** + * 账号 + */ + private String agentName; + /** + * 密码 + */ + private String agentPwd; + /** + * 授权加密字符串 + */ + private String authorize; + + public void setAuthorize(String authorize) { + // 不能外部 set + } + + public String getAuthorize() { + return null; + } + + + /** + * 判断授权是否正确 + * + * @param authorize 授权 + * @return true 正确 + */ + public boolean checkAuthorize(String authorize) { + return StrUtil.equals(authorize, this.authorize); + } + + /** + * 检查是否配置密码 + */ + private void checkPwd(JpomApplication configBean) { + File path = FileUtil.file(configBean.getDataPath(), Const.AUTHORIZE); + if (StrUtil.isNotEmpty(agentPwd)) { + // 有指定密码 清除旧密码信息 + FileUtil.del(path); + log.info("Authorization information has been customized,account:{}", this.agentName); + return; + } + if (FileUtil.exist(path)) { + // 读取旧密码 + String json = FileUtil.readString(path, CharsetUtil.CHARSET_UTF_8); + AgentAutoUser autoUser = JSONObject.parseObject(json, AgentAutoUser.class); + if (!StrUtil.equals(autoUser.getAgentName(), this.agentName)) { + throw new JpomRuntimeException("The existing login name is inconsistent with the configured login name"); + } + String oldAgentPwd = autoUser.getAgentPwd(); + if (StrUtil.isNotEmpty(oldAgentPwd)) { + this.agentPwd = oldAgentPwd; + log.info("Already authorized account:{} password:{} Authorization information storage location:{}", this.agentName, this.agentPwd, FileUtil.getAbsolutePath(path)); + return; + } + } + this.agentPwd = RandomUtil.randomString(10); + AgentAutoUser autoUser = new AgentAutoUser(); + autoUser.setAgentName(this.agentName); + autoUser.setAgentPwd(this.agentPwd); + // 写入文件中 + JsonFileUtil.saveJson(path, autoUser.toJson()); + log.info("Automatically generate authorized account:{} password:{} Authorization information storage location:{}", this.agentName, this.agentPwd, FileUtil.getAbsolutePath(path)); + } + + 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(configBean); + // 生成密码授权字符串 + this.authorize = SecureUtil.sha1(this.agentName + "@" + this.agentPwd); + } else { + log.warn(I18nMessageUtil.get("i18n.authorized_cannot_be_reloaded.6ece")); + } + // + JvmUtil.checkJpsNormal(); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentConfig.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentConfig.java new file mode 100644 index 0000000000..6ea3042713 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentConfig.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; +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 java.io.File; +import java.util.Optional; + +/** + * 插件端配置信息 + * + * @author bwcx_jzy + * @since 2022/12/16 + */ + +@Configuration +@ConfigurationProperties("jpom") +@Data +@EnableConfigurationProperties({ProjectConfig.class, ProjectLogConfig.class, SystemConfig.class, AgentAuthorize.class, MonitorConfig.class, MonitorConfig.NetworkConfig.class}) +public class AgentConfig implements ILoadEvent, InitializingBean { + + private final JpomApplication jpomApplication; + + public AgentConfig(JpomApplication jpomApplication) { + this.jpomApplication = jpomApplication; + } + + /** + * 授权配置 + */ + private AgentAuthorize authorize; + + /** + * 项目配置 + */ + private ProjectConfig project; + /** + * 系统配置参数 + */ + private SystemConfig system; + /** + * 监控配置 + */ + private MonitorConfig monitor; + + /** + * 数据目录 + */ + 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(() -> { + this.project = new ProjectConfig(); + return this.project; + }); + } + + public SystemConfig getSystem() { + return Optional.ofNullable(this.system).orElseGet(() -> { + this.system = new SystemConfig(); + return this.system; + }); + } + + /** + * 获取临时文件存储路径,并添加一个随机字符串 + * + * @return 文件夹 + */ + public String getTempPathName() { + File file = getTempPath(); + // 生成随机的一个文件夹、避免同一个节点分发同一个文件,mv 失败 + return FileUtil.getAbsolutePath(FileUtil.file(file, IdUtil.fastSimpleUUID())); + } + + /** + * 获取临时文件存储路径 + * + * @return 文件夹 + */ + public String getFixedTempPathName() { + File file = getTempPath(); + return FileUtil.getAbsolutePath(file); + } + + + /** + * 获取临时文件存储路径 + * + * @return file + */ + public File getTempPath() { + File file = jpomApplication.getTempPath(); + file = FileUtil.file(file, DateTime.now().toDateStr()); + FileUtil.mkdir(file); + return file; + } + + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + // 登录名不能为空 + this.getAuthorize().init(jpomApplication); + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } + + @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/MonitorConfig.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/MonitorConfig.java new file mode 100644 index 0000000000..6584f6f05f --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/MonitorConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author bwcx_jzy + * @since 2024/4/24 + */ +@Data +@ConfigurationProperties("jpom.monitor") +public class MonitorConfig { + + private NetworkConfig network; + + @Data + @ConfigurationProperties("jpom.monitor.network") + public static class NetworkConfig { + /** + * 忽略的网卡,多个使用逗号分隔 + */ + private String statExcludeNames; + /** + * 仅统计的网卡 + * ,多个使用逗号分隔 + */ + private String statContainsOnlyNames; + } +} 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 0000000000..15362a3b7d --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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 0000000000..2542108417 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectLogConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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 0000000000..ce031f20c0 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/SystemConfig.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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 new file mode 100644 index 0000000000..2a75feadb1 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/IndexController.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.SystemUtil; +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.JpomManifest; +import org.dromara.jpom.common.RemoteVersion; +import org.dromara.jpom.common.commander.ProjectCommander; +import org.dromara.jpom.common.commander.SystemCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotAuthorize; +import org.dromara.jpom.configuration.AgentConfig; +import org.dromara.jpom.configuration.MonitorConfig; +import org.dromara.jpom.model.data.NodeProjectInfoModel; +import org.dromara.jpom.model.data.NodeScriptModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.manage.ProjectInfoService; +import org.dromara.jpom.service.script.NodeScriptServer; +import org.dromara.jpom.util.JvmUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +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.Optional; +import java.util.stream.Collectors; + +/** + * 首页 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +@RestController +@Slf4j +public class IndexController extends BaseAgentController { + + private final ProjectInfoService projectInfoService; + private final NodeScriptServer nodeScriptServer; + private final SystemCommander systemCommander; + private final ProjectCommander projectCommander; + private final AgentConfig agentConfig; + + public IndexController(ProjectInfoService projectInfoService, + NodeScriptServer nodeScriptServer, + SystemCommander systemCommander, + ProjectCommander projectCommander, + AgentConfig agentConfig) { + this.projectInfoService = projectInfoService; + this.nodeScriptServer = nodeScriptServer; + this.systemCommander = systemCommander; + this.projectCommander = projectCommander; + this.agentConfig = agentConfig; + } + + @RequestMapping(value = {"index", "", "index.html", "/"}, produces = MediaType.TEXT_PLAIN_VALUE) + @NotAuthorize + public String index() { + return "Jpom-Agent,Can't access directly,Please configure it to JPOM server"; + } + + @RequestMapping(value = "info", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage info() { + + JpomManifest instance = JpomManifest.getInstance(); + cn.keepbx.jpom.RemoteVersion remoteVersion = RemoteVersion.cacheInfo(); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("manifest", instance); + jsonObject.put("remoteVersion", remoteVersion); + jsonObject.put("pluginSize", PluginFactory.size()); + jsonObject.put("joinBetaRelease", RemoteVersion.betaRelease()); + jsonObject.put("monitor", agentConfig.getMonitor()); + return JsonMessage.success("", jsonObject); + } + + /** + * 获取节点统计信息 + * + * @return json + */ + @PostMapping(value = "get-stat-info", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getDirectTop() { + JSONObject jsonObject = new JSONObject(); + try { + Optional monitorConfig = Optional.ofNullable(agentConfig).map(AgentConfig::getMonitor); + + JSONObject topInfo = org.dromara.jpom.util.OshiUtils.getSimpleInfo(monitorConfig.orElse(null)); + jsonObject.put("simpleStatus", topInfo); + // 系统固定休眠时间 + jsonObject.put("systemSleep", org.dromara.jpom.util.OshiUtils.NET_STAT_SLEEP + org.dromara.jpom.util.OshiUtils.CPU_STAT_SLEEP); + + JSONObject systemInfo = org.dromara.jpom.util.OshiUtils.getSystemInfo(); + jsonObject.put("systemInfo", systemInfo); + //jsonObject.put("oshiError", "测试异常"); + } catch (Throwable e) { + log.error(I18nMessageUtil.get("i18n.oshi_system_monitoring_exception.5c1c"), e); + jsonObject.put("oshiError", e.getMessage()); + } + + JSONObject jpomInfo = this.getJpomInfo(); + jsonObject.put("jpomInfo", jpomInfo); + return JsonMessage.success("", jsonObject); + } + + private JSONObject getJpomInfo() { + List nodeProjectInfoModels = projectInfoService.list(); + List list = nodeScriptServer.list(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("javaVirtualCount", JvmUtil.getJavaVirtualCount()); + JpomManifest instance = JpomManifest.getInstance(); + jsonObject.put("jpomManifest", instance); + jsonObject.put("javaVersion", SystemUtil.getJavaRuntimeInfo().getVersion()); + // 获取JVM中内存总大小 + jsonObject.put("totalMemory", SystemUtil.getTotalMemory()); + // + jsonObject.put("freeMemory", SystemUtil.getFreeMemory()); + Map workspaceMap = new HashMap<>(4); + // + { + 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; + } + + + @RequestMapping(value = "processList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> getProcessList(String processName, Integer count) { + try { + processName = StrUtil.emptyToDefault(processName, "java"); + List processes = org.dromara.jpom.util.OshiUtils.getProcesses(processName, Convert.toInt(count, 20)); + processes = processes.stream() + .peek(jsonObject -> { + int processId = jsonObject.getIntValue("processId"); + String port = projectCommander.getMainPort(processId); + jsonObject.put("port", port); + // + }) + .collect(Collectors.toList()); + return JsonMessage.success("", processes); + } catch (Throwable e) { + log.error(I18nMessageUtil.get("i18n.oshi_system_process_monitoring_exception.a4da"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.system_process_monitoring_error.fe1d") + e.getMessage()); + } + } + + + @PostMapping(value = "kill.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage kill(int pid) { + long jpomAgentId = JpomManifest.getInstance().getPid(); + Assert.state(!StrUtil.equals(StrUtil.toString(jpomAgentId), StrUtil.toString(pid)), I18nMessageUtil.get("i18n.online_agent_close_not_supported.d81d")); + String result = systemCommander.kill(null, pid); + if (StrUtil.isEmpty(result)) { + result = I18nMessageUtil.get("i18n.process_killed_successfully.a4c3"); + } + return JsonMessage.success(result); + } + + @PostMapping(value = "disk-info", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> diskInfo() { + try { + List list = org.dromara.jpom.util.OshiUtils.fileStores(); + return JsonMessage.success("", list); + } catch (Throwable e) { + log.error(I18nMessageUtil.get("i18n.oshi_file_system_monitoring_exception.dc24"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.file_system_monitoring_exception.d4c0") + e.getMessage()); + } + } + + @PostMapping(value = "hw-disk--info", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> hwDiskInfo() { + try { + List list = org.dromara.jpom.util.OshiUtils.diskStores(); + return JsonMessage.success("", list); + } catch (Throwable e) { + log.error(I18nMessageUtil.get("i18n.oshi_hard_disk_monitoring_exception.c642"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.hard_drive_monitoring_error.43e7") + e.getMessage()); + } + } + + @PostMapping(value = "network-interfaces", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> networkInterfaces() { + try { + List list = org.dromara.jpom.util.OshiUtils.networkInterfaces(); + return JsonMessage.success("", list); + } catch (Throwable e) { + log.error(I18nMessageUtil.get("i18n.oshi_network_card_monitoring_exception.6d41"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.network_resource_monitoring_error.4ede") + e.getMessage()); + } + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/MyErrorController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/MyErrorController.java new file mode 100644 index 0000000000..137295e0fb --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/MyErrorController.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + + +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author bwcx_jzy + * @since 2022/4/16 + */ +@Controller +@RequestMapping("${server.error.path:${error.path:/error}}") +public class MyErrorController extends BaseMyErrorController { + + public MyErrorController(ErrorAttributes errorAttributes) { + super(errorAttributes); + } +} 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 new file mode 100644 index 0000000000..f6b0f6ac2d --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/FileManageController.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.manage; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +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.commander.CommandOpResult; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.configuration.AgentConfig; +import org.dromara.jpom.util.CompressionFileUtil; +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 org.springframework.web.multipart.MultipartFile; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2023/3/28 + */ +@RestController +@RequestMapping(value = "/manage/file2/") +@Slf4j +public class FileManageController extends BaseAgentController { + + private final AgentConfig agentConfig; + + public FileManageController(AgentConfig agentConfig) { + this.agentConfig = agentConfig; + } + + @RequestMapping(value = "upload-sharding", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage uploadSharding(MultipartFile file, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5) throws Exception { + String tempPathName = agentConfig.getFixedTempPathName(); + this.uploadSharding(file, tempPathName, sliceId, totalSlice, nowSlice, fileSumMd5); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + @RequestMapping(value = "sharding-merge", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage shardingMerge(String type, + @ValidatorItem String path, + Integer stripComponents, + String sliceId, + Integer totalSlice, + String fileSumMd5) throws Exception { + String tempPathName = agentConfig.getFixedTempPathName(); + File successFile = this.shardingTryMerge(tempPathName, sliceId, totalSlice, fileSumMd5); + File lib = FileUtil.file(path); + // 处理上传文件 + if ("unzip".equals(type)) { + // 解压 + try { + int stripComponentsValue = Convert.toInt(stripComponents, 0); + CompressionFileUtil.unCompress(successFile, lib, stripComponentsValue); + } finally { + if (!FileUtil.del(successFile)) { + log.error(I18nMessageUtil.get("i18n.delete_file_failure_with_full_stop.6c96") + successFile.getPath()); + } + } + } else { + // 移动文件到对应目录 + FileUtil.mkdir(lib); + FileUtil.move(successFile, lib, true); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } +} 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 new file mode 100644 index 0000000000..cf9debd495 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ManageEditProjectController.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.manage; + +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.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.ProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.util.List; + +/** + * 编辑项目 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@RestController +@RequestMapping(value = "/manage/") +@Slf4j +public class ManageEditProjectController extends BaseAgentController { + + private final WhitelistDirectoryService whitelistDirectoryService; + private final ProjectCommander projectCommander; + + public ManageEditProjectController(WhitelistDirectoryService whitelistDirectoryService, + ProjectCommander projectCommander) { + this.whitelistDirectoryService = whitelistDirectoryService; + this.projectCommander = projectCommander; + } + + /** + * 基础检查 + * + * @param projectInfo 项目实体 + * @param 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, I18nMessageUtil.get("i18n.project_id_length_range.7064")); + Assert.state(!Const.SYSTEM_ID.equals(id), StrUtil.format(I18nMessageUtil.get("i18n.project_id_keyword_occupied.1cae"), Const.SYSTEM_ID)); + // 运行模式 + RunMode runMode = projectInfo.getRunMode(); + Assert.notNull(runMode, I18nMessageUtil.get("i18n.select_run_mode.5a5d")); + // 监测 + if (runMode == RunMode.ClassPath || runMode == RunMode.JavaExtDirsCp) { + Assert.hasText(projectInfo.mainClass(), I18nMessageUtil.get("i18n.class_path_and_java_ext_dirs_cp_required.7557")); + if (runMode == RunMode.JavaExtDirsCp) { + Assert.hasText(projectInfo.javaExtDirsCp(), I18nMessageUtil.get("i18n.java_ext_dirs_cp_required.1f4a")); + } + } else if (runMode == RunMode.Jar || runMode == RunMode.JarWar) { + projectInfo.setMainClass(StrUtil.EMPTY); + } else if (runMode == RunMode.Link) { + String linkId = projectInfo.getLinkId(); + Assert.hasText(linkId, I18nMessageUtil.get("i18n.link_id_required.5dc7")); + NodeProjectInfoModel item = projectInfoService.getItem(linkId); + Assert.notNull(item, I18nMessageUtil.get("i18n.soft_link_project_department_exists.fa97")); + RunMode itemRunMode = item.getRunMode(); + Assert.state(itemRunMode != RunMode.File && itemRunMode != RunMode.Link, I18nMessageUtil.get("i18n.soft_link_project_mode_error.ffa0")); + } + // 判断是否为分发添加 + String strOutGivingProject = getParameter("outGivingProject"); + boolean outGivingProject = Boolean.parseBoolean(strOutGivingProject); + + projectInfo.setOutGivingProject(outGivingProject); + if (runMode != RunMode.Link) { + if (!previewData) { + String whitelistDirectory = projectInfo.whitelistDirectory(); + // 不是预检查数据才效验授权 + if (!whitelistDirectoryService.checkProjectDirectory(whitelistDirectory)) { + if (outGivingProject) { + whitelistDirectoryService.addProjectWhiteList(whitelistDirectory); + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.select_correct_project_path_or_no_auth_configured.366a")); + } + } + String logPath = projectInfo.logPath(); + if (StrUtil.isNotEmpty(logPath)) { + if (!whitelistDirectoryService.checkProjectDirectory(logPath)) { + if (outGivingProject) { + whitelistDirectoryService.addProjectWhiteList(logPath); + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.project_log_storage_path_required.d0bb")); + } + } + } + } + // + String lib = projectInfo.getLib(); + Assert.state(StrUtil.isNotEmpty(lib) && !StrUtil.SLASH.equals(lib) && !Validator.isChinese(lib), I18nMessageUtil.get("i18n.invalid_project_path.04f7")); + + FileUtils.checkSlip(lib, e -> new IllegalArgumentException(I18nMessageUtil.get("i18n.project_path_promotion_issue.2250"))); + } + } + + + @RequestMapping(value = "saveProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage saveProject(NodeProjectInfoModel projectInfo) { + // 预检查数据 + String strPreviewData = getParameter("previewData"); + boolean previewData = Convert.toBool(strPreviewData, false); + + // + this.checkParameter(projectInfo, previewData); + + // 自动生成log文件 + File checkFile = this.projectInfoService.resolveAbsoluteLogFile(projectInfo); + Assert.state(!FileUtil.exist(checkFile) || FileUtil.isFile(checkFile), I18nMessageUtil.get("i18n.project_log_is_existing_folder.a80a")); + // + String token = projectInfo.token(); + if (StrUtil.isNotEmpty(token)) { + Validator.validateMatchRegex(RegexPool.URL_HTTP, token, I18nMessageUtil.get("i18n.invalid_webhooks_address.d836")); + } + // 判断 yml + this.checkDslYml(projectInfo); + // + return this.save(projectInfo, previewData); + } + + private void checkDslYml(NodeProjectInfoModel projectInfo) { + if (projectInfo.getRunMode() == RunMode.Dsl) { + String dslContent = projectInfo.getDslContent(); + Assert.hasText(dslContent, I18nMessageUtil.get("i18n.configure_dsl_content.42e3")); + DslYmlDto build = DslYmlDto.build(dslContent); + Assert.state(build.hasRunProcess(ConsoleCommandOp.status.name()), I18nMessageUtil.get("i18n.run_status_not_configured.e959")); + } + } + + /** + * 保存项目 + * + * @param projectInfo 项目 + * @param previewData 是否是预检查 + * @return 错误信息 + */ + private IJsonMessage save(NodeProjectInfoModel projectInfo, boolean previewData) { + this.checkPath(projectInfo); + // + NodeProjectInfoModel exits = projectInfoService.getItem(projectInfo.getId()); + String workspaceId = this.getWorkspaceId(); + projectInfo.setWorkspaceId(workspaceId); + RunMode runMode = projectInfo.getRunMode(); + if (exits == null) { + // 检查运行中的tag 是否被占用 + if (runMode != RunMode.File && runMode != RunMode.Dsl) { + Assert.state(!projectCommander.isRun(projectInfo), I18nMessageUtil.get("i18n.project_id_in_use.1adb")); + } + if (previewData) { + // 预检查数据 + return JsonMessage.success(I18nMessageUtil.get("i18n.check_passed.dce8")); + } else { + projectInfoService.addItem(projectInfo); + return JsonMessage.success(I18nMessageUtil.get("i18n.add_new_success.431a")); + } + } + if (previewData) { + // 预检查数据 + return JsonMessage.success(I18nMessageUtil.get("i18n.check_passed.dce8")); + } else { + exits.setNodeId(projectInfo.getNodeId()); + exits.setName(projectInfo.getName()); + exits.setGroup(projectInfo.getGroup()); + exits.setAutoStart(projectInfo.getAutoStart()); + exits.setDisableScanDir(projectInfo.getDisableScanDir()); + exits.setMainClass(projectInfo.mainClass()); + exits.setJvm(projectInfo.getJvm()); + exits.setArgs(projectInfo.getArgs()); + if (StrUtil.isNotEmpty(exits.getWorkspaceId())) { + Assert.state(StrUtil.equals(exits.getWorkspaceId(), workspaceId), I18nMessageUtil.get("i18n.project_associated_with_other_workspaces_message.299c")); + } + exits.setWorkspaceId(workspaceId); + exits.setOutGivingProject(projectInfo.outGivingProject()); + exits.setRunMode(runMode); + exits.setToken(projectInfo.token()); + exits.setDslContent(projectInfo.getDslContent()); + exits.setDslEnv(projectInfo.getDslEnv()); + exits.setJavaExtDirsCp(projectInfo.javaExtDirsCp()); + if (runMode == RunMode.Link) { + // 如果是链接模式 + Assert.state(runMode == exits.getRunMode(), I18nMessageUtil.get("i18n.existing_project_cannot_be_soft_link.aa5a")); + } 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(I18nMessageUtil.get("i18n.modify_success.69be")); + } + } + + private void moveTo(NodeProjectInfoModel old, NodeProjectInfoModel news) { + { + // 移动目录 + File oldLib = projectInfoService.resolveLibFile(old); + File newLib = projectInfoService.resolveLibFile(news); + if (!FileUtil.equals(oldLib, newLib)) { + // 正在运行的项目不能修改路径 + this.projectMustNotRun(old, I18nMessageUtil.get("i18n.running_project_cannot_change_path.5888")); + if (oldLib.exists()) { + FileUtils.tempMoveContent(oldLib, newLib); + } + } + } + { + // log + File oldLog = projectInfoService.resolveAbsoluteLogFile(old); + File newLog = projectInfoService.resolveAbsoluteLogFile(news); + if (!FileUtil.equals(oldLog, newLog)) { + // 正在运行的项目不能修改路径 + this.projectMustNotRun(old, I18nMessageUtil.get("i18n.running_project_cannot_change_path.5888")); + if (oldLog.exists()) { + FileUtil.mkParentDirs(newLog); + FileUtil.move(oldLog, newLog, true); + } + // logBack + File oldLogBack = projectInfoService.resolveLogBack(old); + if (oldLogBack.exists()) { + File logBack = projectInfoService.resolveLogBack(news); + FileUtils.tempMoveContent(oldLogBack, logBack); + } + } + } + } + + private void projectMustNotRun(NodeProjectInfoModel projectInfoModel, String msg) { + boolean run = projectCommander.isRun(projectInfoModel); + Assert.state(!run, msg); + } + + /** + * 路径存在包含关系 + * + * @param nodeProjectInfoModel 比较的项目 + */ + private void checkPath(NodeProjectInfoModel nodeProjectInfoModel) { + String id = nodeProjectInfoModel.getId(); + Assert.state(!id.contains(StrUtil.SPACE), I18nMessageUtil.get("i18n.project_id_cannot_contain_spaces.251d")); + 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), I18nMessageUtil.get("i18n.project_path_no_spaces.263c")); + File checkFile = FileUtil.file(allLib); + Assert.state(!FileUtil.exist(checkFile) || FileUtil.isDirectory(checkFile), I18nMessageUtil.get("i18n.project_path_already_exists_as_file.a900")); + // 重复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(StrUtil.format(I18nMessageUtil.get("i18n.project_path_occupied.cddd"), item.getName())); + } + } + + NodeProjectInfoModel nodeProjectInfoModel1 = null; + for (NodeProjectInfoModel model : list) { + if (model.getRunMode() == RunMode.Link) { + continue; + } + if (!model.getId().equals(nodeProjectInfoModel.getId())) { + File file1 = projectInfoService.resolveLibFile(model); + File file2 = projectInfoService.resolveLibFile(nodeProjectInfoModel); + if (FileUtil.pathEquals(file1, file2)) { + nodeProjectInfoModel1 = model; + break; + } + // 包含关系 + if (FileUtil.isSub(file1, file2) || FileUtil.isSub(file2, file1)) { + nodeProjectInfoModel1 = model; + break; + } + } + } + if (nodeProjectInfoModel1 != null) { + throw new IllegalArgumentException(StrUtil.format(I18nMessageUtil.get("i18n.project_path_conflict.8c6f"), nodeProjectInfoModel1.getName(), nodeProjectInfoModel1.allLib())); + } + } + + /** + * 删除项目 + * + * @return json + */ + @RequestMapping(value = "deleteProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage deleteProject(String id, String thorough) { + NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); + if (nodeProjectInfoModel == null) { + // 返回正常 200 状态码,考虑节点分发重复操作 + return JsonMessage.success(I18nMessageUtil.get("i18n.project_does_not_exist.3029")); + } + // 运行判断 + boolean run = projectCommander.isRun(nodeProjectInfoModel); + Assert.state(!run, I18nMessageUtil.get("i18n.cannot_delete_running_project.e56b")); + // 判断是否被软链 + List list = projectInfoService.list(); + for (NodeProjectInfoModel projectInfoModel : list) { + if (nodeProjectInfoModel.getRunMode() != RunMode.Link) { + continue; + } + Assert.state(!StrUtil.equals(projectInfoModel.getLinkId(), id), StrUtil.format(I18nMessageUtil.get("i18n.project_soft_linked_by.8556"), projectInfoModel.getName())); + } + this.thorough(thorough, nodeProjectInfoModel); + // + projectInfoService.deleteItem(nodeProjectInfoModel.getId()); + + return JsonMessage.success(I18nMessageUtil.get("i18n.deletion_success_message.4359")); + + } + + /** + * 彻底删除项目文件 + * + * @param thorough 是否彻底 + * @param nodeProjectInfoModel 项目 + */ + private void thorough(String thorough, NodeProjectInfoModel nodeProjectInfoModel) { + if (StrUtil.isEmpty(thorough)) { + return; + } + File logBack = this.projectInfoService.resolveLogBack(nodeProjectInfoModel); + boolean fastDel = CommandUtil.systemFastDel(logBack); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.delete_log_file_failure_with_colon.d867") + logBack.getAbsolutePath()); + File log = this.projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel); + fastDel = CommandUtil.systemFastDel(log); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.delete_log_file_failure_with_colon.d867") + log.getAbsolutePath()); + // + if (nodeProjectInfoModel.getRunMode() != RunMode.Link) { + // 非软链项目才删除文件 + File allLib = projectInfoService.resolveLibFile(nodeProjectInfoModel); + fastDel = CommandUtil.systemFastDel(allLib); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.delete_project_file_failure.f007") + allLib.getAbsolutePath()); + } + } + + @RequestMapping(value = "releaseOutGiving", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage releaseOutGiving() { + NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); + if (nodeProjectInfoModel != null) { + NodeProjectInfoModel update = new NodeProjectInfoModel(); + update.setOutGivingProject(false); + projectInfoService.updateById(update, nodeProjectInfoModel.getId()); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.release_successful.f2ca")); + } + + @RequestMapping(value = "change-workspace-id", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage changeWorkspaceId(String newWorkspaceId, String newNodeId) { + Assert.hasText(newWorkspaceId, I18nMessageUtil.get("i18n.select_workspace_to_modify.ac87")); + Assert.hasText(newWorkspaceId, I18nMessageUtil.get("i18n.select_node_to_modify.6617")); + NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); + if (nodeProjectInfoModel != null) { + NodeProjectInfoModel update = new NodeProjectInfoModel(); + update.setNodeId(newNodeId); + update.setWorkspaceId(newWorkspaceId); + projectInfoService.updateById(update, nodeProjectInfoModel.getId()); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } +} 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 new file mode 100644 index 0000000000..70ed7da99f --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileBackupController.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.manage; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.model.data.NodeProjectInfoModel; +import org.dromara.jpom.service.ProjectFileBackupService; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +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.HttpServletResponse; +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 项目备份文件管理 + * + * @author bwcx_jzy + * @since 2022/5/11 + */ +@RestController +@RequestMapping(value = "/manage/file/") +@Slf4j +public class ProjectFileBackupController extends BaseAgentController { + + private final ProjectFileBackupService projectFileBackupService; + + public ProjectFileBackupController(ProjectFileBackupService projectFileBackupService) { + this.projectFileBackupService = projectFileBackupService; + } + + /** + * 查询备份列表 + * + * @param id 项目ID + * @return list + */ + @RequestMapping(value = "list-backup", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage listBackup(String id) { + // + NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id); + // 合并 + projectFileBackupService.margeBackupPath(projectInfoModel); + // + File path = projectFileBackupService.pathProject(projectInfoModel); + // + List collect = Arrays.stream(Optional.ofNullable(path.listFiles()).orElse(new File[0])) + .filter(FileUtil::isDirectory) + .collect(Collectors.toList()); + if (CollUtil.isEmpty(collect)) { + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b")); + } + List arrayFile = FileUtils.parseInfo(collect, true, path.getAbsolutePath(), projectInfoModel.isDisableScanDir()); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("path", FileUtil.getAbsolutePath(path)); + jsonObject.put("list", arrayFile); + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b"), jsonObject); + } + + /** + * 获取指定备份的文件列表 + * + * @param id 项目 + * @param path 读取的二级目录 + * @param backupId 备份id + * @return list + */ + @RequestMapping(value = "backup-item-files", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> backupItemFiles(String id, String path, @ValidatorItem String backupId) { + // 查询项目路径 + NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(); + 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[]{}; + if (ArrayUtil.isEmpty(filesAll)) { + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b"), Collections.emptyList()); + } + List arrayFile = FileUtils.parseInfo(filesAll, false, lib.getAbsolutePath(), projectInfoModel.isDisableScanDir()); + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b"), arrayFile); + } + + /** + * 将执行文件下载到客户端 本地 + * + * @param id 项目id + * @param filename 文件名 + * @param levelName 文件夹名 + * @param backupId 备份id + */ + @GetMapping(value = "backup-download", produces = MediaType.APPLICATION_JSON_VALUE) + public void download(String id, @ValidatorItem String backupId, @ValidatorItem String filename, String levelName, HttpServletResponse response) { + try { + NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(); + File lib = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId); + File file = FileUtil.file(lib, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); + if (file.isDirectory()) { + ServletUtil.write(response, I18nMessageUtil.get("i18n.folder_download_not_supported.c3b7"), MediaType.TEXT_HTML_VALUE); + return; + } + ServletUtil.write(response, file); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.download_exception.e616"), e); + ServletUtil.write(response, I18nMessageUtil.get("i18n.download_file_error.5bcd") + e.getMessage(), MediaType.TEXT_HTML_VALUE); + } + } + + /** + * 删除文件 + * + * @param id 项目ID + * @param backupId 备份ID + * @param filename 文件名 + * @param levelName 层级目录 + * @return msg + */ + @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 = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId); + File file = FileUtil.file(lib, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); + CommandUtil.systemFastDel(file); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 还原项目文件 + * + * @param id 项目ID + * @param backupId 备份ID + * @param type 类型 clear 清空还原 + * @param filename 文件名 + * @param levelName 目录 + * @return msg + */ + @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 = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId); + File projectPath = projectInfoService.resolveLibFile(projectInfoModel); + // + File backupFile; + File projectFile; + if (StrUtil.isEmpty(filename)) { + // 目录 + backupFile = FileUtil.file(backupPath, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR)); + Assert.state(FileUtil.exist(backupFile), I18nMessageUtil.get("i18n.file_not_exist.5091")); + projectFile = FileUtil.file(projectPath, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR)); + // 创建文件 + FileUtil.mkdir(projectFile); + // 清空 + if (StrUtil.equalsIgnoreCase(type, "clear")) { + FileUtil.clean(projectFile); + } + // + FileUtil.copyContent(backupFile, projectFile, true); + } else { + // 文件 + backupFile = FileUtil.file(backupPath, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); + Assert.state(FileUtil.exist(backupFile), I18nMessageUtil.get("i18n.file_not_exist.5091")); + projectFile = FileUtil.file(projectPath, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); + FileUtil.copy(backupFile, projectFile, true); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.restore_success.4c7f")); + } + + +} 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 new file mode 100644 index 0000000000..484c8af264 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileControl.java @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.manage; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.compress.CompressUtil; +import cn.hutool.extra.compress.archiver.Archiver; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.HttpUtil; +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.CommandOpResult; +import org.dromara.jpom.common.commander.ProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.configuration.AgentConfig; +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.service.ProjectFileBackupService; +import org.dromara.jpom.service.WhitelistDirectoryService; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.CompressionFileUtil; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.StringUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 项目文件管理 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +@RestController +@RequestMapping(value = "/manage/file/") +@Slf4j +public class ProjectFileControl extends BaseAgentController { + + private final WhitelistDirectoryService whitelistDirectoryService; + private final AgentConfig agentConfig; + private final ProjectFileBackupService projectFileBackupService; + private final ProjectCommander projectCommander; + + 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 = getProjectInfoModel(); + String lib = projectInfoService.resolveLibPath(pim); + File fileDir = FileUtil.file(lib, StrUtil.emptyToDefault(path, FileUtil.FILE_SEPARATOR)); + boolean exist = FileUtil.exist(fileDir); + if (!exist) { + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b"), Collections.emptyList()); + } + // + File[] filesAll = fileDir.listFiles(); + if (ArrayUtil.isEmpty(filesAll)) { + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b"), Collections.emptyList()); + } + boolean disableScanDir = pim.isDisableScanDir(); + List arrayFile = FileUtils.parseInfo(filesAll, false, lib, disableScanDir); + AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); + for (JSONObject jsonObject : arrayFile) { + String filename = jsonObject.getString("filename"); + jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(whitelist.getAllowEditSuffix(), filename)); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.query_success.d72b"), arrayFile); + } + + /** + * 对比文件 + * + * @param diffFileVo 参数 + * @return json + */ + @PostMapping(value = "diff_file", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage diffFile(@RequestBody DiffFileVo diffFileVo) { + String id = diffFileVo.getId(); + NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id); + // + List data = diffFileVo.getData(); + Assert.notEmpty(data, I18nMessageUtil.get("i18n.comparison_data_not_found.413e")); + // 扫描项目目录下面的所有文件 + 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 -> { + // + JSONObject item = new JSONObject(); + item.put("name", StringUtil.delStartPath(file, path, true)); + item.put("sha1", SecureUtil.sha1(file)); + return item; + }).collect(Collectors.toList()); + // 得到 当前下面文件夹下面所有的文件信息 map + Map nowMap = CollStreamUtil.toMap(collect, + jsonObject12 -> jsonObject12.getString("name"), + jsonObject1 -> jsonObject1.getString("sha1")); + // 将需要对应的信息转为 map + Map tryMap = CollStreamUtil.toMap(data, DiffFileVo.DiffItem::getName, DiffFileVo.DiffItem::getSha1); + // 对应需要 当前项目文件夹下没有的和文件内容有变化的 + List canSync = tryMap.entrySet() + .stream() + .filter(stringStringEntry -> { + String nowSha1 = nowMap.get(stringStringEntry.getKey()); + if (StrUtil.isEmpty(nowSha1)) { + // 不存在 + return true; + } + // 如果 文件信息一致 则过滤 + return !StrUtil.equals(stringStringEntry.getValue(), nowSha1); + }) + .map(stringStringEntry -> { + // + JSONObject item = new JSONObject(); + item.put("name", stringStringEntry.getKey()); + item.put("sha1", stringStringEntry.getValue()); + return item; + }) + .collect(Collectors.toList()); + // 对比项目文件夹下有对,但是需要对应对信息里面没有对。此类文件需要删除 + List delArray = nowMap.entrySet() + .stream() + .filter(stringStringEntry -> !tryMap.containsKey(stringStringEntry.getKey())) + .map(stringStringEntry -> { + // + JSONObject item = new JSONObject(); + item.put("name", stringStringEntry.getKey()); + item.put("sha1", stringStringEntry.getValue()); + return item; + }) + .collect(Collectors.toList()); + // + JSONObject result = new JSONObject(); + result.put("diff", canSync); + result.put("del", delArray); + return JsonMessage.success("", result); + } + + + private void saveProjectFileBefore(File lib, NodeProjectInfoModel projectInfoModel) throws Exception { + String closeFirstStr = getParameter("closeFirst"); + // 判断是否需要先关闭项目 + boolean closeFirst = BooleanUtil.toBoolean(closeFirstStr); + if (closeFirst) { + CommandOpResult result = projectCommander.execCommand(ConsoleCommandOp.stop, projectInfoModel); + Assert.state(result.isSuccess(), I18nMessageUtil.get("i18n.close_project_failure.a1d2") + result.msgStr()); + } + String clearType = getParameter("clearType"); + // 判断是否需要清空 + if ("clear".equalsIgnoreCase(clearType)) { + CommandUtil.systemFastDel(lib); + } + } + + @RequestMapping(value = "upload-sharding", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage uploadSharding(MultipartFile file, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5) throws Exception { + String tempPathName = agentConfig.getFixedTempPathName(); + this.uploadSharding(file, tempPathName, sliceId, totalSlice, nowSlice, fileSumMd5); + + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + @RequestMapping(value = "sharding-merge", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage shardingMerge(String type, + String levelName, + Integer stripComponents, + String sliceId, + Integer totalSlice, + String fileSumMd5, + String after) throws Exception { + String tempPathName = agentConfig.getFixedTempPathName(); + File successFile = this.shardingTryMerge(tempPathName, sliceId, totalSlice, fileSumMd5); + // 处理上传文件 + return this.upload(successFile, type, levelName, stripComponents, after); + } + + /** + * 处理上传文件 + * + * @param file 上传的文件 + * @param type 上传类型 + * @param levelName 文件夹 + * @param stripComponents 剔除文件夹 + * @param after 上传之后 + * @return 结果 + * @throws Exception 异常 + */ + private IJsonMessage upload(File file, String type, String levelName, Integer stripComponents, String after) throws Exception { + NodeProjectInfoModel pim = getProjectInfoModel(); + File libFile = projectInfoService.resolveLibFile(pim); + File lib = StrUtil.isEmpty(levelName) ? libFile : FileUtil.file(libFile, levelName); + // 备份文件 + String backupId = projectFileBackupService.backup(pim); + try { + // + this.saveProjectFileBefore(lib, pim); + if ("unzip".equals(type)) { + // 解压 + try { + int stripComponentsValue = Convert.toInt(stripComponents, 0); + CompressionFileUtil.unCompress(file, lib, stripComponentsValue); + } finally { + if (!FileUtil.del(file)) { + log.error(I18nMessageUtil.get("i18n.delete_file_failure_with_full_stop.6c96") + file.getPath()); + } + } + } else { + // 移动文件到对应目录 + FileUtil.mkdir(lib); + FileUtil.move(file, lib, true); + } + 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 { + projectFileBackupService.checkDiff(pim, backupId); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + private JsonMessage saveProjectFileAfter(String after, NodeProjectInfoModel pim) throws Exception { + if (StrUtil.isEmpty(after)) { + return null; + } + log.debug(I18nMessageUtil.get("i18n.prepare_restart.8251"), pim.getId(), after); + // + AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(after, AfterOpt.No.getCode())); + if ("restart".equalsIgnoreCase(after) || afterOpt == AfterOpt.Restart) { + CommandOpResult result = projectCommander.execCommand(ConsoleCommandOp.restart, pim); + + return new JsonMessage<>(result.isSuccess() ? 200 : 405, I18nMessageUtil.get("i18n.upload_success_and_restart.7bc3"), result); + } else if (afterOpt == AfterOpt.Order_Restart || afterOpt == AfterOpt.Order_Must_Restart) { + CommandOpResult result = projectCommander.execCommand(ConsoleCommandOp.restart, pim); + + return new JsonMessage<>(result.isSuccess() ? 200 : 405, I18nMessageUtil.get("i18n.upload_success_and_restart.7bc3"), result); + } + return null; + } + + @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage deleteFile(String filename, String type, String levelName) { + NodeProjectInfoModel pim = getProjectInfoModel(); + File libFile = projectInfoService.resolveLibFile(pim); + File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, StrUtil.SLASH)); + // 备份文件 + String backupId = projectFileBackupService.backup(pim); + try { + if ("clear".equalsIgnoreCase(type)) { + // 清空文件 + if (FileUtil.clean(file)) { + projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename); + return JsonMessage.success(I18nMessageUtil.get("i18n.clear_success_message.51f4")); + } + boolean run = projectCommander.isRun(pim); + Assert.state(!run, I18nMessageUtil.get("i18n.file_in_use_stop_project_first.a2c3")); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.delete_failure_with_colon_and_full_stop.bc42") + file.getAbsolutePath()); + } else { + // 删除文件 + Assert.hasText(filename, I18nMessageUtil.get("i18n.select_file_to_delete.33d6")); + file = FileUtil.file(file, filename); + if (FileUtil.del(file)) { + projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.delete_failure.acf0")); + } + } finally { + projectFileBackupService.checkDiff(pim, backupId); + } + } + + + @RequestMapping(value = "batch_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage batchDelete(@RequestBody DiffFileVo diffFileVo) { + String id = diffFileVo.getId(); + String dir = diffFileVo.getDir(); + NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id); + // 备份文件 + String backupId = projectFileBackupService.backup(projectInfoModel); + try { + // + List data = diffFileVo.getData(); + Assert.notEmpty(data, I18nMessageUtil.get("i18n.comparison_data_not_found.413e")); + File libFile = projectInfoService.resolveLibFile(projectInfoModel); + // + 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)) { + continue; + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.delete_failure_with_colon_and_full_stop.bc42") + file.getAbsolutePath()); + } + projectCommander.asyncWebHooks(projectInfoModel, "fileChange", "changeEvent", "batch-delete", "levelName", dir); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } finally { + projectFileBackupService.checkDiff(projectInfoModel, backupId); + } + + } + + /** + * 读取文件内容 (只能处理文本文件) + * + * @param filePath 相对项目文件的文件夹 + * @param filename 读取的文件名 + * @return json + */ + @PostMapping(value = "read_file", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage readFile(String filePath, String filename) { + NodeProjectInfoModel pim = getProjectInfoModel(); + filePath = StrUtil.emptyToDefault(filePath, File.separator); + // 判断文件后缀 + AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); + Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename); + File libFile = projectInfoService.resolveLibFile(pim); + File file = FileUtil.file(libFile, filePath, filename); + String ymlString = FileUtil.readString(file, charset); + return JsonMessage.success("", ymlString); + } + + /** + * copy + * + * @param filePath 相对项目文件的文件夹 + * @param filename 文件名 + * @return json + */ + @PostMapping(value = "copy", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage copy(String filePath, String filename) { + NodeProjectInfoModel pim = getProjectInfoModel(); + filePath = StrUtil.emptyToDefault(filePath, File.separator); + File libFile = projectInfoService.resolveLibFile(pim); + File file = FileUtil.file(libFile, filePath, filename); + int counter = 1; + String baseName = FileUtil.mainName(file); + String extension = FileUtil.extName(file); + if (StrUtil.isNotEmpty(extension)) { + extension = StrUtil.DOT + extension; + } else { + extension = StrUtil.EMPTY; + } + String newName; + File targetFile; + // 生成不冲突的新文件名 + do { + newName = StrUtil.format("{}({}){}", baseName, counter, extension); + targetFile = FileUtil.file(libFile, filePath, newName); + counter++; + } while (FileUtil.exist(targetFile)); + if (FileUtil.isDirectory(file)) { + FileUtil.copyContent(file, targetFile, false); + } else { + FileUtil.copy(file, targetFile, false); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.copy_success.20a4")); + } + + /** + * compress + * + * @param filePath 相对项目文件的文件夹 + * @param filename 文件名 + * @return json + */ + @PostMapping(value = "compress", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage compress(String filePath, String filename, String type) { + NodeProjectInfoModel pim = getProjectInfoModel(); + filePath = StrUtil.emptyToDefault(filePath, File.separator); + File libFile = projectInfoService.resolveLibFile(pim); + File file = FileUtil.file(libFile, filePath, filename); + Assert.state(FileUtil.isDirectory(file), I18nMessageUtil.get("i18n.select_folder_to_compress.915f")); + String ext; + if (StrUtil.equals(type, "zip")) { + ext = ".zip"; + } else if (StrUtil.equals(type, "tar")) { + ext = ".tar"; + } else if (StrUtil.equals(type, "tar.gz")) { + ext = ".tar.gz"; + } else { + return JsonMessage.fail(I18nMessageUtil.get("i18n.compression_type_not_supported.9dea") + type); + } + int counter = 0; + String baseName = FileUtil.mainName(file); + String newName; + File targetFile; + // 生成不冲突的新文件名 + do { + if (counter == 0) { + newName = StrUtil.format("{}{}", baseName, ext); + } else { + newName = StrUtil.format("{}({}){}", baseName, counter, ext); + } + targetFile = FileUtil.file(libFile, filePath, newName); + counter++; + } while (FileUtil.exist(targetFile)); + // + try (Archiver archiver = CompressUtil.createArchiver(Charset.defaultCharset(), FileUtil.extName(targetFile), targetFile)) { + archiver.add(file); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.compression_success.80b3")); + } + + /** + * 保存文件内容 (只能处理文本文件) + * + * @param filePath 相对项目文件的文件夹 + * @param filename 读取的文件名 + * @param fileText 文件内容 + * @return json + */ + @PostMapping(value = "update_config_file", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage updateConfigFile(String filePath, String filename, String fileText) { + NodeProjectInfoModel pim = getProjectInfoModel(); + filePath = StrUtil.emptyToDefault(filePath, File.separator); + // 判断文件后缀 + AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist(); + Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename); + // 备份文件 + String backupId = projectFileBackupService.backup(pim); + File libFile = projectInfoService.resolveLibFile(pim); + try { + FileUtil.writeString(fileText, FileUtil.file(libFile, filePath, filename), charset); + projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "edit", "levelName", filePath, "fileName", filename); + return JsonMessage.success(I18nMessageUtil.get("i18n.file_write_success.804a")); + } finally { + projectFileBackupService.checkDiff(pim, backupId); + } + } + + + /** + * 将执行文件下载到客户端 本地 + * + * @param id 项目id + * @param filename 文件名 + * @param levelName 文件夹名 + */ + @GetMapping(value = "download", produces = MediaType.APPLICATION_JSON_VALUE) + public void download(String id, String filename, String levelName, HttpServletResponse response) { + Assert.hasText(filename, I18nMessageUtil.get("i18n.select_file.9feb")); +// String safeFileName = pathSafe(filename); +// if (StrUtil.isEmpty(safeFileName)) { +// return JsonMessage.getString(405, "非法操作"); +// } + NodeProjectInfoModel pim = getProjectInfoModel(); + File libFile = projectInfoService.resolveLibFile(pim); + try { + File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); + if (file.isDirectory()) { + ServletUtil.write(response, JsonMessage.getString(400, I18nMessageUtil.get("i18n.folder_download_not_supported.c3b7")), MediaType.APPLICATION_JSON_VALUE); + return; + } + ServletUtil.write(response, file); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.download_exception.e616"), e); + ServletUtil.write(response, JsonMessage.getString(400, I18nMessageUtil.get("i18n.download_failed_retry.c113"), e.getMessage()), MediaType.APPLICATION_JSON_VALUE); + } + } + + /** + * 下载远程文件 + * + * @param id 项目id + * @param url 远程 url 地址 + * @param levelName 保存的文件夹 + * @param unzip 是否为压缩包、true 将自动解压 + * @param stripComponents 剔除层级 + * @return json + */ + @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, I18nMessageUtil.get("i18n.correct_remote_address_required.0ce1")); + NodeProjectInfoModel pim = getProjectInfoModel(); + File libFile = projectInfoService.resolveLibFile(pim); + String tempPathName = agentConfig.getTempPathName(); + // + String backupId = null; + try { + File downloadFile = HttpUtil.downloadFileFromUrl(url, tempPathName); + String fileSize = FileUtil.readableFileSize(downloadFile); + // 备份文件 + backupId = projectFileBackupService.backup(pim); + File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR)); + FileUtil.mkdir(file); + if (BooleanUtil.toBoolean(unzip)) { + // 需要解压文件 + try { + int stripComponentsValue = Convert.toInt(stripComponents, 0); + CompressionFileUtil.unCompress(downloadFile, file, stripComponentsValue); + } finally { + if (!FileUtil.del(downloadFile)) { + log.error(I18nMessageUtil.get("i18n.delete_file_failure_with_full_stop.6c96") + file.getPath()); + } + } + } else { + // 移动文件到对应目录 + FileUtil.move(downloadFile, file, true); + } + projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "remoteDownload", "levelName", levelName, "fileName", file.getName(), "url", url); + return JsonMessage.success(I18nMessageUtil.get("i18n.download_file_size.d4de") + fileSize); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.download_remote_file_exception.3ee0"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.download_remote_file_failed.fcc3") + e.getMessage()); + } finally { + projectFileBackupService.checkDiff(pim, backupId); + } + } + + /** + * 创建文件夹/文件 + * + * @param id 项目ID + * @param levelName 二级文件夹名 + * @param filename 文件名 + * @param unFolder true/1 为文件夹,false/2 为文件 + * @return json + */ + @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 = getProjectInfoModel(); + File libFile = projectInfoService.resolveLibFile(projectInfoModel); + File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename); + // + Assert.state(!FileUtil.exist(file), I18nMessageUtil.get("i18n.folder_or_file_exists.c687")); + boolean folder = !Convert.toBool(unFolder, false); + if (folder) { + FileUtil.mkdir(file); + } else { + FileUtil.touch(file); + } + projectCommander.asyncWebHooks(projectInfoModel, "fileChange", "changeEvent", "newFileOrFolder", "levelName", levelName, "fileName", filename, "folder", folder); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 修改文件夹/文件 + * + * @param id 项目ID + * @param levelName 二级文件夹名 + * @param filename 文件名 + * @param newname 新文件名 + * @return json + */ + @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 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), I18nMessageUtil.get("i18n.file_not_found.d952")); + Assert.state(!FileUtil.exist(newFile), I18nMessageUtil.get("i18n.file_name_already_exists.0d4e")); + + FileUtil.rename(file, newname, false); + projectCommander.asyncWebHooks(projectInfoModel, "fileChange", "changeEvent", "rename", "levelName", levelName, "fileName", filename, "newname", newname); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + +} 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 new file mode 100644 index 0000000000..3638d86d92 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectListController.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.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; + +/** + * 管理的信息获取接口 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@RestController +@RequestMapping(value = "/manage/") +@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; + } + + /** + * 获取项目的信息 + * + * @param id id + * @return item + * @see NodeProjectInfoModel + */ + @RequestMapping(value = "getProjectItem", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getProjectItem(String id) { + NodeProjectInfoModel nodeProjectInfoModel = projectInfoService.getItem(id); + if (nodeProjectInfoModel != null) { + RunMode runMode = nodeProjectInfoModel.getRunMode(); + if (runMode != RunMode.Dsl && runMode != RunMode.File) { + // 返回实际执行的命令 + 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); + } + + /** + * 程序项目信息 + * + * @return json + */ + @RequestMapping(value = "getProjectInfo", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> getProjectInfo() { + // 查询数据 + List nodeProjectInfoModels = projectInfoService.list(); + return JsonMessage.success("", nodeProjectInfoModels); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectLogBackController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectLogBackController.java new file mode 100644 index 0000000000..33efcd0d9e --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectLogBackController.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.manage; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.servlet.ServletUtil; +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.ProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.NodeProjectInfoModel; +import org.dromara.jpom.util.FileUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2019/4/17 + */ +@RestController +@RequestMapping(value = "manage/log") +@Slf4j +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) { + NodeProjectInfoModel nodeProjectInfoModel = getProjectInfoModel(); + JSONObject jsonObject = new JSONObject(); + // + //获取日志备份路径 + File logBack = projectInfoService.resolveLogBack(nodeProjectInfoModel); + boolean logBackBool = logBack.exists() && logBack.isDirectory(); + jsonObject.put("logBack", logBackBool); + 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 = projectCommander.backLog(pim); + if (msg.contains("ok")) { + return JsonMessage.success(I18nMessageUtil.get("i18n.reset_success.faa3")); + } + return new JsonMessage<>(201, I18nMessageUtil.get("i18n.reset_failed.5281") + msg); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.reset_log_failure.b3d3"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.reset_log_failure.b3d3")); + } + } + + @RequestMapping(value = "logBack_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage clear(String name) { + Assert.hasText(name, I18nMessageUtil.get("i18n.no_file_found.7d40")); + NodeProjectInfoModel pim = getProjectInfoModel(); + File logBack = projectInfoService.resolveLogBack(pim); + if (logBack.exists() && logBack.isDirectory()) { + logBack = FileUtil.file(logBack, name); + if (logBack.exists()) { + FileUtil.del(logBack); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.no_corresponding_file.97b5")); + } else { + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.no_corresponding_folder.621f")); + } + } + + @RequestMapping(value = "logBack_download", method = RequestMethod.GET) + public void download(String key, HttpServletResponse response) { + Assert.hasText(key, I18nMessageUtil.get("i18n.corresponding_file_required.57b3")); + try { + NodeProjectInfoModel pim = getProjectInfoModel(); + File logBack = projectInfoService.resolveLogBack(pim); + if (logBack.exists() && logBack.isDirectory()) { + logBack = FileUtil.file(logBack, key); + ServletUtil.write(response, logBack); + } else { + ServletUtil.write(response, JsonMessage.getString(400, I18nMessageUtil.get("i18n.no_corresponding_file_colon.8970") + logBack.getPath()), MediaType.APPLICATION_JSON_VALUE); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.download_exception.e616"), e); + ServletUtil.write(response, JsonMessage.getString(400, I18nMessageUtil.get("i18n.download_failed_retry.c113"), e.getMessage()), MediaType.APPLICATION_JSON_VALUE); + } + } + + @RequestMapping(value = "logBack", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage console() { + // 查询项目路径 + NodeProjectInfoModel pim = getProjectInfoModel(); + + JSONObject jsonObject = new JSONObject(); + NodeProjectInfoModel infoModel = projectInfoService.resolveModel(pim); + File logBack = projectInfoService.resolveLogBack(pim, infoModel); + if (logBack.exists() && logBack.isDirectory()) { + File[] filesAll = logBack.listFiles(); + if (filesAll != null) { + List jsonArray = FileUtils.parseInfo(filesAll, true, null, pim.isDisableScanDir()); + jsonObject.put("array", jsonArray); + } + } + jsonObject.put("id", pim.getId()); + jsonObject.put("logPath", projectInfoService.resolveAbsoluteLog(pim, infoModel)); + jsonObject.put("logBackPath", logBack.getAbsolutePath()); + return JsonMessage.success("", jsonObject); + } + + @RequestMapping(value = "export", method = RequestMethod.GET) + @ResponseBody + public void export(HttpServletResponse response) { + NodeProjectInfoModel pim = getProjectInfoModel(); + + File file = projectInfoService.resolveAbsoluteLogFile(pim); + if (!file.exists()) { + ServletUtil.write(response, JsonMessage.getString(400, I18nMessageUtil.get("i18n.log_file_not_found.7f2e") + file.getPath()), MediaType.APPLICATION_JSON_VALUE); + return; + } + ServletUtil.write(response, file); + } +} 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 new file mode 100644 index 0000000000..7cc287c473 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectStatusController.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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; +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.CommandOpResult; +import org.dromara.jpom.common.commander.ProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.socket.ConsoleCommandOp; +import org.dromara.jpom.util.CommandUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * 项目文件管理 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +@RestController +@RequestMapping(value = "/manage/") +@Slf4j +public class ProjectStatusController extends BaseAgentController { + private final ProjectCommander projectCommander; + + public ProjectStatusController(ProjectCommander projectCommander) { + this.projectCommander = projectCommander; + } + + /** + * 获取项目的进程id + * + * @param id 项目id + * @return json + */ + @RequestMapping(value = "getProjectStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getProjectStatus(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.incorrect_project_id.5f70") String id, String getCopy) { + NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(); + Assert.notNull(nodeProjectInfoModel, I18nMessageUtil.get("i18n.project_id_does_not_exist.6b9b")); + JSONObject jsonObject = new JSONObject(); + try { + CommandUtil.openCache(); + try { + CommandOpResult status = projectCommander.execCommand(ConsoleCommandOp.status, nodeProjectInfoModel); + jsonObject.put("pId", status.getPid()); + jsonObject.put("pIds", status.getPids()); + jsonObject.put("statusMsg", status.getStatusMsg()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.get_project_pid_failure.17b0"), e); + } + } finally { + CommandUtil.closeCache(); + } + return JsonMessage.success("", jsonObject); + } + + /** + * 获取项目的运行端口 + * + * @param ids ids + * @return obj + */ + @RequestMapping(value = "getProjectPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getProjectPort(String ids) { + Assert.hasText(ids, I18nMessageUtil.get("i18n.info_to_retrieve_not_found.96d7")); + JSONArray jsonArray = JSONArray.parseArray(ids); + JSONObject jsonObject = new JSONObject(); + try { + CommandUtil.openCache(); + for (Object object : jsonArray) { + String item = object.toString(); + JSONObject itemObj = new JSONObject(); + try { + NodeProjectInfoModel projectInfoServiceItem = projectInfoService.getItem(item); + itemObj.put("name", projectInfoServiceItem.getName()); + CommandOpResult commandOpResult = projectCommander.execCommand(ConsoleCommandOp.status, projectInfoServiceItem); + Integer pid = commandOpResult.getPid(); + // + itemObj.put("pid", pid); + itemObj.put("pids", commandOpResult.getPids()); + itemObj.put("statusMsg", commandOpResult.getStatusMsg()); + if (StrUtil.isNotEmpty(commandOpResult.getPorts())) { + itemObj.put("port", commandOpResult.getPorts()); + } else { + String port = projectCommander.getMainPort(pid); + itemObj.put("port", port); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.get_port_error.0698"), e); + itemObj.put("error", e.getMessage()); + } + jsonObject.put(item, itemObj); + } + } finally { + CommandUtil.closeCache(); + } + return JsonMessage.success("", jsonObject); + } + + + @RequestMapping(value = "operate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage operate(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.incorrect_project_id.5f70") String id, + @ValidatorItem String opt) throws Exception { + NodeProjectInfoModel item = projectInfoService.getItem(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_project_found.ef5e")); + ConsoleCommandOp consoleCommandOp = EnumUtil.fromStringQuietly(ConsoleCommandOp.class, opt); + Assert.notNull(consoleCommandOp, I18nMessageUtil.get("i18n.select_operation_type.63c6")); + Assert.state(consoleCommandOp.isCanOpt(), I18nMessageUtil.get("i18n.current_operation_not_supported.3aec") + opt); + CommandOpResult result = projectCommander.execCommand(consoleCommandOp, item); + String success = I18nMessageUtil.get("i18n.operation_succeeded.3313"); + String errorMsg = I18nMessageUtil.get("i18n.operation_failed_with_details.7280") + result.msgStr(); + return new JsonMessage<>(result.isSuccess() ? 200 : 201, result.isSuccess() ? success : errorMsg, result); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/vo/DiffFileVo.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/vo/DiffFileVo.java new file mode 100644 index 0000000000..4cb64360f8 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/vo/DiffFileVo.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.manage.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2021/12/16 + */ +@Data +public class DiffFileVo { + + /** + * 项目id + */ + private String id; + /** + * 需要对比的数据 + */ + private List data; + /** + * 需要对比的目录 + */ + private String dir; + + @Data + public static class DiffItem { + /** + * 名称 + */ + private String name; + /** + * 文件签名 + */ + private String sha1; + } +} 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 new file mode 100644 index 0000000000..d814cad695 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptController.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.script; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseAgentController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.data.NodeScriptExecLogModel; +import org.dromara.jpom.model.data.NodeScriptModel; +import org.dromara.jpom.script.NodeScriptProcessBuilder; +import org.dromara.jpom.service.script.NodeScriptExecLogServer; +import org.dromara.jpom.service.script.NodeScriptServer; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.StringUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 脚本管理 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@RestController +@RequestMapping(value = "/script") +public class ScriptController extends BaseAgentController { + + private final NodeScriptServer nodeScriptServer; + private final NodeScriptExecLogServer nodeScriptExecLogServer; + + public ScriptController(NodeScriptServer nodeScriptServer, + NodeScriptExecLogServer nodeScriptExecLogServer) { + this.nodeScriptServer = nodeScriptServer; + this.nodeScriptExecLogServer = nodeScriptExecLogServer; + } + + @RequestMapping(value = "list.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> list() { + return JsonMessage.success("", nodeScriptServer.list()); + } + + @RequestMapping(value = "item.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage item(String id) { + return JsonMessage.success("", nodeScriptServer.getItem(id)); + } + + @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage save(NodeScriptModel nodeScriptModel, String type, String global, String nodeId) { + Assert.notNull(nodeScriptModel, I18nMessageUtil.get("i18n.no_data.1ac0")); + Assert.hasText(nodeScriptModel.getContext(), I18nMessageUtil.get("i18n.content_is_empty.3122")); + // + String autoExecCron = nodeScriptModel.getAutoExecCron(); + autoExecCron = StringUtil.checkCron(autoExecCron, s -> s); + // + boolean globalBool = Convert.toBool(global, false); + if (globalBool) { + nodeScriptModel.setWorkspaceId(Const.WORKSPACE_GLOBAL); + } else { + nodeScriptModel.setWorkspaceId(getWorkspaceId()); + } + // + nodeScriptModel.setContext(nodeScriptModel.getContext()); + NodeScriptModel eModel = nodeScriptServer.getItem(nodeScriptModel.getId()); + boolean needCreate = false; + if ("add".equalsIgnoreCase(type)) { + Assert.isNull(eModel, I18nMessageUtil.get("i18n.id_already_exists.6208")); + + nodeScriptModel.setId(IdUtil.fastSimpleUUID()); + nodeScriptModel.setNodeId(nodeId); + nodeScriptServer.addItem(nodeScriptModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.addition_succeeded.3fda")); + } else if ("sync".equalsIgnoreCase(type)) { + // 同步脚本 + if (eModel == null) { + eModel = new NodeScriptModel(); + eModel.setId(nodeScriptModel.getId()); + eModel.setNodeId(nodeId); + needCreate = true; + } else { + if (!eModel.global() && nodeScriptModel.global()) { + // 修改绑定的节点id + eModel.setNodeId(nodeId); + } + } + eModel.setScriptType("server-sync"); + eModel.setWorkspaceId(nodeScriptModel.getWorkspaceId()); + } + Assert.notNull(eModel, I18nMessageUtil.get("i18n.data_not_exist.41f9")); + eModel.setName(nodeScriptModel.getName()); + eModel.setAutoExecCron(autoExecCron); + eModel.setDescription(nodeScriptModel.getDescription()); + eModel.setContext(nodeScriptModel.getContext()); + eModel.setDefArgs(nodeScriptModel.getDefArgs()); + if (needCreate) { + nodeScriptServer.addItem(eModel); + } else { + nodeScriptServer.updateItem(eModel); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage del(String id) { + nodeScriptServer.deleteItem(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 获取的日志 + * + * @param id id + * @param executeId 执行ID + * @param line 需要获取的行号 + * @return json + */ + @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 = "i18n.incorrect_line_number.5877") int line) { + NodeScriptModel item = nodeScriptServer.getItem(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + File logFile = item.logFile(executeId); + Assert.state(FileUtil.isFile(logFile), I18nMessageUtil.get("i18n.log_file_error.473b")); + + JSONObject data = FileUtils.readLogFile(logFile, line); + // 运行中 + data.put("run", NodeScriptProcessBuilder.isRun(executeId)); + return JsonMessage.success("", data); + } + + /** + * 删除日志 + * + * @param id id + * @param executeId 执行ID + * @return json + */ + @RequestMapping(value = "del_log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage delLog(@ValidatorItem() String id, + @ValidatorItem() String executeId) { + NodeScriptModel item = nodeScriptServer.getItem(id); + if (item == null) { + return JsonMessage.success(I18nMessageUtil.get("i18n.script_template_not_exist.1d5b")); + } + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + File logFile = item.logFile(executeId); + boolean fastDel = CommandUtil.systemFastDel(logFile); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.delete_log_file_failure.bf0b")); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 执行 + * + * @param id ID + * @param args 执行参数 + * @param params 环境变量参数 + * @return json + */ + @RequestMapping(value = "exec", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage exec(@ValidatorItem() String id, String args, String params) { + NodeScriptModel item = nodeScriptServer.getItem(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.script_not_exist.b180")); + String nowUserName = getNowUserName(); + + Map paramMap = Opt.ofBlankAble(params) + .map(JSONObject::parseObject) + .map(jsonObject -> { + Map paramMap1 = new HashMap<>(10); + for (Map.Entry entry : jsonObject.entrySet()) { + String key = StrUtil.format("trigger_{}", entry.getKey()); + key = StrUtil.toUnderlineCase(key); + paramMap1.put(key, StrUtil.toString(entry.getValue())); + } + return paramMap1; + }) + .orElse(null); + // + + String execute = nodeScriptServer.execute(item, 2, nowUserName, null, args, paramMap); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_execution.00d7"), execute); + } + + /** + * 同步定时执行日志 + * + * @param pullCount 领取个数 + * @return json + */ + @RequestMapping(value = "pull_exec_log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> pullExecLog(@ValidatorItem int pullCount) { + Assert.state(pullCount > 0, "pull count error"); + List list = nodeScriptExecLogServer.list(); + list = CollUtil.sub(list, 0, pullCount); + if (list == null) { + return JsonMessage.success("", Collections.emptyList()); + } + return JsonMessage.success("", list); + } + + /** + * 删除定时执行日志 + * + * @param jsonObject 拉起参数 + * @return json + */ + @RequestMapping(value = "del_exec_log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage delExecLog(@RequestBody JSONObject jsonObject) { + JSONArray ids = jsonObject.getJSONArray("ids"); + if (ids != null) { + for (Object id : ids) { + String idStr = (String) id; + nodeScriptExecLogServer.deleteItem(idStr); + } + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + @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, I18nMessageUtil.get("i18n.select_workspace_to_modify.ac87")); + Assert.hasText(newWorkspaceId, I18nMessageUtil.get("i18n.select_node_to_modify.6617")); + NodeScriptModel item = nodeScriptServer.getItem(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.script_info_not_found.bd8d")); + // + NodeScriptModel update = new NodeScriptModel(); + update.setNodeId(newNodeId); + update.setWorkspaceId(newWorkspaceId); + nodeScriptServer.updateById(update, item.getId()); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptLibraryController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptLibraryController.java new file mode 100644 index 0000000000..e1b04c7c5b --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptLibraryController.java @@ -0,0 +1,72 @@ +package org.dromara.jpom.controller.script; + +import cn.hutool.core.io.FileUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.model.data.ScriptLibraryModel; +import org.dromara.jpom.service.script.ScriptLibraryService; +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.io.File; +import java.util.List; + +/** + * @author bwcx_jzy1 + * @since 2024/6/1 + */ +@RestController +@RequestMapping(value = "/script-library") +@Slf4j +public class ScriptLibraryController extends BaseAgentController { + + private final ScriptLibraryService scriptLibraryService; + + public ScriptLibraryController(ScriptLibraryService scriptLibraryService) { + this.scriptLibraryService = scriptLibraryService; + } + + @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> list() { + List modelList = scriptLibraryService.list(); + return JsonMessage.success("", modelList); + } + + @RequestMapping(value = "get", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage get(@ValidatorItem String id) { + ScriptLibraryModel scriptModel = scriptLibraryService.get(id); + if (scriptModel != null) { + return JsonMessage.success("", scriptModel); + } + return JsonMessage.fail(I18nMessageUtil.get("i18n.missing_script_message.af89")); + } + + @RequestMapping(value = "save", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage save(@ValidatorItem String id, + @ValidatorItem(msg = "i18n.script_content_cannot_be_empty.49be") String script, + String description, + String version) { + File file = FileUtil.file(scriptLibraryService.getGlobalScriptDir(), id + ".json"); + ScriptLibraryModel scriptModel = new ScriptLibraryModel(); + scriptModel.setId(id); + scriptModel.setScript(script); + scriptModel.setDescription(description); + scriptModel.setVersion(version); + FileUtil.writeUtf8String(JSONObject.toJSONString(scriptModel), file); + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } + + @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage del(@ValidatorItem String id) { + File file = FileUtil.file(scriptLibraryService.getGlobalScriptDir(), id + ".json"); + FileUtil.del(file); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } +} 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 new file mode 100644 index 0000000000..b00600863b --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentCacheManageController.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.FileUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.event.ICacheTask; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.BaseAgentController; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.commander.AbstractProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.configuration.AgentConfig; +import org.dromara.jpom.configuration.SystemConfig; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.model.data.ScriptLibraryModel; +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.script.ScriptLibraryService; +import org.dromara.jpom.service.system.AgentWorkspaceEnvVarService; +import org.dromara.jpom.socket.AgentFileTailWatcher; +import org.dromara.jpom.util.CommandUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.stream.Collectors; + +/** + * 缓存管理 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@RestController +@RequestMapping(value = "system") +public class AgentCacheManageController extends BaseAgentController implements ICacheTask { + + private final AgentWorkspaceEnvVarService agentWorkspaceEnvVarService; + private final JpomApplication configBean; + private final NodeScriptExecLogServer nodeScriptExecLogServer; + private final SystemConfig systemConfig; + private final ScriptLibraryService scriptLibraryService; + + private long dataSize; + private long oldJarsSize; + private long tempFileSize; + + public AgentCacheManageController(AgentWorkspaceEnvVarService agentWorkspaceEnvVarService, + JpomApplication configBean, + NodeScriptExecLogServer nodeScriptExecLogServer, + AgentConfig agentConfig, + ScriptLibraryService scriptLibraryService) { + this.agentWorkspaceEnvVarService = agentWorkspaceEnvVarService; + this.configBean = configBean; + this.nodeScriptExecLogServer = nodeScriptExecLogServer; + this.systemConfig = agentConfig.getSystem(); + this.scriptLibraryService = scriptLibraryService; + } + + /** + * 缓存信息 + * + * @return json + */ + @PostMapping(value = "cache", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage cache() { + JSONObject jsonObject = new JSONObject(); + // + jsonObject.put("fileSize", this.tempFileSize); + jsonObject.put("dataSize", this.dataSize); + jsonObject.put("oldJarsSize", this.oldJarsSize); + jsonObject.put("pidPort", AbstractProjectCommander.PID_PORT.size()); + + int oneLineCount = AgentFileTailWatcher.getOneLineCount(); + jsonObject.put("readFileOnLineCount", oneLineCount); + jsonObject.put("taskList", CronUtils.list()); + jsonObject.put("pluginSize", PluginFactory.size()); + // + WorkspaceEnvVarModel item = agentWorkspaceEnvVarService.getItem(getWorkspaceId()); + if (item != null) { + Map varData = item.getVarData(); + if (varData != null) { + jsonObject.put("envVarKeys", varData.keySet()); + } + } + // + List scriptLibraryModels = scriptLibraryService.list(); + Map scriptLibraryTagMap = scriptLibraryModels.stream() + .collect(Collectors.toMap(ScriptLibraryModel::getTag, ScriptLibraryModel::getVersion)); + jsonObject.put("scriptLibraryTagMap", scriptLibraryTagMap); + // + jsonObject.put("dateTime", DateTime.now().toString()); + jsonObject.put("timeZoneId", TimeZone.getDefault().getID()); + // 待同步待日志数 + int size = nodeScriptExecLogServer.size(); + jsonObject.put("scriptExecLogSize", size); + jsonObject.put("timerMatchSecond", systemConfig.isTimerMatchSecond()); + // + return JsonMessage.success("", jsonObject); + } + + /** + * 清空缓存 + * + * @param type 缓存类型 + * @return json + */ + @RequestMapping(value = "clearCache", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage clearCache(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.type_error.395f") String type) { + switch (type) { + case "pidPort": + AbstractProjectCommander.PID_PORT.clear(); + break; + case "oldJarsSize": { + File oldJarsPath = JpomManifest.getOldJarsPath(); + boolean clean = CommandUtil.systemFastDel(oldJarsPath); + Assert.state(!clean, I18nMessageUtil.get("i18n.clear_old_version_package_failed.021c")); + break; + } + case "fileSize": { + File tempPath = configBean.getTempPath(); + boolean clean = CommandUtil.systemFastDel(tempPath); + Assert.state(!clean, I18nMessageUtil.get("i18n.clear_file_cache_failed.5cd1")); + break; + } + default: + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.no_type_specified.8c65") + type); + + } + return JsonMessage.success(I18nMessageUtil.get("i18n.clear_success.2685")); + } + + @Override + public void refreshCache() { + File file = configBean.getTempPath(); + this.tempFileSize = FileUtil.size(file); + this.dataSize = configBean.dataSize(); + File oldJarsPath = JpomManifest.getOldJarsPath(); + this.oldJarsSize = FileUtil.size(oldJarsPath); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentWorkspaceEnvVarController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentWorkspaceEnvVarController.java new file mode 100644 index 0000000000..e0d687be1a --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentWorkspaceEnvVarController.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.map.MapUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseAgentController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.model.system.WorkspaceEnvVarModel; +import org.dromara.jpom.service.system.AgentWorkspaceEnvVarService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lidaofu + * @since 2022/3/8 + */ +@RestController +@RequestMapping(value = "/system/workspace_env") +public class AgentWorkspaceEnvVarController extends BaseAgentController { + + private final AgentWorkspaceEnvVarService agentWorkspaceEnvVarService; + + public AgentWorkspaceEnvVarController(AgentWorkspaceEnvVarService agentWorkspaceEnvVarService) { + this.agentWorkspaceEnvVarService = agentWorkspaceEnvVarService; + } + + /** + * 更新环境变量 + * + * @param name 名称 + * @param value 值 + * @param description 描述 + * @return json + */ + @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage updateWorkspaceEnvVar(@ValidatorItem String name, + @ValidatorItem String value, + @ValidatorItem String description, + Integer privacy) { + String workspaceId = getWorkspaceId(); + synchronized (AgentWorkspaceEnvVarController.class) { + WorkspaceEnvVarModel.WorkspaceEnvVarItemModel workspaceEnvVarModel = new WorkspaceEnvVarModel.WorkspaceEnvVarItemModel(); + workspaceEnvVarModel.setName(name); + workspaceEnvVarModel.setValue(value); + workspaceEnvVarModel.setDescription(description); + workspaceEnvVarModel.setPrivacy(privacy); + // + WorkspaceEnvVarModel item = agentWorkspaceEnvVarService.getItem(workspaceId); + if (null == item) { + item = new WorkspaceEnvVarModel(); + item.setVarData(MapUtil.of(name, workspaceEnvVarModel)); + item.setName(workspaceId); + item.setId(workspaceId); + agentWorkspaceEnvVarService.addItem(item); + } else { + item.put(name, workspaceEnvVarModel); + agentWorkspaceEnvVarService.updateItem(item); + } + } + return JsonMessage.success(I18nMessageUtil.get("i18n.update_success.55aa")); + } + + + /** + * 删除环境变量 + * + * @param name 名称 + * @return json + */ + @PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage delete(@ValidatorItem String name) { + String workspaceId = getWorkspaceId(); + synchronized (AgentWorkspaceEnvVarController.class) { + // + WorkspaceEnvVarModel item = agentWorkspaceEnvVarService.getItem(workspaceId); + if (null != item) { + item.remove(name); + agentWorkspaceEnvVarService.updateItem(item); + } + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/system/LogManageController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/system/LogManageController.java new file mode 100644 index 0000000000..0e81b1e1e4 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/LogManageController.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseAgentController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.socket.AgentFileTailWatcher; +import org.dromara.jpom.system.LogbackConfig; +import org.dromara.jpom.util.DirTreeUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 系统日志管理 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@RestController +@RequestMapping(value = "system") +public class LogManageController extends BaseAgentController { + + + @RequestMapping(value = "log_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> logData() { + List data = DirTreeUtil.getTreeData(LogbackConfig.getPath()); + return JsonMessage.success("", data); + } + + + @RequestMapping(value = "log_del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public IJsonMessage logData(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_path_error.f482") String path) { + File file = FileUtil.file(LogbackConfig.getPath(), path); + // 判断修改时间 + long modified = file.lastModified(); + Assert.state(System.currentTimeMillis() - modified > TimeUnit.DAYS.toMillis(1), I18nMessageUtil.get("i18n.cannot_delete_recent_logs.ee19")); + AgentFileTailWatcher.offlineFile(file); + if (FileUtil.del(file)) { + FileUtil.cleanEmpty(file.getParentFile()); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.delete_failure.acf0")); + } + + + @RequestMapping(value = "log_download", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public void logDownload(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_path_error.f482") String path, HttpServletResponse response) { + File file = FileUtil.file(LogbackConfig.getPath(), path); + if (file.isFile()) { + ServletUtil.write(response, file); + } + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java new file mode 100644 index 0000000000..ef96451551 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +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.JpomApplication; +import org.dromara.jpom.common.BaseAgentController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.system.ExtConfigBean; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * 系统配置 + * + * @author bwcx_jzy + * @since 2019/08/08 + */ +@RestController +@RequestMapping(value = "system") +@Slf4j +public class SystemConfigController extends BaseAgentController { + + @RequestMapping(value = "getConfig.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage config() throws IOException { + Resource resource = ExtConfigBean.getResource(); + String content = IoUtil.read(resource.getInputStream(), CharsetUtil.CHARSET_UTF_8); + JSONObject json = new JSONObject(); + json.put("content", content); + json.put("file", FileUtil.getAbsolutePath(resource.getFile())); + return JsonMessage.success("", json); + } + + @RequestMapping(value = "save_config.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage saveConfig(@ValidatorItem(msg = "i18n.content_cannot_be_empty.9f0d") String content, String restart) throws IOException { + try { + YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); + // @author hjk 前端编辑器允许使用tab键,并设定为2个空格,再转换为yml时要把tab键换成2个空格 + ByteArrayResource resource = new ByteArrayResource(content.replace("\t", " ").getBytes(StandardCharsets.UTF_8)); + yamlPropertySourceLoader.load("test", resource); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.content_format_error.ce15"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.content_format_error_with_detail.c846") + e.getMessage()); + } + Resource resource = ExtConfigBean.getResource(); + Assert.state(resource.isFile(), I18nMessageUtil.get("i18n.configuration_modification_not_supported.5872")); + FileUtil.writeString(content, resource.getFile(), CharsetUtil.CHARSET_UTF_8); + + if (Convert.toBool(restart, false)) { + // 重启 + JpomApplication.restart(); + return JsonMessage.success(Const.UPGRADE_MSG.get()); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } +} 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 new file mode 100644 index 0000000000..b4ae2eb5a4 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.JpomApplication; +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.common.i18n.I18nMessageUtil; +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; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; + +/** + * 在线升级 + * + * @author bwcx_jzy + * @since 2019/7/22 + */ +@RestController +@RequestMapping(value = "system") +public class SystemUpdateController extends BaseAgentController { + + private final AgentConfig agentConfig; + private final JpomApplication configBean; + + public SystemUpdateController(AgentConfig agentConfig, + JpomApplication configBean) { + this.agentConfig = agentConfig; + this.configBean = configBean; + } + + @PostMapping(value = "upload-jar-sharding", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage uploadJarSharding(MultipartFile file, String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5) throws IOException { + // + String tempPathName = agentConfig.getFixedTempPathName(); + this.uploadSharding(file, tempPathName, sliceId, totalSlice, nowSlice, fileSumMd5, "jar", "zip"); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + @PostMapping(value = "upload-jar-sharding-merge", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage uploadJarShardingMerge(String sliceId, + Integer totalSlice, + String fileSumMd5) throws IOException { + // + String tempPathName = agentConfig.getFixedTempPathName(); + File successFile = this.shardingTryMerge(tempPathName, sliceId, totalSlice, fileSumMd5); + Objects.requireNonNull(JpomManifest.getScriptFile()); + String absolutePath = agentConfig.getTempPath().getAbsolutePath(); + String path = FileUtil.getAbsolutePath(successFile); + // 解析压缩包 + File file = JpomManifest.zipFileFind(path, Type.Agent, absolutePath); + path = FileUtil.getAbsolutePath(file); + // 基础检查 + JsonMessage error = JpomManifest.checkJpomJar(path, Type.Agent); + if (!error.success()) { + return new JsonMessage<>(error.getCode(), error.getMsg()); + } + Tuple data = error.getData(); + String version = data.get(0); + JpomManifest.releaseJar(path, version); + // + JpomApplication.restart(); + return JsonMessage.success(Const.UPGRADE_MSG.get()); + } + + @PostMapping(value = "change_log", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage changeLog(String beta) { + // + boolean betaBool = Convert.toBool(beta, false); + boolean betaRelease = RemoteVersion.betaRelease(); + URL resource = ResourceUtil.getResource((betaRelease || betaBool) ? "CHANGELOG-BETA.md" : "CHANGELOG.md"); + String log = StrUtil.EMPTY; + if (resource != null) { + InputStream stream = URLUtil.getStream(resource); + log = IoUtil.readUtf8(stream); + } + return JsonMessage.success("", log); + } + + /** + * 检查是否存在新版本 + * + * @return json + * @see RemoteVersion + */ + @PostMapping(value = "check_version.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage checkVersion() { + cn.keepbx.jpom.RemoteVersion remoteVersion = RemoteVersion.loadRemoteInfo(); + return JsonMessage.success("", remoteVersion); + } + + /** + * 远程下载升级 + * + * @return json + * @see RemoteVersion + */ + @PostMapping(value = "remote_upgrade.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage upgrade() throws IOException { + RemoteVersion.upgrade(configBean.getTempPath().getAbsolutePath()); + return JsonMessage.success(Const.UPGRADE_MSG.get()); + } +} 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 new file mode 100644 index 0000000000..09cbf24798 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/WhitelistDirectoryController.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.service.WhitelistDirectoryService; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2019/4/16 + */ +@RestController +@RequestMapping(value = "/system") +public class WhitelistDirectoryController extends BaseJpomController { + + private final WhitelistDirectoryService whitelistDirectoryService; + + public WhitelistDirectoryController(WhitelistDirectoryService whitelistDirectoryService) { + this.whitelistDirectoryService = whitelistDirectoryService; + } + + @RequestMapping(value = "whitelistDirectory_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage whiteListDirectoryData() { + AgentWhitelist agentWhitelist = whitelistDirectoryService.getWhitelist(); + return JsonMessage.success("", agentWhitelist); + } + + + @PostMapping(value = "whitelistDirectory_submit", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage whitelistDirectorySubmit(String project, + + + String allowEditSuffix) { + List list = AgentWhitelist.parseToList(project, true, I18nMessageUtil.get("i18n.project_path_auth_required.9e58")); + // + List allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, I18nMessageUtil.get("i18n.suffix_cannot_be_empty.ec72")); + return save(list, allowEditSuffixList); + } + + + private JsonMessage save(List projects, + + List allowEditSuffixList) { + List projectArray; + { + projectArray = AgentWhitelist.covertToArray(projects, I18nMessageUtil.get("i18n.project_path_auth_not_under_jpom.0e18")); + String error = findStartsWith(projectArray); + Assert.isNull(error, I18nMessageUtil.get("i18n.auth_directory_cannot_contain_hierarchy.d6ca") + error); + } + + // + if (CollUtil.isNotEmpty(allowEditSuffixList)) { + for (String s : allowEditSuffixList) { + List split = StrUtil.split(s, StrUtil.AT); + if (split.size() > 1) { + String last = CollUtil.getLast(split); + try { + CharsetUtil.charset(last); + } catch (Exception e) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.illegal_character_encoding_format.af7a") + s); + } + } + } + } + + AgentWhitelist agentWhitelist = whitelistDirectoryService.getWhitelist(); + + agentWhitelist.setProject(projectArray); + agentWhitelist.setAllowEditSuffix(allowEditSuffixList); + whitelistDirectoryService.saveWhitelistDirectory(agentWhitelist); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } + + /** + * 检查授权包含关系 + * + * @param jsonArray 要检查的对象 + * @return null 正常 + */ + private String findStartsWith(List jsonArray) { + return AgentWhitelist.findStartsWith(jsonArray); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/data/BaseWorkspaceModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/data/BaseWorkspaceModel.java new file mode 100644 index 0000000000..5dc419c6e8 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/BaseWorkspaceModel.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.model.BaseModel; + +/** + * 插件端 工作空间相关的数据 + * + * @author bwcx_jzy + * @since 2021/12/12 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseWorkspaceModel extends BaseModel { + + /** + * 数据关联的工作空间 + */ + private String workspaceId; + /** + * 数据跟随的节点 ID + */ + private String nodeId; + /** + * 最后修改人 + */ + private String modifyUser; + + private String createTime; + + private String modifyTime; + + /** + * 创建人 + */ + private String createUser; + + public boolean global() { + return StrUtil.equals(this.workspaceId, Const.WORKSPACE_GLOBAL); + } +} 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 new file mode 100644 index 0000000000..afef6e5feb --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/DslYmlDto.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Map; +import java.util.Optional; + +/** + * dsl yml 配置 + * + * @author bwcx_jzy + * @since 2022/1/15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DslYmlDto extends BaseJsonModel { + + /** + * 描述 + */ + private String description; + + /** + * 运行 + */ + private Run run; + + /** + * 文件相关配置 + */ + private FileConfig file; + /** + * 配置 + */ + private Config config; + + /** + * 判断是否包含指定流程 + * + * @param opt 流程名 + * @return true + */ + public boolean hasRunProcess(String opt) { + DslYmlDto.Run run = this.getRun(); + if (run == null) { + return false; + } + DslYmlDto.BaseProcess baseProcess = (DslYmlDto.BaseProcess) ReflectUtil.getFieldValue(run, opt); + return baseProcess != null; + } + + /** + * 构建对象 + * + * @param yml yml 内容 + * @return DslYmlDto + */ + public static DslYmlDto build(String yml) { + InputStream inputStream = new ByteArrayInputStream(yml.getBytes()); + return YamlUtil.load(inputStream, DslYmlDto.class); + } + + /** + * 运行管理 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class Run extends BaseJsonModel { + private Start start; + private Status status; + private Stop stop; + private Restart restart; + private Reload reload; + /** + * 文件变动是否执行重新加载 + */ + private Boolean fileChangeReload; + /** + * 在指定目录执行 + */ + private String execPath; + } + + /** + * 重新加载 + * + * @see org.dromara.jpom.socket.ConsoleCommandOp + */ + public static class Reload extends BaseProcess { + + } + + /** + * 启动流程 + * + * @see org.dromara.jpom.socket.ConsoleCommandOp + */ + public static class Start extends BaseProcess { + + } + + /** + * 获取状态流程 + * + * @see org.dromara.jpom.socket.ConsoleCommandOp + */ + public static class Status extends BaseProcess { + + } + + /** + * 停止流程 + * + * @see org.dromara.jpom.socket.ConsoleCommandOp + */ + public static class Stop extends BaseProcess { + + } + + /** + * 重启流程 + * + * @see org.dromara.jpom.socket.ConsoleCommandOp + */ + public static class Restart extends BaseProcess { + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class BaseProcess extends BaseJsonModel { + /** + * 脚本 ID + */ + private String scriptId; + /** + * 执行参数 + */ + private String scriptArgs; + /** + * 执行脚本的环境变量 + */ + private Map scriptEnv; + } + + @Data + public static class FileConfig { + /** + * 保留文件备份数量 + */ + private Integer backupCount; + + /** + * 指定备份文件后缀,如果未指定则备份所有类型文件 + */ + private String[] backupSuffix; + + /** + * 项目文件备份路径 + */ + private String backupPath; + } + + @Data + public static class Config { + /** + * 是否自动将控制台日志文件备份 + */ + private Boolean autoBackToFile; + } + + + /** + * 获取 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)) + .orElse(null); + } + + /** + * 获取 dsl 流程信息 + * + * @param opt 操作 + * @return 结果 + */ + public DslYmlDto.BaseProcess tryDslProcess(String opt) { + return tryDslProcess(this, opt); + } + + /** + * 获取 dsl 流程信息 + * + * @param opt 操作 + * @return 结果 + */ + public DslYmlDto.BaseProcess getDslProcess(String opt) { + DslYmlDto.BaseProcess baseProcess = this.tryDslProcess(opt); + Assert.notNull(baseProcess, StrUtil.format(I18nMessageUtil.get("i18n.dsl_not_configured.8a57"), opt)); + return baseProcess; + } +} 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 new file mode 100644 index 0000000000..45c5bd1d98 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeProjectInfoModel.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.io.FileUtil; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.RunMode; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.util.Assert; + +import java.util.List; + +/** + * 项目配置信息实体 + * + * @author bwcx_jzy + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeProjectInfoModel extends BaseWorkspaceModel { + /** + * 分组 + */ + private String group; + /** + * 项目路径 + */ + private String lib; + /** + * 授权目录 + */ + private String whitelistDirectory; + /** + * 日志目录 + */ + private String logPath; + /** + * java 模式运行的 class + */ + private String mainClass; + /** + * jvm 参数 + */ + private String jvm; + /** + * java main 方法参数 + */ + private String args; + /** + * WebHooks + */ + private String token; + /** + * 项目运行模式 + */ + private RunMode runMode; + /** + * 软链的父级项目id + */ + private String linkId; + /** + * 节点分发项目,不允许在项目管理中编辑 + */ + private Boolean outGivingProject; + /** + * -Djava.ext.dirs=lib -cp conf:run.jar + * 填写【lib:conf】 + */ + private String javaExtDirsCp; + /** + * 项目自动启动 + */ + private Boolean autoStart; + /** + * dsl yml 内容 + * + * @see DslYmlDto + */ + private String dslContent; + /** + * dsl 环境变量 + */ + private String dslEnv; + /** + * 最后一次执行 reload 结果 + */ + private CommandOpResult lastReloadResult; + /** + * 禁用扫描目录 + */ + private Boolean disableScanDir; + // ---------------- 中转字段 start + /** + * 是否可以重新加载 + */ + private Boolean canReload; + /** + * DSL 流程信息统计 + */ + private List dslProcessInfo; + /** + * 实际运行的命令 + */ + private String runCommand; + // ---------------- 中转字段 end + + public boolean isDisableScanDir() { + return disableScanDir != null && disableScanDir; + } + + + public String javaExtDirsCp() { + return StrUtil.emptyToDefault(javaExtDirsCp, StrUtil.EMPTY); + } + + public boolean outGivingProject() { + return outGivingProject != null && outGivingProject; + } + + public String mainClass() { + return StrUtil.emptyToDefault(mainClass, StrUtil.EMPTY); + } + + public String whitelistDirectory() { + if (StrUtil.isEmpty(whitelistDirectory)) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.restore_authorization_data_exception.015a")); + } + return whitelistDirectory; + } + + public String allLib() { + String directory = this.whitelistDirectory(); + return FileUtil.file(directory, this.getLib()).getAbsolutePath(); + } + + public String logPath() { + return StrUtil.emptyToDefault(this.logPath, StrUtil.EMPTY); + } + + /** + * 默认 + * + * @return url token + */ + public String token() { + // 兼容旧数据 + if ("no".equalsIgnoreCase(this.token)) { + return ""; + } + return StrUtil.emptyToDefault(token, StrUtil.EMPTY); + } + + /** + * 获取当前 dsl 配置 + * + * @return DslYmlDto + */ + public DslYmlDto dslConfig() { + String dslContent = this.getDslContent(); + if (StrUtil.isEmpty(dslContent)) { + return null; + } + return DslYmlDto.build(dslContent); + } + + /** + * 必须存在 dsl 配置 + * + * @return DslYmlDto + */ + public DslYmlDto mustDslConfig() { + DslYmlDto dslYmlDto = this.dslConfig(); + Assert.notNull(dslYmlDto, I18nMessageUtil.get("i18n.dsl_info_not_configured.3487")); + return dslYmlDto; + } + +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeScriptExecLogModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeScriptExecLogModel.java new file mode 100644 index 0000000000..e117e3093f --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeScriptExecLogModel.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 脚本执行记录 + * + * @author bwcx_jzy + * @since 2021/12/26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeScriptExecLogModel extends BaseWorkspaceModel { + + /** + * 运行时间 + */ + private Long createTimeMillis; + + /** + * 脚本ID + */ + private String scriptId; + + /** + * 脚本名称 + */ + private String scriptName; + + /** + * 触发类型 {0,手动,1 自动触发,2 触发器} + */ + private Integer triggerExecType; +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeScriptModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeScriptModel.java new file mode 100644 index 0000000000..3146a8db08 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeScriptModel.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.script.CommandParam; + +import java.io.File; + +/** + * 脚本模板 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeScriptModel extends BaseWorkspaceModel { + /** + * 最后执行人员 + */ + private String lastRunUser; + /** + * 脚本内容 + */ + private String context; + /** + * 自动执行的 cron + */ + private String autoExecCron; + /** + * 默认参数 + */ + private String defArgs; + /** + * 描述 + */ + private String description; + /** + * 脚本类型:server-sync + */ + private String scriptType; + + + public String getLastRunUser() { + return StrUtil.emptyToDefault(lastRunUser, StrUtil.DASHED); + } + + public void setDefArgs(String defArgs) { + this.defArgs = CommandParam.convertToParam(defArgs); + } + + public File scriptPath() { + return scriptPath(getId()); + } + + public static File scriptPath(String id) { + if (StrUtil.isEmpty(id)) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.id_is_empty.3bbf")); + } + File path = JpomApplication.getInstance().getScriptPath(); + return FileUtil.file(path, id); + } + + public File logFile(String executeId) { + if (StrUtil.isEmpty(getId())) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.id_is_empty.3bbf")); + } + File path = JpomApplication.getInstance().getScriptPath(); + return FileUtil.file(path, getId(), "log", executeId + ".log"); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/data/ScriptLibraryModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/data/ScriptLibraryModel.java new file mode 100644 index 0000000000..545eab4fe5 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/ScriptLibraryModel.java @@ -0,0 +1,21 @@ +package org.dromara.jpom.model.data; + +import lombok.Data; + +/** + * @author bwcx_jzy1 + * @since 2024/6/1 + */ +@Data +public class ScriptLibraryModel { + + private String id; + + private String tag; + + private String script; + + private String description; + + private String version; +} 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 new file mode 100644 index 0000000000..71ee289f0f --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/system/NetstatModel.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.system; + +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.model.BaseJsonModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 网络端口信息实体 + * + * @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; +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/system/WorkspaceEnvVarModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/system/WorkspaceEnvVarModel.java new file mode 100644 index 0000000000..d3f4218ff7 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/model/system/WorkspaceEnvVarModel.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.system; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.model.BaseModel; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author lidaofu + * @since 2022/3/8 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WorkspaceEnvVarModel extends BaseModel { + + private Map varData; + + /** + * 更新变量 + * + * @param name 变量名称 + * @param workspaceEnvVarModel 变量信息 + */ + public void put(String name, WorkspaceEnvVarItemModel workspaceEnvVarModel) { + if (varData == null) { + varData = new HashMap<>(2); + } + varData.put(name, workspaceEnvVarModel); + } + + /** + * 删除 变量 + * + * @param name 名称 + */ + public void remove(String name) { + if (varData == null) { + return; + } + varData.remove(name); + } + + /** + * @author lidaofu + * @since 2022/3/8 + */ + @Data + public static class WorkspaceEnvVarItemModel { + + private String name; + + private String value; + + private String description; + + /** + * 隐私变量{1,隐私变量,0 非隐私变量(明文回显)} + */ + private Integer privacy; + } +} 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 new file mode 100644 index 0000000000..33132b7326 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/script/DslScriptBuilder.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.script; + +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.LineHandler; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.StrUtil; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.CommandUtil; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * dsl 执行脚本 + * + * @author bwcx_jzy + * @since 2022/1/15 + */ +@Setter +@Slf4j +public class DslScriptBuilder extends BaseRunScript implements Runnable { + + + private final String args; + private String action; + private File scriptFile; + private boolean autoDelete; + private EnvironmentMapBuilder environmentMapBuilder; + + public DslScriptBuilder(String action, + EnvironmentMapBuilder environmentMapBuilder, + String args, + String log, + Charset charset) { + super(FileUtil.file(log), charset); + this.action = action; + this.environmentMapBuilder = environmentMapBuilder; + this.args = args; + } + + /** + * 初始化 + */ + private ProcessBuilder init() { + // + String script = FileUtil.getAbsolutePath(scriptFile); + ProcessBuilder processBuilder = new ProcessBuilder(); + List command = StrUtil.splitTrim(args, StrUtil.SPACE); + command.add(0, script); + CommandUtil.paddingPrefix(command); + log.debug(CollUtil.join(command, StrUtil.SPACE)); + processBuilder + .environment() + .putAll(environmentMapBuilder.environment()); + // + String jpomExecPath = environmentMapBuilder.get("JPOM_EXEC_PATH"); + String projectPath = environmentMapBuilder.get("PROJECT_PATH"); + if (StrUtil.isNotEmpty(jpomExecPath) && StrUtil.isNotEmpty(projectPath)) { + boolean absolutePath = FileUtil.isAbsolutePath(jpomExecPath); + if (absolutePath) { + processBuilder.directory(FileUtil.file(projectPath)); + } else { + processBuilder.directory(FileUtil.file(projectPath, jpomExecPath)); + } + } else { + processBuilder.directory(FileUtil.getParent(scriptFile, 1)); + } + processBuilder.redirectErrorStream(true); + processBuilder.command(command); + return processBuilder; + } + + @Override + public void run() { + try { + ProcessBuilder processBuilder = this.init(); + environmentMapBuilder.eachStr(this::info); + // + this.system(I18nMessageUtil.get("i18n.start_executing.f0b9"), this.action); + process = processBuilder.start(); + inputStream = process.getInputStream(); + IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), (LineHandler) line -> { + String formatLine = formatLine(line); + this.info(formatLine); + }); + // + int waitFor = process.waitFor(); + // + this.system(I18nMessageUtil.get("i18n.execution_ended_with_detail.8f93"), this.action, waitFor); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e); + this.systemError(I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } finally { + this.close(); + } + } + + + private String formatLine(String line) { + return StrUtil.format("{} [{}] - {}", DateTime.now().toString(DatePattern.NORM_DATETIME_MS_FORMAT), this.action, line); + } + + /** + * 执行 + *

+ * 0 退出码 + * 1 日志 + */ + public Tuple syncExecute() { + ProcessBuilder processBuilder = this.init(); + List result = new ArrayList<>(); + int waitFor = -100; + try { + // + process = processBuilder.start(); + inputStream = process.getInputStream(); + + IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), (LineHandler) line -> result.add(this.formatLine(line))); + // + waitFor = process.waitFor(); + // 插入第一行 + result.add(0, this.formatLine(StrUtil.format(I18nMessageUtil.get("i18n.exit_code.3b54"), waitFor))); + // + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e); + result.add(this.formatLine(StrUtil.format(I18nMessageUtil.get("i18n.general_execution_exception.62e9"), e.getMessage()))); + } finally { + this.close(); + } + return new Tuple(waitFor, result); + } + + @Override + protected void end(String msg) { + + } + + @Override + protected void msgCallback(String msg) { + + } + + @Override + public void close() { + super.close(); + // + if (autoDelete) { + try { + FileUtil.del(this.scriptFile); + } catch (Exception ignored) { + } + } + } + + +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/script/NodeScriptProcessBuilder.java b/modules/agent/src/main/java/org/dromara/jpom/script/NodeScriptProcessBuilder.java new file mode 100644 index 0000000000..319d837638 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/script/NodeScriptProcessBuilder.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.script; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.model.EnvironmentMapBuilder; +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.socket.ConsoleCommandOp; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.SocketSessionUtil; + +import javax.websocket.Session; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 脚本执行 + * + * @author bwcx_jzy + * @since 2019/4/25 + */ +@Slf4j +public class NodeScriptProcessBuilder extends BaseRunScript implements Runnable { + /** + * 执行中的缓存 + */ + private static final ConcurrentHashMap FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP = new SafeConcurrentHashMap<>(); + + private final ProcessBuilder processBuilder; + private final Set sessions = new HashSet<>(); + private final String executeId; + private final File scriptFile; + private final EnvironmentMapBuilder environmentMapBuilder; + private NodeScriptServer nodeScriptServer; + + private NodeScriptProcessBuilder(NodeScriptModel nodeScriptModel, String executeId, String args, Map paramMap) { + super(nodeScriptModel.logFile(executeId), CharsetUtil.CHARSET_UTF_8); + this.executeId = executeId; + if (nodeScriptServer == null) { + nodeScriptServer = SpringUtil.getBean(NodeScriptServer.class); + } + // + scriptFile = nodeScriptServer.toExecuteFile(nodeScriptModel); + // + String script = FileUtil.getAbsolutePath(scriptFile); + processBuilder = new ProcessBuilder(); + List command = CommandParam.toCommandList(args); + command.add(0, script); + CommandUtil.paddingPrefix(command); + log.debug(CollUtil.join(command, StrUtil.SPACE)); + String workspaceId = nodeScriptModel.getWorkspaceId(); + // 添加环境变量 + Map environment = processBuilder.environment(); + AgentWorkspaceEnvVarService workspaceService = SpringUtil.getBean(AgentWorkspaceEnvVarService.class); + environmentMapBuilder = workspaceService.getEnv(workspaceId); + environmentMapBuilder.putStr(paramMap); + environment.putAll(environmentMapBuilder.environment()); + processBuilder.redirectErrorStream(true); + processBuilder.command(command); + processBuilder.directory(scriptFile.getParentFile()); + } + + /** + * 创建执行 并监听 + * + * @param nodeScriptModel 脚本模版 + * @param executeId 执行ID + * @param args 参数 + * @param paramMap 执行环境变量参数 + */ + public static NodeScriptProcessBuilder create(NodeScriptModel nodeScriptModel, String executeId, String args, Map paramMap) { + return FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.computeIfAbsent(executeId, file1 -> { + NodeScriptProcessBuilder nodeScriptProcessBuilder1 = new NodeScriptProcessBuilder(nodeScriptModel, executeId, args, paramMap); + I18nThreadUtil.execute(nodeScriptProcessBuilder1); + return nodeScriptProcessBuilder1; + }); + } + + /** + * 创建执行 并监听 + * + * @param nodeScriptModel 脚本模版 + * @param executeId 执行ID + * @param args 参数 + * @param session 会话 + */ + public static void addWatcher(NodeScriptModel nodeScriptModel, String executeId, String args, Session session) { + NodeScriptProcessBuilder nodeScriptProcessBuilder = create(nodeScriptModel, executeId, args, null); + // + if (nodeScriptProcessBuilder.sessions.add(session)) { + if (FileUtil.exist(nodeScriptProcessBuilder.logFile)) { + // 读取之前的信息并发送 + FileUtil.readLines(nodeScriptProcessBuilder.logFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line -> { + try { + SocketSessionUtil.send(session, line); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + } + }); + } + } + } + + /** + * 判断是否还在执行中 + * + * @param executeId 执行id + * @return true 还在执行 + */ + public static boolean isRun(String executeId) { + return FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.containsKey(executeId); + } + + /** + * 关闭会话 + * + * @param session 会话 + */ + public static void stopWatcher(Session session) { + Collection nodeScriptProcessBuilders = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.values(); + for (NodeScriptProcessBuilder nodeScriptProcessBuilder : nodeScriptProcessBuilders) { + Set sessions = nodeScriptProcessBuilder.sessions; + sessions.removeIf(session1 -> session1.getId().equals(session.getId())); + } + } + + /** + * 停止脚本命令 + * + * @param executeId 执行ID + */ + public static void stopRun(String executeId) { + NodeScriptProcessBuilder nodeScriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.get(executeId); + if (nodeScriptProcessBuilder != null) { + nodeScriptProcessBuilder.end(I18nMessageUtil.get("i18n.stop_running.1d4e")); + } + } + + @Override + public void run() { + //初始化ProcessBuilder对象 + try { + environmentMapBuilder.eachStr(this::info); + process = processBuilder.start(); + inputStream = process.getInputStream(); + IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), (LineHandler) NodeScriptProcessBuilder.this::info); + int waitFor = process.waitFor(); + this.system(I18nMessageUtil.get("i18n.execution_ended.b793"), waitFor); + JsonMessage jsonMessage = new JsonMessage<>(200, I18nMessageUtil.get("i18n.execution_completed.24a1") + waitFor); + JSONObject jsonObject = jsonMessage.toJson(); + jsonObject.put(Const.SOCKET_MSG_TAG, Const.SOCKET_MSG_TAG); + jsonObject.put("op", ConsoleCommandOp.stop.name()); + this.end(jsonObject.toString()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e); + this.systemError(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e.getMessage()); + this.end(I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } finally { + this.close(); + } + } + + /** + * 结束执行 + * + * @param msg 响应的消息 + */ + @Override + protected void end(String msg) { + Iterator iterator = sessions.iterator(); + while (iterator.hasNext()) { + Session session = iterator.next(); + try { + SocketSessionUtil.send(session, msg); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + } + iterator.remove(); + } + NodeScriptProcessBuilder nodeScriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.remove(this.executeId); + IoUtil.close(nodeScriptProcessBuilder); + } + + @Override + public void close() { + super.close(); + try { + FileUtil.del(this.scriptFile); + } catch (Exception ignored) { + } + } + + @Override + protected void msgCallback(String info) { + // + Iterator iterator = sessions.iterator(); + while (iterator.hasNext()) { + Session session = iterator.next(); + try { + SocketSessionUtil.send(session, info); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + iterator.remove(); + } + } + } +} 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 new file mode 100644 index 0000000000..627b0ec906 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/BaseOperService.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.common.i18n.I18nMessageUtil; +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 + * + * @author bwcx_jzy + * @since 2019/3/14 + */ +public abstract class BaseOperService { + + private final String fileName; + private final Class typeArgument; + private final Lock lock = LockUtil.createStampLock().asWriteLock(); + + public BaseOperService(String fileName) { + this.fileName = fileName; + this.typeArgument = (Class) ClassUtil.getTypeArgument(this.getClass()); + } + + /** + * 获取所有数据 + * + * @return list + */ + public List list() { + return list(typeArgument); + } + + public int size() { + List list = this.list(); + return CollUtil.size(list); + } + + public List list(Class cls) { + JSONObject jsonObject = getJSONObject(); + if (jsonObject == null) { + return new ArrayList<>(); + } + JSONArray jsonArray = JsonFileUtil.formatToArray(jsonObject); + return jsonArray.toJavaList(cls); + } + + public JSONObject getJSONObject() { + Objects.requireNonNull(fileName, I18nMessageUtil.get("i18n.file_name_not_configured.39fa")); + return getJSONObject(fileName); + } + + /** + * 工具id 获取 实体 + * + * @param id 数据id + * @return T + */ + public T getItem(String id) { + Objects.requireNonNull(fileName, I18nMessageUtil.get("i18n.file_name_not_configured.39fa")); + return getJsonObjectById(fileName, id, typeArgument); + } + + + /** + * 添加实体 + * + * @param t 实体 + */ + public void addItem(T t) { + Objects.requireNonNull(fileName, I18nMessageUtil.get("i18n.file_name_not_configured.39fa")); + try { + lock.lock(); + saveJson(fileName, t); + } finally { + lock.unlock(); + } + } + + /** + * 删除实体 + * + * @param id 数据id + */ + public void deleteItem(String id) { + Objects.requireNonNull(fileName, I18nMessageUtil.get("i18n.file_name_not_configured.39fa")); + try { + lock.lock(); + deleteJson(fileName, id); + } finally { + lock.unlock(); + } + } + + /** + * 修改实体 + * + * @param t 实体 + */ + public void updateItem(T t) { + Objects.requireNonNull(fileName, I18nMessageUtil.get("i18n.file_name_not_configured.39fa")); + 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, I18nMessageUtil.get("i18n.file_name_not_configured.39fa")); + try { + lock.lock(); + T item = getItem(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.data_does_not_exist.b201")); + 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(StrUtil.format(I18nMessageUtil.get("i18n.data_id_already_exists.28b6"), 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(I18nMessageUtil.get("i18n.data_does_not_exist_with_details.d9b5") + 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 new file mode 100644 index 0000000000..37b191954a --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/BaseWorkspaceOptService.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.BaseAgentController; +import org.dromara.jpom.model.data.BaseWorkspaceModel; + +/** + * @author bwcx_jzy + * @since 2022/1/17 + */ +public abstract class BaseWorkspaceOptService extends BaseOperService { + + public BaseWorkspaceOptService(String fileName) { + super(fileName); + } + + @Override + public void addItem(T t) { + t.setCreateTime(DateUtil.now()); + String userName = BaseAgentController.getNowUserName(); + if (!StrUtil.DASHED.equals(userName)) { + t.setCreateUser(userName); + t.setModifyUser(userName); + } + super.addItem(t); + } + + /** + * 修改信息 + * + * @param data 信息 + */ + @Override + public void updateItem(T data) { + data.setModifyTime(DateUtil.now()); + String userName = BaseAgentController.getNowUserName(); + if (!StrUtil.DASHED.equals(userName)) { + data.setModifyUser(userName); + } + super.updateItem(data); + } + + @Override + public void updateById(T updateData, String id) { + updateData.setModifyTime(DateUtil.now()); + String userName = BaseAgentController.getNowUserName(); + if (!StrUtil.DASHED.equals(userName)) { + updateData.setModifyUser(userName); + } + super.updateById(updateData, id); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/ProjectFileBackupService.java b/modules/agent/src/main/java/org/dromara/jpom/service/ProjectFileBackupService.java new file mode 100644 index 0000000000..4ab15aeed8 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/ProjectFileBackupService.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service; + +import cn.hutool.core.collection.CollStreamUtil; +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.util.ArrayUtil; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +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.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; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 项目文件备份工具 + * + * @author bwcx_jzy + * @since 2022/5/10 + */ +@Slf4j +@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; + } + + /** + * 整个项目的备份目录 + * + * @param projectInfoModel 项目 + * @return file + */ + public File pathProject(NodeProjectInfoModel projectInfoModel) { + NodeProjectInfoModel infoModel = projectInfoService.resolveModel(projectInfoModel); + DslYmlDto dslYmlDto = infoModel.dslConfig(); + String backupPath = resolveBackupPath(dslYmlDto); + return pathProject(backupPath, infoModel.getId()); + } + + /** + * 整个项目的备份目录 + * + * @param pathId 项目ID + * @param backupPath 备份路径 + * @return file + */ + private File pathProject(String backupPath, String pathId) { + if (StrUtil.isEmpty(backupPath)) { + String dataPath = JpomApplication.getInstance().getDataPath(); + return FileUtil.file(dataPath, "project_file_backup", pathId); + } + return FileUtil.file(backupPath, pathId); + } + + /** + * 获取项目的单次备份目录,备份ID + * + * @param projectInfoModel 项目 + * @param backupId 备份ID + * @return file + */ + public File pathProjectBackup(NodeProjectInfoModel projectInfoModel, String backupId) { + File fileBackup = pathProject(projectInfoModel); + return FileUtil.file(fileBackup, backupId); + } + + /** + * 备份项目文件 + * + * @param projectInfoModel 项目 + */ + public String backup(NodeProjectInfoModel projectInfoModel) { + NodeProjectInfoModel infoModel = projectInfoService.resolveModel(projectInfoModel); + int backupCount = resolveBackupCount(infoModel.dslConfig()); + if (backupCount <= 0) { + // 未开启备份 + return null; + } + File file = projectInfoService.resolveLibFile(infoModel); + // + if (!FileUtil.exist(file)) { + return null; + } + String backupId = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_FORMAT); + log.debug(I18nMessageUtil.get("i18n.prepare_backup.7970"), projectInfoModel.getId(), backupId); + File projectFileBackup = this.pathProjectBackup(infoModel, backupId); + Assert.state(!FileUtil.exist(projectFileBackup), I18nMessageUtil.get("i18n.backup_directory_conflict.c13e") + projectFileBackup.getName()); + FileUtil.copyContent(file, projectFileBackup, true); + // + return backupId; + } + + /** + * 检查备份保留个数 + * + * @param backupPath 目录 + */ + private void clearOldBackup(File backupPath, DslYmlDto dslYmlDto) { + int backupCount = resolveBackupCount(dslYmlDto); + // + if (!FileUtil.isDirectory(backupPath)) { + return; + } + File[] files = backupPath.listFiles(); + if (files == null) { + return; + } + List collect = Arrays.stream(files) + .filter(FileUtil::isDirectory) + .sorted(Comparator.comparing(FileUtil::lastModifiedTime)) + .collect(Collectors.toList()); + // 截取 + int max = Math.max(collect.size() - backupCount, 0); + if (max > 0) { + collect = CollUtil.sub(collect, 0, max); + // 删除 + collect.forEach(CommandUtil::systemFastDel); + } + } + + /** + * 解析项目的备份路径 + * + * @param dslYmlDto dsl 配置 + * @return path + */ + public String resolveBackupPath(DslYmlDto dslYmlDto) { + return Optional.ofNullable(dslYmlDto) + .map(DslYmlDto::getFile) + .map(DslYmlDto.FileConfig::getBackupPath) + .filter(s -> !StrUtil.isEmpty(s)) + .orElse(null); + } + + public int resolveBackupCount(DslYmlDto dslYmlDto) { + return Optional.ofNullable(dslYmlDto) + .map(DslYmlDto::getFile) + .map(DslYmlDto.FileConfig::getBackupCount) + .orElseGet(projectConfig::getFileBackupCount); + } + + /** + * 检查文件变动 + * + * @param projectInfoModel 项目 + * @param backupId 要对比的备份ID + */ + public void checkDiff(NodeProjectInfoModel projectInfoModel, String backupId) { + if (StrUtil.isEmpty(backupId)) { + // 备份ID 不存在 + return; + } + log.debug(I18nMessageUtil.get("i18n.start_checking_backup_project_files.baa7"), projectInfoModel.getId(), backupId); + NodeProjectInfoModel infoModel = projectInfoService.resolveModel(projectInfoModel); + File projectPath = projectInfoService.resolveLibFile(infoModel); + DslYmlDto dslYmlDto = infoModel.dslConfig(); + // 考虑到大文件对比,比较耗时。需要异步对比文件 + I18nThreadUtil.execute(() -> { + try { + //String useBackupPath = resolveBackupPath(dslYmlDto); + File backupItemPath = this.pathProjectBackup(infoModel, backupId); + File backupPath = this.pathProject(infoModel); + // 获取文件列表 + Map backupFiles = this.listFiles(backupItemPath); + Map nowFiles = this.listFiles(projectPath); + nowFiles.forEach((fileSha1, file) -> { + // 当前目录存在的,但是备份目录也存在的相同文件则删除 + File backupFile = backupFiles.get(fileSha1); + if (backupFile != null) { + CommandUtil.systemFastDel(backupFile); + backupFiles.remove(fileSha1); + } + }); + // 判断保存指定后缀 + String[] backupSuffix = Optional.ofNullable(dslYmlDto) + .map(DslYmlDto::getFile) + .map(DslYmlDto.FileConfig::getBackupSuffix) + .orElseGet(projectConfig::getFileBackupSuffix); + if (ArrayUtil.isNotEmpty(backupSuffix)) { + backupFiles.values() + .stream() + .filter(file -> { + String name = FileUtil.getName(file); + for (String reg : backupSuffix) { + if (ReUtil.isMatch(reg, name)) { + // 满足正则条件 + return false; + } + } + return !StrUtil.endWithAny(name, backupSuffix); + }) + .forEach(CommandUtil::systemFastDel); + } + // 删除空文件夹 + loopClean(backupItemPath); + // 检查备份保留个数 + clearOldBackup(backupPath, dslYmlDto); + // 合并之前备份目录 + margeBackupPath(infoModel); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.compare_backup_failure.303e"), e); + } + }); + } + + /** + * 合并备份路径 + * + * @param projectInfoModel 项目 + */ + 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); + FileUtil.del(backupPathBefore); + } + } + + private void loopClean(File backupPath) { + if (FileUtil.isFile(backupPath)) { + return; + } + // + Optional.ofNullable(backupPath.listFiles()).ifPresent(files1 -> { + for (File file : files1) { + this.loopClean(file); + } + }); + // 检查目录是否为空 + if (FileUtil.isDirEmpty(backupPath)) { + FileUtil.del(backupPath); + } + } + + /** + * 获取文件列表信息 + * + * @param path 路径 + * @return 文件列表信息 + */ + private Map listFiles(File path) { + // 将所有的文件信息组装并签名 + List files = FileUtil.loopFiles(path); + List collect = files.stream().map(file -> { + // + JSONObject item = new JSONObject(); + item.put("file", file); + item.put("sha1", SecureUtil.sha1(file) + StrUtil.DASHED + StringUtil.delStartPath(file, path, true)); + return item; + }).collect(Collectors.toList()); + return CollStreamUtil.toMap(collect, + jsonObject12 -> jsonObject12.getString("sha1"), + jsonObject1 -> (File) jsonObject1.get("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 new file mode 100644 index 0000000000..246e02b219 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/WhitelistDirectoryService.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.AgentConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.util.JsonFileUtil; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 授权服务 + * + * @author bwcx_jzy + * @since 2019/2/28 + */ +@Service +@Slf4j +public class WhitelistDirectoryService extends BaseOperService { + + public WhitelistDirectoryService() { + super(AgentConst.WHITELIST_DIRECTORY); + } + + /** + * 获取授权信息配置、如何没有配置或者配置错误将返回新对象 + * + * @return AgentWhitelist + */ + public AgentWhitelist getWhitelist() { + try { + JSONObject jsonObject = getJSONObject(); + if (jsonObject == null) { + return new AgentWhitelist(); + } + return jsonObject.toJavaObject(AgentWhitelist.class); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return new AgentWhitelist(); + } + + /** + * 单项添加授权 + * + * @param item 授权 + */ + public void addProjectWhiteList(String item) { + ArrayList list = CollUtil.newArrayList(item); + List checkOk = AgentWhitelist.covertToArray(list, I18nMessageUtil.get("i18n.project_path_auth_not_under_jpom.0e18")); + + AgentWhitelist agentWhitelist = getWhitelist(); + List project = agentWhitelist.getProject(); + project = ObjectUtil.defaultIfNull(project, new ArrayList<>()); + project = CollUtil.addAll(project, checkOk) + .stream() + .distinct() + .collect(Collectors.toList()); + agentWhitelist.setProject(project); + saveWhitelistDirectory(agentWhitelist); + } + + public boolean checkProjectDirectory(String path) { + AgentWhitelist agentWhitelist = getWhitelist(); + List list = agentWhitelist.getProject(); + return AgentWhitelist.checkPath(list, path); + } + + + /** + * 保存授权 + * + * @param jsonObject 实体 + */ + public void saveWhitelistDirectory(AgentWhitelist jsonObject) { + String path = getDataFilePath(AgentConst.WHITELIST_DIRECTORY); + JsonFileUtil.saveJson(path, jsonObject.toJson()); + } +} 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 new file mode 100644 index 0000000000..6c16358224 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/manage/ProjectInfoService.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.manage; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.AgentConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.RunMode; +import org.dromara.jpom.model.data.NodeProjectInfoModel; +import org.dromara.jpom.service.BaseWorkspaceOptService; +import org.dromara.jpom.service.system.AgentWorkspaceEnvVarService; +import org.dromara.jpom.system.ExtConfigBean; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.io.File; +import java.util.Map; + +/** + * 项目管理 + * + * @author bwcx_jzy + */ +@Service +public class ProjectInfoService extends BaseWorkspaceOptService { + + private final AgentWorkspaceEnvVarService agentWorkspaceEnvVarService; + + public ProjectInfoService(AgentWorkspaceEnvVarService agentWorkspaceEnvVarService) { + super(AgentConst.PROJECT); + this.agentWorkspaceEnvVarService = agentWorkspaceEnvVarService; + } + + @Override + 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, I18nMessageUtil.get("i18n.soft_link_project_does_not_exist.8ad2") + nodeProjectInfoModel.getLinkId()); + return item; + } + + /** + * 解析lib路径 + * + * @param nodeProjectInfoModel 项目 + * @return 项目的 lib 路径(文件路径) + */ + public String resolveLibPath(NodeProjectInfoModel nodeProjectInfoModel) { + RunMode runMode = nodeProjectInfoModel.getRunMode(); + if (runMode == RunMode.Link) { + NodeProjectInfoModel item = this.getItem(nodeProjectInfoModel.getLinkId()); + Assert.notNull(item, I18nMessageUtil.get("i18n.soft_link_project_does_not_exist.4e4f")); + return item.allLib(); + } + 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, I18nMessageUtil.get("i18n.project_id_not_found.b87e")); + 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"); + } + + /** + * 获取环境变量 + * + * @param workspaceId 工作空间ID + * @return map + */ + public Map getEnv(String workspaceId) { + EnvironmentMapBuilder env = agentWorkspaceEnvVarService.getEnv(workspaceId); + return env.environment(); + } +} 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 0000000000..96c15de7d2 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/script/DslScriptServer.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.ProjectLogConfig; +import org.dromara.jpom.exception.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.model.data.ScriptLibraryModel; +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; + private final ScriptLibraryService scriptLibraryService; + + public DslScriptServer(AgentWorkspaceEnvVarService agentWorkspaceEnvVarService, + NodeScriptServer nodeScriptServer, + ProjectLogConfig logConfig, + JpomApplication jpomApplication, + ProjectInfoService projectInfoService, + ScriptLibraryService scriptLibraryService) { + this.agentWorkspaceEnvVarService = agentWorkspaceEnvVarService; + this.nodeScriptServer = nodeScriptServer; + this.logConfig = logConfig; + this.jpomApplication = jpomApplication; + this.projectInfoService = projectInfoService; + this.scriptLibraryService = scriptLibraryService; + } + + /** + * 异步执行 + * + * @param dslYmlDto dsl 配置 + * @param consoleCommandOp 操作 + */ + public void run(DslYmlDto dslYmlDto, ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, boolean sync) throws Exception { + String log = projectInfoService.resolveAbsoluteLog(nodeProjectInfoModel, originalModel); + DslScriptBuilder builder = this.create(dslYmlDto, consoleCommandOp, nodeProjectInfoModel, originalModel, log); + Future execute = I18nThreadUtil.execAsync(builder); + if (sync) { + execute.get(); + } + } + + /** + * 同步执行 + * + * @param dslYmlDto dsl 配置 + * @param consoleCommandOp 操作 + */ + public Tuple syncRun(DslYmlDto dslYmlDto, ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) { + try (DslScriptBuilder builder = this.create(dslYmlDto, consoleCommandOp, nodeProjectInfoModel, originalModel, 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 = dslYml.tryDslProcess(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) { + String value = I18nMessageUtil.get("i18n.process_does_not_exist.4e39"); + jsonObject.put("msg", value); + return new Tuple(jsonObject, null); + } + String scriptId = scriptProcess.getScriptId(); + if (StrUtil.isEmpty(scriptId)) { + String value = I18nMessageUtil.get("i18n.script_template_id_required.f339"); + jsonObject.put("msg", value); + return new Tuple(jsonObject, null); + } + if (StrUtil.startWithIgnoreCase(scriptId, "G@")) { + // 判断是否引用脚本库 + scriptId = scriptId.substring(2); + ScriptLibraryModel libraryModel = scriptLibraryService.get(scriptId); + if (libraryModel != null) { + jsonObject.put("status", true); + jsonObject.put("type", "library"); + jsonObject.put("scriptId", scriptId); + return new Tuple(jsonObject, libraryModel); + } else { + String string = I18nMessageUtil.get("i18n.missing_script_library_message.be9a") + scriptId; + jsonObject.put("msg", string); + 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); + } + String value = I18nMessageUtil.get("i18n.script_template_not_exist.e05f") + scriptId; + jsonObject.put("msg", value); + return new Tuple(jsonObject, null); + } + + /** + * 构建 DSL 执行器 + * + * @param dslYmlDto 脚本流程 + * @param nodeProjectInfoModel 项目 + * @param log 日志路径 + * @param consoleCommandOp 具体操作 + */ + private DslScriptBuilder create(DslYmlDto dslYmlDto, ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, String log) { + DslYmlDto.BaseProcess scriptProcess = dslYmlDto.getDslProcess(consoleCommandOp.name()); + 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); + DslYmlDto.Run run = dslYmlDto.getRun(); + String execPath = run.getExecPath(); + environment.put("JPOM_EXEC_PATH", execPath); + File scriptFile; + boolean autoDelete = false; + if (StrUtil.equals(type, "file")) { + // 项目文件 + scriptFile = tuple.get(1); + } else if ("library".equals(type)) { + // 脚本库 + ScriptLibraryModel libraryModel = tuple.get(1); + scriptFile = this.initScriptFile(libraryModel); + // 系统生成的脚本需要自动删除 + autoDelete = true; + } else { + // 节点脚本 + NodeScriptModel item = tuple.get(1); + scriptFile = this.initScriptFile(item); + // 系统生成的脚本需要自动删除 + autoDelete = true; + } + DslScriptBuilder builder = new DslScriptBuilder(consoleCommandOp.name(), environment, scriptProcess.getScriptArgs(), log, logConfig.getFileCharset()); + builder.setScriptFile(scriptFile); + builder.setAutoDelete(autoDelete); + return builder; + } + + /** + * 创建脚本文件 + * + * @param scriptModel 脚本对象 + * @return file + */ + private File initScriptFile(NodeScriptModel scriptModel) { + return nodeScriptServer.toExecuteFile(scriptModel); + } + + /** + * 创建脚本文件 + * + * @param scriptModel 脚本对象 + * @return file + */ + private File initScriptFile(ScriptLibraryModel scriptModel) { + String dataPath = jpomApplication.getDataPath(); + File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX)); + // 替换内容 + String context = scriptModel.getScript(); + 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/service/script/NodeScriptExecLogServer.java b/modules/agent/src/main/java/org/dromara/jpom/service/script/NodeScriptExecLogServer.java new file mode 100644 index 0000000000..3a5dd077fc --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/script/NodeScriptExecLogServer.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.script; + +import org.dromara.jpom.common.AgentConst; +import org.dromara.jpom.model.data.NodeScriptExecLogModel; +import org.dromara.jpom.service.BaseOperService; +import org.springframework.stereotype.Service; + +/** + * @author bwcx_jzy + * @since 2021/12/26 + */ +@Service +public class NodeScriptExecLogServer extends BaseOperService { + + public NodeScriptExecLogServer() { + super(AgentConst.SCRIPT_LOG); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/script/NodeScriptServer.java b/modules/agent/src/main/java/org/dromara/jpom/service/script/NodeScriptServer.java new file mode 100644 index 0000000000..0f27c34c16 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/script/NodeScriptServer.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.script; + +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.cron.ICron; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.AgentConst; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.model.data.NodeScriptExecLogModel; +import org.dromara.jpom.model.data.NodeScriptModel; +import org.dromara.jpom.script.NodeScriptProcessBuilder; +import org.dromara.jpom.service.BaseWorkspaceOptService; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.StringUtil; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * 脚本模板管理 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@Service +public class NodeScriptServer extends BaseWorkspaceOptService implements ICron { + private final NodeScriptExecLogServer execLogServer; + private final JpomApplication jpomApplication; + private final ScriptLibraryService scriptLibraryService; + + public NodeScriptServer(NodeScriptExecLogServer execLogServer, + JpomApplication jpomApplication, + ScriptLibraryService scriptLibraryService) { + super(AgentConst.SCRIPT); + this.execLogServer = execLogServer; + this.jpomApplication = jpomApplication; + this.scriptLibraryService = scriptLibraryService; + } + + @Override + public void addItem(NodeScriptModel nodeScriptModel) { + super.addItem(nodeScriptModel); + this.checkCron(nodeScriptModel); + } + + @Override + public void updateItem(NodeScriptModel nodeScriptModel) { + super.updateItem(nodeScriptModel); + this.checkCron(nodeScriptModel); + } + + /** + * @param id 数据id + * @see NodeScriptModel#logFile(String) + */ + @Override + public void deleteItem(String id) { + NodeScriptModel nodeScriptModel = getItem(id); + if (nodeScriptModel != null) { + File file = nodeScriptModel.scriptPath(); + FileUtil.del(file); + } + super.deleteItem(id); + String taskId = "script:" + id; + CronUtils.remove(taskId); + } + + public File toExecuteFile(NodeScriptModel nodeScriptModel) { + String dataPath = jpomApplication.getDataPath(); + File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX)); + String context = nodeScriptModel.getContext(); + // + context = scriptLibraryService.referenceReplace(context); + FileUtils.writeScript(context, scriptFile, ExtConfigBean.getConsoleLogCharset()); + return scriptFile; + } + + @Override + public boolean checkCron(NodeScriptModel nodeScriptModel) { + String id = "script:" + nodeScriptModel.getId(); + String autoExecCron = nodeScriptModel.getAutoExecCron(); + autoExecCron = StringUtil.parseCron(autoExecCron); + if (StrUtil.isEmpty(autoExecCron)) { + CronUtils.remove(id); + return false; + } else { + CronUtils.upsert(id, autoExecCron, new CronTask(nodeScriptModel.getId())); + return true; + } + } + + @Override + public List queryStartingList() { + return this.list(); + } + + private static class CronTask implements Task { + private final String id; + + public CronTask(String id) { + this.id = id; + } + + @Override + public void execute() { + NodeScriptServer nodeScriptServer = SpringUtil.getBean(NodeScriptServer.class); + NodeScriptModel scriptServerItem = nodeScriptServer.getItem(id); + if (scriptServerItem == null) { + return; + } + // 创建记录 + NodeScriptExecLogServer execLogServer = SpringUtil.getBean(NodeScriptExecLogServer.class); + NodeScriptExecLogModel nodeScriptExecLogModel = new NodeScriptExecLogModel(); + nodeScriptExecLogModel.setId(IdUtil.fastSimpleUUID()); + nodeScriptExecLogModel.setCreateTimeMillis(SystemClock.now()); + nodeScriptExecLogModel.setScriptId(scriptServerItem.getId()); + nodeScriptExecLogModel.setScriptName(scriptServerItem.getName()); + nodeScriptExecLogModel.setWorkspaceId(scriptServerItem.getWorkspaceId()); + nodeScriptExecLogModel.setTriggerExecType(1); + execLogServer.addItem(nodeScriptExecLogModel); + // 执行 + NodeScriptProcessBuilder.create(scriptServerItem, nodeScriptExecLogModel.getId(), scriptServerItem.getDefArgs(), null); + } + } + + /** + * 执行脚本 + * + * @param scriptServerItem 脚本 + * @param type 类型 + * @param args 参数 + * @return 执行记录ID + */ + public String execute(NodeScriptModel scriptServerItem, int type, String uerName, String workspaceId, String args, Map paramMap) { + NodeScriptExecLogModel nodeScriptExecLogModel = new NodeScriptExecLogModel(); + nodeScriptExecLogModel.setId(IdUtil.fastSimpleUUID()); + nodeScriptExecLogModel.setCreateTimeMillis(SystemClock.now()); + nodeScriptExecLogModel.setScriptId(scriptServerItem.getId()); + nodeScriptExecLogModel.setScriptName(scriptServerItem.getName()); + nodeScriptExecLogModel.setModifyUser(uerName); + nodeScriptExecLogModel.setWorkspaceId(StrUtil.emptyToDefault(workspaceId, scriptServerItem.getWorkspaceId())); + nodeScriptExecLogModel.setTriggerExecType(type); + execLogServer.addItem(nodeScriptExecLogModel); + String userArgs = StrUtil.emptyToDefault(args, scriptServerItem.getDefArgs()); + // 执行 + NodeScriptProcessBuilder.create(scriptServerItem, nodeScriptExecLogModel.getId(), userArgs, paramMap); + return nodeScriptExecLogModel.getId(); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/script/ScriptLibraryService.java b/modules/agent/src/main/java/org/dromara/jpom/service/script/ScriptLibraryService.java new file mode 100644 index 0000000000..ff92277f90 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/script/ScriptLibraryService.java @@ -0,0 +1,117 @@ +package org.dromara.jpom.service.script; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.ScriptLibraryModel; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy1 + * @since 2024/6/14 + */ +@Service +@Slf4j +@Getter +public class ScriptLibraryService { + /** + * 脚本库目录 + */ + private final File globalScriptDir; + private final Pattern pattern = PatternPool.get("G@\\(\"(.*?)\"\\)", Pattern.DOTALL); + + public ScriptLibraryService(JpomApplication jpomApplication) { + this.globalScriptDir = FileUtil.file(jpomApplication.getDataPath(), "global-script"); + } + + /** + * 引用替换 + * + * @param script 脚本 + * @return 替换后的脚本 + */ + public String referenceReplace(String script) { + if (StrUtil.isEmpty(script)) { + return script; + } + Map map = new HashMap<>(3); + Matcher matcher = pattern.matcher(script); + StringBuffer modified = new StringBuffer(); + while (matcher.find()) { + String tag = matcher.group(1); + ScriptLibraryModel scriptLibraryModel = map.get(tag); + if (scriptLibraryModel == null) { + scriptLibraryModel = this.get(tag); + if (scriptLibraryModel != null) { + map.put(tag, scriptLibraryModel); + } + } + Assert.notNull(scriptLibraryModel, StrUtil.format(I18nMessageUtil.get("i18n.error_message.483d"), tag)); + matcher.appendReplacement(modified, scriptLibraryModel.getScript()); + } + matcher.appendTail(modified); + return modified.toString(); + } + + /** + * 获取脚本库列表 + * + * @return list + */ + public List list() { + List list = FileUtil.loopFiles(globalScriptDir, 1, pathname -> StrUtil.equals("json", FileUtil.extName(pathname))); + return list.stream() + .map(file -> { + try { + String string = FileUtil.readUtf8String(file); + ScriptLibraryModel scriptModel = JSONObject.parseObject(string, ScriptLibraryModel.class); + // 以文件名为脚本标签 + scriptModel.setTag(FileUtil.mainName(file)); + // 脚本内容不返回 + scriptModel.setScript(null); + return scriptModel; + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.read_global_script_file_error.0d4c"), e); + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * 获取脚本库 + * + * @param id id + * @return model + */ + public ScriptLibraryModel get(String id) { + if (StrUtil.isEmpty(id)) { + return null; + } + File file = FileUtil.file(globalScriptDir, id + ".json"); + if (FileUtil.exist(file)) { + String string = FileUtil.readUtf8String(file); + ScriptLibraryModel scriptModel = JSONObject.parseObject(string, ScriptLibraryModel.class); + // 以文件名为脚本标签 + scriptModel.setTag(FileUtil.mainName(file)); + return scriptModel; + } + return null; + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/system/AgentWorkspaceEnvVarService.java b/modules/agent/src/main/java/org/dromara/jpom/service/system/AgentWorkspaceEnvVarService.java new file mode 100644 index 0000000000..4e5141a39a --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/system/AgentWorkspaceEnvVarService.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.system; + +import cn.hutool.core.collection.CollStreamUtil; +import org.dromara.jpom.common.AgentConst; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.system.WorkspaceEnvVarModel; +import org.dromara.jpom.service.BaseOperService; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * @author lidaofu + * @since 2022/3/8 9:16 + **/ +@Service +public class AgentWorkspaceEnvVarService extends BaseOperService { + + public AgentWorkspaceEnvVarService() { + super(AgentConst.WORKSPACE_ENV_VAR); + } + + /** + * 获取指定工作空间的环境变量 + * + * @param workspaceId 工作空间 + * @return env + */ + public EnvironmentMapBuilder getEnv(String workspaceId) { + WorkspaceEnvVarModel item = this.getItem(workspaceId); + Map objectMap = Optional.ofNullable(item) + .map(WorkspaceEnvVarModel::getVarData) + .map(map -> CollStreamUtil.toMap(map.values(), WorkspaceEnvVarModel.WorkspaceEnvVarItemModel::getName, workspaceEnvVarItemModel -> { + // 需要考虑兼容之前没有隐私变量字段,默认为隐私字段 + Integer privacy = workspaceEnvVarItemModel.getPrivacy(); + return new EnvironmentMapBuilder.Item(workspaceEnvVarItemModel.getValue(), privacy == null || privacy == 1); + })) + .orElse(new HashMap<>(1)); + return EnvironmentMapBuilder.builder(objectMap); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFileTailWatcher.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFileTailWatcher.java new file mode 100644 index 0000000000..cb69b307ad --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFileTailWatcher.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.BaseFileTailWatcher; +import org.dromara.jpom.util.SocketSessionUtil; + +import javax.websocket.Session; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文件跟随器 + * + * @author bwcx_jzy + * @since 2019/3/16 + */ +@Slf4j +public class AgentFileTailWatcher extends BaseFileTailWatcher { + private static final ConcurrentHashMap> CONCURRENT_HASH_MAP = new SafeConcurrentHashMap<>(); + + + private AgentFileTailWatcher(File logFile, Charset charset) { + super(logFile, charset); + } + + public static int getOneLineCount() { + return CONCURRENT_HASH_MAP.size(); + } + + /** + * 添加文件监听 + * + * @param file 文件 + * @param charset 编码格式 + * @param session 会话 + * @throws IOException 异常 + */ + public static boolean addWatcher(File file, Charset charset, Session session) throws IOException { + if (!FileUtil.isFile(file)) { + log.warn(I18nMessageUtil.get("i18n.file_or_directory_not_found.f03e") + file.getPath()); + return false; + } + AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.computeIfAbsent(file, s -> { + try { + return new AgentFileTailWatcher<>(file, charset); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.create_file_watch_failure.bc1a"), e); + return null; + } + }); + if (agentFileTailWatcher == null) { + throw new IOException(I18nMessageUtil.get("i18n.load_file_failure.86cc") + file.getPath()); + } + if (agentFileTailWatcher.add(session, FileUtil.getName(file))) { + agentFileTailWatcher.start(); + } + return true; + } + + /** + * 有客户端离线 + * + * @param session 会话 + */ + public static void offline(Session session) { + Collection> collection = CONCURRENT_HASH_MAP.values(); + for (AgentFileTailWatcher agentFileTailWatcher : collection) { + agentFileTailWatcher.socketSessions.removeIf(session::equals); + if (agentFileTailWatcher.socketSessions.isEmpty()) { + agentFileTailWatcher.close(); + } + } + } + + /** + * 关闭文件读取流 + * + * @param fileName 文件名 + */ + public static void offlineFile(File fileName) { + AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); + if (null == agentFileTailWatcher) { + return; + } + Set socketSessions = agentFileTailWatcher.socketSessions; + for (Session socketSession : socketSessions) { + offline(socketSession); + } + agentFileTailWatcher.close(); + } + + /** + * 重新监听 + * + * @param fileName 文件名 + */ + public static void reWatcher(File fileName) { + AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); + if (null == agentFileTailWatcher) { + return; + } + agentFileTailWatcher.restart(); + } + + /** + * 关闭文件读取流 + * + * @param fileName 文件名 + */ + static void offlineFile(File fileName, Session session) { + AgentFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); + if (null == agentFileTailWatcher) { + return; + } + Set socketSessions = agentFileTailWatcher.socketSessions; + for (Session socketSession : socketSessions) { + if (socketSession.equals(session)) { + offline(socketSession); + break; + } + } + if (agentFileTailWatcher.socketSessions.isEmpty()) { + agentFileTailWatcher.close(); + } + + } + + @Override + protected boolean send(T session, String msg) throws IOException { +// try { + SocketSessionUtil.send((Session) session, msg); +// } catch (Exception e) { +// log.error("发送消息异常", e); +// } + return true; + } + + /** + * 关闭 + */ + @Override + protected void close() { + super.close(); + // 清理线程记录 + CONCURRENT_HASH_MAP.remove(this.logFile); + } +} 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 new file mode 100644 index 0000000000..d64ec430d0 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFreeWebSocketScriptHandle.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.websocket.Constants; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AgentConfig; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 自由脚本socket + * + * @author bwcx_jzy + * @since 2023/03/28 + */ +@ServerEndpoint(value = "/free-script-run") +@Component +@Slf4j +public class AgentFreeWebSocketScriptHandle extends BaseAgentWebSocketHandle { + + private final static Map CACHE = new SafeConcurrentHashMap<>(); + + @Autowired + public void init(AgentConfig agentConfig) { + setAgentAuthorize(agentConfig.getAuthorize()); + } + + @OnOpen + public void onOpen(Session session) { + try { + setLanguage(session); + if (super.checkAuthorize(session)) { + return; + } + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.connection_successful.b331")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.socket_error.18c1"), e); + try { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.system_error.9417"))); + session.close(); + } catch (IOException e1) { + log.error(e1.getMessage(), e1); + } + } finally { + clearLanguage(); + } + } + + /** + * @param message 消息 + * @param session 会话 + * @throws Exception 异常 + * @see Constants#DEFAULT_BUFFER_SIZE + */ + @OnMessage(maxMessageSize = 5 * 1024 * 1024) + public void onMessage(String message, Session session) throws Exception { + try { + setLanguage(session); + if (CACHE.containsKey(session.getId())) { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.do_not_reopen.f86a"))); + return; + } + JSONObject json = JSONObject.parseObject(message); + String type = json.getString("type"); + if (StrUtil.equals(type, "close")) { + // 关闭、停止脚本执行 + IoUtil.close(CACHE.remove(session.getId())); + session.close(); + return; + } + String path = json.getString("path"); + String tag = json.getString("tag"); + JSONObject environment = json.getJSONObject("environment"); + String content = json.getString("content"); + if (StrUtil.hasEmpty(path, tag, content)) { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.incorrect_parameter.02ce"))); + return; + } + if (environment == null) { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.environment_variables_not_found.dbd4"))); + return; + } + Map map = environment.to(new TypeReference>() { + }); + ScriptProcess scriptProcess = new ScriptProcess(content, map, path, tag); + CACHE.put(session.getId(), scriptProcess); + scriptProcess.run(line -> { + try { + SocketSessionUtil.send(session, line); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + } + }); + } finally { + clearLanguage(); + } + } + + + @Override + @OnClose + public void onClose(Session session, CloseReason closeReason) { + super.onClose(session, closeReason); + IoUtil.close(CACHE.remove(session.getId())); + } + + @OnError + @Override + public void onError(Session session, Throwable thr) { + super.onError(session, thr); + IoUtil.close(CACHE.remove(session.getId())); + } + + public static class ScriptProcess implements AutoCloseable { + private final String content; + private final EnvironmentMapBuilder environment; + private final String path; + private final String tag; + + private Process process; + private InputStream inputStream; + private File scriptFile; + + public ScriptProcess(String content, Map environment, String path, String tag) { + this.content = content; + this.environment = EnvironmentMapBuilder.builder(environment); + this.path = path; + this.tag = tag; + } + + /** + * 开始执行脚本 + * + * @param lineHandler 回调 + * @throws IOException io 异常 + * @throws InterruptedException 中断 + */ + public void run(LineHandler lineHandler) throws IOException, InterruptedException { + String dataPath = JpomApplication.getInstance().getDataPath(); + this.scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX)); + FileUtils.writeScript(this.content, scriptFile, ExtConfigBean.getConsoleLogCharset()); + // + String script = FileUtil.getAbsolutePath(scriptFile); + ProcessBuilder processBuilder = new ProcessBuilder(); + List command = new ArrayList<>(); + command.add(0, script); + CommandUtil.paddingPrefix(command); + log.debug(CollUtil.join(command, StrUtil.SPACE)); + // 添加环境变量 + this.environment.eachStr(lineHandler::handle); + Map environment = processBuilder.environment(); + environment.putAll(this.environment.environment()); + processBuilder.redirectErrorStream(true); + processBuilder.command(command); + // + if (StrUtil.isNotEmpty(path)) { + File directory = FileUtil.file(path).getAbsoluteFile(); + // 需要创建目录 + FileUtil.mkdir(directory); + processBuilder.directory(directory); + } + // + process = processBuilder.start(); + inputStream = process.getInputStream(); + IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), lineHandler); + int waitFor = process.waitFor(); + lineHandler.handle(StrUtil.format(I18nMessageUtil.get("i18n.execution_ended.b793"), waitFor)); + // 客户端可以关闭会话啦 + lineHandler.handle("JPOM_SYSTEM_TAG:" + tag); + } + + @Override + public void close() throws Exception { + IoUtil.close(inputStream); + CommandUtil.kill(process); + try { + FileUtil.del(this.scriptFile); + } catch (Exception ignored) { + } + } + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConfig.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConfig.java new file mode 100644 index 0000000000..b14983d115 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConfig.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; +import org.springframework.web.context.support.WebApplicationObjectSupport; + +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 插件端socket 配置 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@Configuration +public class AgentWebSocketConfig extends WebApplicationObjectSupport implements SmartInitializingSingleton { + + + private ServerContainer getServerContainer() { + return (ServerContainer) Objects.requireNonNull(getServletContext()).getAttribute("javax.websocket.server.ServerContainer"); + } + + @Override + protected boolean isContextRequired() { + return true; + } + + @Override + public void afterSingletonsInstantiated() { + registerEndpoints(); + } + + protected void registerEndpoints() { + Set> endpointClasses = new LinkedHashSet<>(); + + ApplicationContext context = getApplicationContext(); + if (context != null) { + String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerEndpoint.class); + for (String beanName : endpointBeanNames) { + endpointClasses.add(context.getType(beanName)); + } + } + + for (Class endpointClass : endpointClasses) { + registerEndpoint(endpointClass); + } + + if (context != null) { + Map endpointConfigMap = context.getBeansOfType(ServerEndpointConfig.class); + for (ServerEndpointConfig endpointConfig : endpointConfigMap.values()) { + registerEndpoint(endpointConfig); + } + } + } + + private void registerEndpoint(Class endpointClass) { + ServerContainer serverContainer = getServerContainer(); + Assert.state(serverContainer != null, + "No ServerContainer set. Most likely the server's own WebSocket ServletContainerInitializer " + + "has not run yet. Was the Spring ApplicationContext refreshed through a " + + "org.springframework.web.context.ContextLoaderListener, " + + "i.e. after the ServletContext has been fully initialized?"); + try { + if (logger.isDebugEnabled()) { + logger.debug("Registering @ServerEndpoint class: " + endpointClass); + } + serverContainer.addEndpoint(endpointClass); + } catch (DeploymentException ex) { + throw new IllegalStateException("Failed to register @ServerEndpoint class: " + endpointClass, ex); + } + } + + private void registerEndpoint(ServerEndpointConfig endpointConfig) { + ServerContainer serverContainer = this.getServerContainer(); + Assert.state(serverContainer != null, "No ServerContainer set"); + try { + if (logger.isDebugEnabled()) { + logger.debug("Registering ServerEndpointConfig: " + endpointConfig); + } + serverContainer.addEndpoint(endpointConfig); + } catch (DeploymentException ex) { + throw new IllegalStateException("Failed to register ServerEndpointConfig: " + endpointConfig, ex); + } + } +} 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 new file mode 100644 index 0000000000..b8e50cf7ce --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConsoleHandle.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +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.common.Const; +import org.dromara.jpom.common.commander.CommandOpResult; +import org.dromara.jpom.common.commander.ProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.AgentConfig; +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.ProjectInfoService; +import org.dromara.jpom.util.FileSearchUtil; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * 插件端,控制台socket + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@ServerEndpoint(value = "/console") +@Component +@Slf4j +public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle { + + private static ProjectInfoService projectInfoService; + private static ProjectLogConfig logConfig; + private static ProjectCommander projectCommander; + + @Autowired + public void init(ProjectInfoService projectInfoService, + AgentConfig agentConfig, + ProjectCommander projectCommander) { + AgentWebSocketConsoleHandle.projectInfoService = projectInfoService; + AgentWebSocketConsoleHandle.logConfig = agentConfig.getProject().getLog(); + AgentWebSocketConsoleHandle.projectCommander = projectCommander; + setAgentAuthorize(agentConfig.getAuthorize()); + } + + @OnOpen + public void onOpen(Session session) { + try { + setLanguage(session); + if (super.checkAuthorize(session)) { + return; + } + String projectId = super.getParameters(session, "projectId"); + // 判断项目 + if (!Const.SYSTEM_ID.equals(projectId)) { + NodeProjectInfoModel nodeProjectInfoModel = this.checkProject(projectId, session); + if (nodeProjectInfoModel == null) { + return; + } + // + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.connection_successful_with_message.5cf2") + nodeProjectInfoModel.getName()); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.socket_error.18c1"), e); + try { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.system_error.9417"))); + session.close(); + } catch (IOException e1) { + log.error(e1.getMessage(), e1); + } + } finally { + clearLanguage(); + } + } + + /** + * 静默消息不做过多处理 + * + * @param consoleCommandOp 操作 + * @param session 回话 + * @return true + */ + private boolean silentMsg(ConsoleCommandOp consoleCommandOp, Session session) { + if (consoleCommandOp == ConsoleCommandOp.heart) { + return true; + } +// if (consoleCommandOp == ConsoleCommandOp.top) { +// TopManager.addMonitor(session); +// return true; +// } + return false; + } + + private NodeProjectInfoModel checkProject(String projectId, Session session) throws IOException { + NodeProjectInfoModel nodeProjectInfoModel = projectInfoService.getItem(projectId); + if (nodeProjectInfoModel == null) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_project_specified2.a7f5") + projectId); + session.close(); + return null; + } + return nodeProjectInfoModel; + } + + @OnMessage + public void onMessage(String message, Session session) throws Exception { + try { + setLanguage(session); + JSONObject json = JSONObject.parseObject(message); + String op = json.getString("op"); + ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op); + if (silentMsg(consoleCommandOp, session)) { + return; + } + String projectId = json.getString("projectId"); + NodeProjectInfoModel nodeProjectInfoModel = this.checkProject(projectId, session); + 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); + } finally { + clearLanguage(); + } + } + + private void runMsg(ConsoleCommandOp consoleCommandOp, Session session, NodeProjectInfoModel nodeProjectInfoModel, JSONObject reqJson) throws Exception { + // + + JsonMessage resultData = null; + CommandOpResult strResult; + boolean logUser = false; + try { + // 执行相应命令 + switch (consoleCommandOp) { + case start: + case restart: + case stop: + case reload: + logUser = true; + strResult = projectCommander.execCommand(consoleCommandOp, nodeProjectInfoModel); + if (strResult.isSuccess()) { + resultData = new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313"), strResult); + } else { + resultData = new JsonMessage<>(400, strResult.msgStr()); + } + break; + + case status: { + // 获取项目状态 + strResult = projectCommander.execCommand(consoleCommandOp, nodeProjectInfoModel); + if (strResult.isSuccess()) { + resultData = new JsonMessage<>(200, I18nMessageUtil.get("i18n.running_status.d679"), strResult); + } else { + resultData = new JsonMessage<>(404, I18nMessageUtil.get("i18n.not_running.4f8a"), strResult); + } + break; + } + case showlog: { + // 进入管理页面后需要实时加载日志 + String search = reqJson.getString("search"); + if (StrUtil.isNotEmpty(search)) { + resultData = searchLog(session, nodeProjectInfoModel, reqJson); + } else { + showLog(session, nodeProjectInfoModel, reqJson); + } + break; + } + default: + resultData = new JsonMessage<>(404, I18nMessageUtil.get("i18n.unsupported_method_with_colon.eae8") + consoleCommandOp.name()); + break; + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.command_execution_failed.90ef"), e); + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.command_execution_failed_details.77ed")); + SocketSessionUtil.send(session, ExceptionUtil.stacktraceToString(e)); + return; + } finally { + if (logUser) { + // 记录操作人 + NodeProjectInfoModel update = new NodeProjectInfoModel(); + String name = getOptUserName(session); + 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); + SocketSessionUtil.send(session, reqJson.toString()); + } + } + + /** + * { + * "op": "showlog", + * "projectId": "python", + * "search": true, + * "useProjectId": "python", + * "useNodeId": "localhost", + * "beforeCount": 0, + * "afterCount": 10, + * "head": 0, + * "tail": 100, + * "first": "false", + * "logFile": "/run.log" + * } + * + * @param session 会话 + * @param nodeProjectInfoModel 项目 + * @param reqJson 请求参数 + * @return 返回信息 + */ + private JsonMessage searchLog(Session session, NodeProjectInfoModel nodeProjectInfoModel, JSONObject reqJson) { + // + String fileName = reqJson.getString("logFile"); + File libFile = projectInfoService.resolveLibFile(nodeProjectInfoModel); + File file = FileUtil.file(libFile, fileName); + if (!FileUtil.isFile(file)) { + return new JsonMessage<>(404, I18nMessageUtil.get("i18n.file_not_found.d952")); + } + I18nThreadUtil.execute(() -> { + try { + boolean first = Convert.toBool(reqJson.getString("first"), false); + int head = reqJson.getIntValue("head"); + int tail = reqJson.getIntValue("tail"); + int beforeCount = reqJson.getIntValue("beforeCount"); + int afterCount = reqJson.getIntValue("afterCount"); + String keyword = reqJson.getString("keyword"); + Charset charset = logConfig.getFileCharset(); + //BaseFileTailWatcher.detectorCharset(file); + String resultMsg = FileSearchUtil.searchList(file, charset, keyword, beforeCount, afterCount, head, tail, first, objects -> { + try { + String line = objects.get(1); + SocketSessionUtil.send(session, line); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + SocketSessionUtil.send(session, resultMsg); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.file_search_failed.231b"), e); + try { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.command_execution_failed_details.77ed")); + } catch (IOException ignored) { + } + } + }); + return null; + } + + private void showLog(Session session, NodeProjectInfoModel nodeProjectInfoModel, JSONObject reqJson) throws IOException { + // 日志文件路径 + String fileName = reqJson.getString("fileName"); + File file; + if (StrUtil.isEmpty(fileName)) { + file = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel); + } else { + File libFile = projectInfoService.resolveLibFile(nodeProjectInfoModel); + file = FileUtil.file(libFile, fileName); + } + try { + Charset charset = logConfig.getFileCharset(); + boolean watcher = AgentFileTailWatcher.addWatcher(file, charset, session); + if (!watcher) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.listen_file_failed_may_not_exist.fd56")); + } + } catch (Exception io) { + log.error(I18nMessageUtil.get("i18n.listen_log_changes.9081"), io); + SocketSessionUtil.send(session, io.getMessage()); + } + } + + @Override + @OnClose + public void onClose(Session session, CloseReason closeReason) { + super.onClose(session, closeReason); + AgentFileTailWatcher.offline(session); + } + + @OnError + @Override + public void onError(Session session, Throwable thr) { + super.onError(session, thr); + } +} 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 new file mode 100644 index 0000000000..35520447a0 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketScriptHandle.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +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.common.i18n.I18nMessageUtil; +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; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +/** + * 脚本模板socket + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@ServerEndpoint(value = "/script_run") +@Component +@Slf4j +public class AgentWebSocketScriptHandle extends BaseAgentWebSocketHandle { + + private static NodeScriptServer nodeScriptServer; + + @Autowired + public void init(NodeScriptServer nodeScriptServer, AgentConfig agentConfig) { + AgentWebSocketScriptHandle.nodeScriptServer = nodeScriptServer; + setAgentAuthorize(agentConfig.getAuthorize()); + } + + @OnOpen + public void onOpen(Session session) { + try { + setLanguage(session); + if (super.checkAuthorize(session)) { + return; + } + String id = this.getParameters(session, "id"); + String workspaceId = this.getParameters(session, "workspaceId"); + if (StrUtil.hasEmpty(id, workspaceId)) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.unknown_script_template_or_workspace.27f1")); + return; + } + + NodeScriptModel nodeScriptModel = nodeScriptServer.getItem(id); + if (nodeScriptModel == null) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_script_template_found.0498")); + return; + } + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.connection_successful_with_message.5cf2") + nodeScriptModel.getName()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.socket_error.18c1"), e); + try { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.system_error.9417"))); + session.close(); + } catch (IOException e1) { + log.error(e1.getMessage(), e1); + } + } finally { + clearLanguage(); + } + } + + @OnMessage + public void onMessage(String message, Session session) throws Exception { + try { + setLanguage(session); + JSONObject json = JSONObject.parseObject(message); + String scriptId = json.getString("scriptId"); + NodeScriptModel nodeScriptModel = nodeScriptServer.getItem(scriptId); + if (nodeScriptModel == null) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_script_template_specified.7d14") + scriptId); + session.close(); + return; + } + String op = json.getString("op"); + ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op); + switch (consoleCommandOp) { + case start: { + String args = json.getString("args"); + String executeId = json.getString("executeId"); + if (StrUtil.isEmpty(executeId)) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_execution_id.68dc")); + session.close(); + return; + } + NodeScriptProcessBuilder.addWatcher(nodeScriptModel, executeId, args, session); + break; + } + case stop: { + String executeId = json.getString("executeId"); + if (StrUtil.isEmpty(executeId)) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_execution_id.68dc")); + session.close(); + return; + } + NodeScriptProcessBuilder.stopRun(executeId); + break; + } + case heart: + default: + return; + } + // 记录操作人 + nodeScriptModel = nodeScriptServer.getItem(scriptId); + String name = getOptUserName(session); + nodeScriptModel.setLastRunUser(name); + nodeScriptServer.updateItem(nodeScriptModel); + json.put("code", 200); + String value = I18nMessageUtil.get("i18n.execution_succeeded.f56c"); + json.put("msg", value); + log.debug(json.toString()); + SocketSessionUtil.send(session, json.toString()); + } finally { + clearLanguage(); + } + } + + + @Override + @OnClose + public void onClose(Session session, CloseReason closeReason) { + super.onClose(session, closeReason); + NodeScriptProcessBuilder.stopWatcher(session); + } + + @OnError + @Override + public void onError(Session session, Throwable thr) { + super.onError(session, thr); + } +} 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 new file mode 100644 index 0000000000..e9544196c3 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketSystemLogHandle.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +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.common.i18n.I18nMessageUtil; +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; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.File; +import java.io.IOException; +import java.util.Map; + +/** + * 系统日志 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@ServerEndpoint(value = "/system_log") +@Component +@Slf4j +public class AgentWebSocketSystemLogHandle extends BaseAgentWebSocketHandle { + + 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 + public void onOpen(Session session) { + try { + setLanguage(session); + if (super.checkAuthorize(session)) { + return; + } + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.plugin_end_log_connection_successful.9035")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.socket_error.18c1"), e); + try { + SocketSessionUtil.send(session, JsonMessage.getString(500, I18nMessageUtil.get("i18n.system_error.9417"))); + session.close(); + } catch (IOException e1) { + log.error(e1.getMessage(), e1); + } + } finally { + clearLanguage(); + } + } + + @OnMessage + public void onMessage(String message, Session session) throws Exception { + try { + setLanguage(session); + JSONObject json = JSONObject.parseObject(message); + String op = json.getString("op"); + ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op); + if (consoleCommandOp == ConsoleCommandOp.heart) { + return; + } + runMsg(session, json); + } finally { + clearLanguage(); + } + } + + private void runMsg(Session session, JSONObject reqJson) throws Exception { + try { + String fileName = reqJson.getString("fileName"); + // 进入管理页面后需要实时加载日志 + File file = FileUtil.file(LogbackConfig.getPath(), fileName); + File file1 = CACHE_FILE.get(session.getId()); + if (file1 != null && !file1.equals(file)) { + // 离线上一个日志 + AgentFileTailWatcher.offlineFile(file, session); + } + try { + AgentFileTailWatcher.addWatcher(file, systemConfig.getLogCharset(), session); + CACHE_FILE.put(session.getId(), file); + } catch (Exception io) { + log.error(I18nMessageUtil.get("i18n.listen_log_changes.9081"), io); + SocketSessionUtil.send(session, io.getMessage()); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.command_execution_failed.90ef"), e); + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.command_execution_failed_details.77ed")); + SocketSessionUtil.send(session, ExceptionUtil.stacktraceToString(e)); + } + } + + @Override + @OnClose + public void onClose(Session session, CloseReason closeReason) { + super.onClose(session, closeReason); + } + + @OnError + @Override + public void onError(Session session, Throwable thr) { + super.onError(session, thr); + } +} 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 new file mode 100644 index 0000000000..ff23543bf9 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketUpdateHandle.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.lang.Tuple; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.websocket.Constants; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AgentConfig; +import org.dromara.jpom.model.AgentFileModel; +import org.dromara.jpom.model.UploadFileModel; +import org.dromara.jpom.model.WebSocketMessageModel; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.unit.DataSize; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * 在线升级 + * + * @author bwcx_jzy + * @since 2021/8/3 + */ +@ServerEndpoint(value = "/node_update") +@Component +@Slf4j +public class AgentWebSocketUpdateHandle extends BaseAgentWebSocketHandle { + + private static final Map UPLOAD_FILE_INFO = new HashMap<>(); + + private static AgentConfig agentConfig; + private static MultipartProperties multipartProperties; + + @Autowired + public void init(AgentConfig agentConfig, MultipartProperties multipartProperties) { + AgentWebSocketUpdateHandle.agentConfig = agentConfig; + AgentWebSocketUpdateHandle.multipartProperties = multipartProperties; + setAgentAuthorize(agentConfig.getAuthorize()); + } + + @OnOpen + public void onOpen(Session session) { + try { + setLanguage(session); + if (super.checkAuthorize(session)) { + return; + } + DataSize maxRequestSize = multipartProperties.getMaxRequestSize(); + int max = Optional.ofNullable(maxRequestSize) + .map(dataSize -> { + // 最大 10MB + long value = Math.min(dataSize.toBytes(), DataSize.ofMegabytes(10).toBytes()); + // 最后转换,不然可能出现 0 + int valueInt = (int) value; + return valueInt > 0 ? valueInt : null; + }) + .orElseGet(() -> (int) DataSize.ofMegabytes(10).toBytes()); + + session.setMaxBinaryMessageBufferSize(max); + // + } finally { + clearLanguage(); + } + } + + + @OnMessage + public void onMessage(String message, Session session) throws Exception { + try { + setLanguage(session); + WebSocketMessageModel model = WebSocketMessageModel.getInstance(message); + switch (model.getCommand()) { + case "getVersion": + model.setData(JSONObject.toJSONString(JpomManifest.getInstance())); + break; + case "upload": + AgentFileModel agentFileModel = ((JSONObject) model.getParams()).toJavaObject(AgentFileModel.class); + UploadFileModel uploadFileModel = new UploadFileModel(); + uploadFileModel.setId(model.getNodeId()); + uploadFileModel.setName(agentFileModel.getName()); + uploadFileModel.setSize(agentFileModel.getSize()); + uploadFileModel.setVersion(agentFileModel.getVersion()); + uploadFileModel.setSavePath(agentConfig.getTempPath().getAbsolutePath()); + uploadFileModel.remove(); + UPLOAD_FILE_INFO.put(session.getId(), uploadFileModel); + break; + case "restart": + model.setData(restart(session)); + break; + case "heart": + break; + default: + log.warn(I18nMessageUtil.get("i18n.ignored_operation.edee"), message); + break; + } + SocketSessionUtil.send(session, model.toString()); + //session.sendMessage(new TextMessage(model.toString())); + } finally { + clearLanguage(); + } + } + + /** + * @param message byte 消息 + * @param session 会话 + * @throws Exception 异常 + * @see Constants#DEFAULT_BUFFER_SIZE + */ + @OnMessage(maxMessageSize = 5 * 1024 * 1024) + public void onMessage(byte[] message, Session session) throws Exception { + try { + setLanguage(session); + UploadFileModel uploadFileModel = UPLOAD_FILE_INFO.get(session.getId()); + uploadFileModel.save(message); + // 更新进度 + WebSocketMessageModel model = new WebSocketMessageModel("updateNode", uploadFileModel.getId()); + model.setData(uploadFileModel); + SocketSessionUtil.send(session, model.toString()); + // session.sendMessage(new TextMessage(model.toString())); + } finally { + clearLanguage(); + } + } + + /** + * 重启 + * + * @param session 回话 + * @return 结果 + */ + public String restart(Session session) { + String result = Const.UPGRADE_MSG.get(); + try { + UploadFileModel uploadFile = UPLOAD_FILE_INFO.get(session.getId()); + String filePath = uploadFile.getFilePath(); + JsonMessage error = JpomManifest.checkJpomJar(filePath, Type.Agent); + if (!error.success()) { + return error.getMsg(); + } + JpomManifest.releaseJar(filePath, uploadFile.getVersion()); + JpomApplication.restart(); + } catch (Exception e) { + result = I18nMessageUtil.get("i18n.restart_failed.f92a") + e.getMessage(); + log.error(I18nMessageUtil.get("i18n.restart_failed.f92a"), e); + } + return result; + } + + @Override + @OnClose + public void onClose(Session session, CloseReason closeReason) { + super.onClose(session, closeReason); + UPLOAD_FILE_INFO.remove(session.getId()); + } + + @OnError + @Override + public void onError(Session session, Throwable thr) { + super.onError(session, thr); + } +} 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 new file mode 100644 index 0000000000..78a050d829 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/BaseAgentWebSocketHandle.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.collection.CollUtil; +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 com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AgentAuthorize; +import org.dromara.jpom.util.SocketSessionUtil; + +import javax.websocket.CloseReason; +import javax.websocket.Session; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 插件端socket 基类 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@Slf4j +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 void setLanguage(Session session) { + Map> requestParameterMap = session.getRequestParameterMap(); + List lang = requestParameterMap.get("lang"); + I18nMessageUtil.setLanguage(CollUtil.getFirst(lang)); + } + + protected void clearLanguage() { + I18nMessageUtil.clearLanguage(); + } + + protected String getParameters(Session session, String name) { + Map> requestParameterMap = session.getRequestParameterMap(); + Map parameters = session.getPathParameters(); + if (log.isDebugEnabled()) { + log.debug("web socket parameters: {} {}", JSONObject.toJSONString(requestParameterMap), parameters); + } + List strings = requestParameterMap.get(name); + String value = CollUtil.join(strings, StrUtil.COMMA); + if (StrUtil.isEmpty(value)) { + value = parameters.get(name); + } + return URLUtil.decode(value); + } + + /** + * 判断授权信息是否正确 + * + * @param session session + * @return true 需要结束回话 + */ + public boolean checkAuthorize(Session session) { + String authorize = this.getParameters(session, Const.JPOM_AGENT_AUTHORIZE); + boolean ok = agentAuthorize.checkAuthorize(authorize); + if (!ok) { + log.warn(I18nMessageUtil.get("i18n.socket_session_establishment_failed.4924")); + try { + session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, I18nMessageUtil.get("i18n.auth_info_error.c184"))); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.socket_error.18c1"), e); + } + return true; + } + this.addUser(session, this.getParameters(session, "optUser")); + return false; + } + + /** + * 添加用户监听的 + * + * @param session session + * @param name 用户名 + */ + private void addUser(Session session, String name) { + String optUser = URLUtil.decode(name); + if (optUser == null) { + return; + } + USER.put(session.getId(), optUser); + } + + public void onError(Session session, Throwable thr) { + // java.io.IOException: Broken pipe + try { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.server_exception_occurred.9eb4") + ExceptionUtil.stacktraceToString(thr)); + } catch (IOException ignored) { + } + log.error("{}{}", session.getId(), I18nMessageUtil.get("i18n.socket_exception.d836"), thr); + } + + protected String getOptUserName(Session session) { + String name = USER.get(session.getId()); + return StrUtil.emptyToDefault(name, StrUtil.DASHED); + } + + public void onClose(Session session, CloseReason closeReason) { + log.debug(I18nMessageUtil.get("i18n.session_closed_reason.103a"), session.getId(), closeReason); + // 清理日志监听 + try { + AgentFileTailWatcher.offline(session); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.close_exception.5b86"), e); + } + // top + // TopManager.removeMonitor(session); + USER.remove(session.getId()); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/system/AgentLogbackConfig.java b/modules/agent/src/main/java/org/dromara/jpom/system/AgentLogbackConfig.java new file mode 100644 index 0000000000..527954f465 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/system/AgentLogbackConfig.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +/** + * @author bwcx_jzy + * @since 2022/12/17 + */ +public class AgentLogbackConfig extends LogbackConfig { +} 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 new file mode 100644 index 0000000000..bccd2a1654 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/system/AgentStartInit.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateException; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.text.CharPool; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.keepbx.jpom.event.ISystemTask; +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.ProjectCommander; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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; +import org.springframework.util.unit.DataSize; + +import java.io.File; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 自动备份控制台日志,防止日志文件过大 + * + * @author bwcx_jzy + * @since 2019/3/17 + */ +@Slf4j +@Configuration +public class AgentStartInit implements ILoadEvent, ISystemTask { + + private static final String ID = "auto_back_log"; + private final ProjectInfoService projectInfoService; + 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, + JpomApplication jpomApplication, + ProjectCommander projectCommander) { + this.projectInfoService = projectInfoService; + this.agentConfig = agentConfig; + this.agentAuthorize = agentConfig.getAuthorize(); + this.jpomApplication = jpomApplication; + this.projectCommander = projectCommander; + projectLogConfig = agentConfig.getProject().getLog(); + } + + + private void startAutoBackLog() { + // 获取cron 表达式 + String cron = Opt.ofBlankAble(projectLogConfig.getAutoBackupConsoleCron()).orElse("0 0/10 * * * ?"); + // + CronUtils.upsert(ID, cron, () -> { + try { + List list = projectInfoService.list(); + if (list == null) { + return; + } + // + list.forEach(this::checkProject); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.scheduled_backup_log_failure.a0d7"), e); + } + }); + } + + private void checkProject(NodeProjectInfoModel nodeProjectInfoModel) { + File file = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel); + if (!file.exists()) { + return; + } + DataSize autoBackSize = projectLogConfig.getAutoBackupSize(); + autoBackSize = Optional.ofNullable(autoBackSize).orElseGet(() -> DataSize.ofMegabytes(50)); + long len = file.length(); + if (len > autoBackSize.toBytes()) { + try { + projectCommander.backLog(nodeProjectInfoModel); + } catch (Exception e) { + log.warn("auto back log", e); + } + } + // 清理过期的文件 + 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 = projectLogConfig.getSaveDays(); + return days > saveDays; + }); + files.forEach(FileUtil::del); + } + + @Override + public void executeTask() { + // 启动加载 + RemoteVersion.loadRemoteInfo(); + // 清空脚本缓存 + BaseRunScript.clearRunScript(); + // 清理临时文件 + File tempPath = agentConfig.getTempPath(); + if (FileUtil.exist(tempPath)) { + File[] files = tempPath.listFiles((dir, name) -> { + try { + DateTime dateTime = DateUtil.parse(name); + long between = DateUtil.between(dateTime, DateTime.now(), DateUnit.DAY); + // 保留一天以内的 + return between > 1; + } catch (DateException dateException) { + return false; + } + }); + Optional.ofNullable(files).ifPresent(files1 -> { + for (File file : files1) { + CommandUtil.systemFastDel(file); + } + }); + } + } + + /** + * 尝试开启项目 + */ + private void autoStartProject() { + List allProject = projectInfoService.list(); + if (CollUtil.isEmpty(allProject)) { + return; + } + List startList = allProject.stream() + .filter(nodeProjectInfoModel -> nodeProjectInfoModel.getAutoStart() != null && nodeProjectInfoModel.getAutoStart()) + .collect(Collectors.toList()); + ThreadUtil.execute(() -> { + for (NodeProjectInfoModel nodeProjectInfoModel : startList) { + try { + if (!projectCommander.isRun(nodeProjectInfoModel)) { + projectCommander.execCommand(ConsoleCommandOp.start, nodeProjectInfoModel); + } + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.auto_start_project_failed.c7b5"), 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(I18nMessageUtil.get("i18n.auto_migrate_exist_backup_logs.dc33"), logBack.getAbsolutePath(), resolveLogBack); + FileUtil.moveContent(logBack, resolveLogBack, true); + FileUtil.del(logBack); + } + if (FileUtil.isFile(log1)) { + if (projectCommander.isRun(nodeProjectInfoModel)) { + log.warn(I18nMessageUtil.get("i18n.old_version_project_logs_exist_while_running.75ab"), nodeProjectInfoModel.getName(), log1); + } else { + File resolveLogBack = projectInfoService.resolveLogBack(nodeProjectInfoModel); + FileUtil.mkdir(resolveLogBack); + log.info(I18nMessageUtil.get("i18n.auto_migrate_exist_logs.c169"), log1, resolveLogBack); + FileUtil.move(FileUtil.file(log1), resolveLogBack, true); + } + } + }); + } + + + /** + * 自动推送插件端信息到服务端 + * + * @param url 服务端url + */ + public void autoPushToServer(String url) { + url = StrUtil.removeSuffix(url, CharPool.SINGLE_QUOTE + ""); + url = StrUtil.removePrefix(url, CharPool.SINGLE_QUOTE + ""); + UrlBuilder urlBuilder = UrlBuilder.ofHttp(url); + String networkName = (String) urlBuilder.getQuery().get("networkName"); + // + LinkedHashSet localAddressList = NetUtil.localAddressList(networkInterface -> StrUtil.isEmpty(networkName) || StrUtil.equals(networkName, networkInterface.getName()), address -> { + // 非loopback地址,指127.*.*.*的地址 + return !address.isLoopbackAddress() + // 需为IPV4地址 + && address instanceof Inet4Address; + }); + if (StrUtil.isNotEmpty(networkName) && CollUtil.isEmpty(localAddressList)) { + log.warn("No usable IP found by NIC name,{}", networkName); + } + Set ips = localAddressList.stream().map(InetAddress::getHostAddress).filter(StrUtil::isNotEmpty).collect(Collectors.toSet()); + urlBuilder.addQuery("ips", CollUtil.join(ips, StrUtil.COMMA)); + urlBuilder.addQuery("loginName", agentAuthorize.getAgentName()); + urlBuilder.addQuery("loginPwd", agentAuthorize.getAgentPwd()); + int port = jpomApplication.getPort(); + urlBuilder.addQuery("port", port + ""); + // + String build = urlBuilder.build(); + try (HttpResponse execute = HttpUtil.createGet(build, true).execute()) { + String body = execute.body(); + log.info("推送注册结果:{}", body); + } + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + this.startAutoBackLog(); + this.autoStartProject(); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/util/FileSearchUtil.java b/modules/agent/src/main/java/org/dromara/jpom/util/FileSearchUtil.java new file mode 100644 index 0000000000..491e67f54b --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/util/FileSearchUtil.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.LineNumberReader; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +/** + * 文件搜索工具 + * + * @author bwcx_jzy + * @since 2022/5/15 + */ +public class FileSearchUtil { + + /** + * @param file 文件 + * @param charset 编码格式 + * @param searchKey 搜索关键词 + * @param cacheBeforeCount 关键词前多少行 + * @param afterCount 关键词后多少行 + * @param head 搜索文件头多少行 + * @param tailCount 文件后多少行 + * @param first 是否从头开始读取 + * @param consumer 回调 + * @return 结果描述 + * @throws IOException io + */ + public static String searchList(File file, Charset charset, + String searchKey, + int cacheBeforeCount, int afterCount, + int head, int tailCount, + boolean first, Consumer consumer) throws IOException { + + int[] calculate = FileSearchUtil.calculate(head, tailCount, first); + Collection strings; + if (calculate.length == 1) { + strings = FileSearchUtil.readLastLine(file, charset, calculate[0]); + } else { + strings = FileSearchUtil.readRangeLine(file, charset, calculate); + } + int showLine = searchList(strings, searchKey, cacheBeforeCount, afterCount, consumer); + return StrUtil.format(I18nMessageUtil.get("i18n.search_result_display.d2c3"), CollUtil.size(strings), showLine); + } + + private static int searchList(Collection strings, String searchKey, int beforeCount, int afterCount, Consumer consumer) { + AtomicInteger hitIndex = new AtomicInteger(0); + LimitQueue beforeQueue = new LimitQueue<>(beforeCount); + List cacheLineNum = new LinkedList<>(); + strings.forEach(tuple -> { + String s = tuple.get(1); + Integer index = tuple.get(0); + // System.out.println(s); + if (StrUtil.isEmpty(searchKey) || StrUtil.containsIgnoreCase(s, searchKey) || ReUtil.isMatch(searchKey, s)) { + // 先输出之前的 + for (Tuple before : beforeQueue) { + checkEchoCache(cacheLineNum, before, consumer); + } + checkEchoCache(cacheLineNum, tuple, consumer); + hitIndex.set(index); + } + // 是否需要输出后面的内容 + int i = hitIndex.get(); + if (i > 0 && index > i && index <= i + afterCount) { + checkEchoCache(cacheLineNum, tuple, consumer); + } + if (beforeCount > 0) { + // + beforeQueue.offerFirst(tuple); + } + }); + return CollUtil.size(cacheLineNum); + } + + private static void checkEchoCache(List cacheLineNum, Tuple tuple, Consumer consumer) { + int index = tuple.get(0); + if (cacheLineNum.contains(index)) { + return; + } + consumer.accept(tuple); + cacheLineNum.add(index); + } + + public static Collection readLastLine(File file, Charset charset, int line) throws IOException { + BufferedReader reader = FileUtil.getReader(file, charset); + LineNumberReader lineNumberReader = new LineNumberReader(reader); + LimitQueue limitQueue = new LimitQueue<>(line); + while (true) { + String readLine = lineNumberReader.readLine(); + if (readLine == null) { + break; + } + limitQueue.add(new Tuple(lineNumberReader.getLineNumber(), readLine)); + } + return limitQueue; + } + + public static Collection readRangeLine(File file, Charset charset, int[] range) throws IOException { + BufferedReader reader = FileUtil.getReader(file, charset); + LineNumberReader lineNumberReader = new LineNumberReader(reader); + List list = new LinkedList<>(); + while (true) { + String readLine = lineNumberReader.readLine(); + if (readLine == null) { + break; + } + int lineNumber = lineNumberReader.getLineNumber(); + if (lineNumber >= range[0] && lineNumber <= range[1]) { + list.add(new Tuple(lineNumber, readLine)); + } + if (lineNumber > range[1]) { + break; + } + } + return list; + } + + /** + * 计算读取文件行数相关 + * + * @param head 从文件头开始读取 + * @param tailLine 读最后几乎 + * @param first 是否从头开始读取 + * @return int + */ + public static int[] calculate(int head, int tailLine, boolean first) { + if (head > 0) { + return first ? new int[]{Math.min(tailLine, head), head} : new int[]{Math.max(head - tailLine, 1), head}; + } + return first ? new int[]{tailLine, Integer.MAX_VALUE} : new int[]{tailLine}; + } + +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/util/KeyLock.java b/modules/agent/src/main/java/org/dromara/jpom/util/KeyLock.java new file mode 100644 index 0000000000..e606848f1c --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/util/KeyLock.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.map.SafeConcurrentHashMap; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 根据执行KEY 多线程锁 + * + * @author bwcx_jzy + * @since 2018/8/24. + */ +public class KeyLock { + /** + * 保存所有锁定的KEY及其信号量 + */ + private final ConcurrentMap map = new SafeConcurrentHashMap<>(); + + /** + * 获取锁的数量 + * + * @return key总数 + */ + public int getLockKeyCount() { + return map.size(); + } + + /** + * 根据key 获取等待的线程数 + * + * @param k k + * @return 总数 + */ + public int getLockCount(K k) { + LockInfo lockInfo = map.get(k); + return lockInfo == null ? 0 : lockInfo.getLockCount(); + } + + + /** + * 释放key,唤醒其他等待此key的线程 + * + * @param key key + */ + public void unlock(K key) { + if (key == null) { + return; + } + LockInfo lockInfo = map.get(key); + if (lockInfo == null) { + return; + } + // 释放许可 + lockInfo.release(); + if (lockInfo.getLockCount() <= 0) { + // 清除锁 + map.remove(key); + } + } + + /** + * 锁定key,其他等待此key的线程将进入等待,直到调用{@link KeyLock#unlock} + * 使用hashcode和equals来判断key是否相同,因此key必须实现{@link #hashCode()}和 + * {@link #equals(Object)}方法 + * + * @param key key + */ + public void lock(K key) { + if (key == null) { + return; + } + LockInfo lockInfo = map.computeIfAbsent(key, k -> new LockInfo()); + lockInfo.lock(); + } + + /** + * 锁定多个key + * 建议在调用此方法前先对keys进行排序,使用相同的锁定顺序,防止死锁发生 + * + * @param keys keys + */ + public void lock(K[] keys) { + if (keys == null) { + return; + } + for (K key : keys) { + lock(key); + } + } + + /** + * 释放多个key + * + * @param keys 多个keys + */ + public void unlock(K[] keys) { + if (keys == null) { + return; + } + for (K key : keys) { + unlock(key); + } + } + + /** + * 锁的信息 + */ + private static class LockInfo { + private final Semaphore semaphore; + private final AtomicInteger lockCount = new AtomicInteger(0); + + private LockInfo() { + this.semaphore = new Semaphore(1); + } + + private void lock() { + lockCount.getAndIncrement(); + semaphore.acquireUninterruptibly(); + } + + private void release() { + semaphore.release(); + lockCount.getAndDecrement(); + } + + private int getLockCount() { + return lockCount.get(); + } + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/util/OshiUtils.java b/modules/agent/src/main/java/org/dromara/jpom/util/OshiUtils.java new file mode 100644 index 0000000000..f38b10240e --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/util/OshiUtils.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.oshi.CpuInfo; +import cn.hutool.system.oshi.OshiUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import org.dromara.jpom.configuration.MonitorConfig; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import oshi.hardware.*; +import oshi.software.os.FileSystem; +import oshi.software.os.NetworkParams; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.GlobalConfig; +import oshi.util.Util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author bwcx_jzy + * @since 2023/2/12 + */ +public class OshiUtils { + + public static final int NET_STAT_SLEEP = 1000; + public static final int CPU_STAT_SLEEP = 1000; + + private static final PathMatcher pathMatcher = new AntPathMatcher(); + + static { + // 解决Oshi获取CPU使用率与Windows任务管理器显示不匹配的问题 + GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, true); + } + + /** + * 获取系统信息 + * + * @return json + */ + public static JSONObject getSystemInfo() { + JSONObject jsonObject = new JSONObject(); + OperatingSystem os = OshiUtil.getOs(); + jsonObject.put("systemUptime", os.getSystemUptime()); + String manufacturer = os.getManufacturer(); + String family = os.getFamily(); + OperatingSystem.OSVersionInfo versionInfo = os.getVersionInfo(); + String versionStr = versionInfo.toString(); + jsonObject.put("osVersion", StrUtil.format("{} {} {}", manufacturer, family, versionStr)); + NetworkParams networkParams = os.getNetworkParams(); + String hostName = networkParams.getHostName(); + jsonObject.put("hostName", hostName); + // + HardwareAbstractionLayer hardware = OshiUtil.getHardware(); + ComputerSystem computerSystem = hardware.getComputerSystem(); + jsonObject.put("hardwareVersion", StrUtil.format("{} {}", computerSystem.getManufacturer(), computerSystem.getModel())); + List networks = hardware.getNetworkIFs(); + List collect = networks.stream() + .flatMap((Function>) network -> Arrays.stream(network.getIPv4addr())) + .collect(Collectors.toList()); + jsonObject.put("hostIpv4s", collect); + // + CentralProcessor processor = OshiUtil.getProcessor(); + CentralProcessor.ProcessorIdentifier identifier = processor.getProcessorIdentifier(); + jsonObject.put("osCpuIdentifierName", identifier.getName()); + jsonObject.put("osCpuCores", processor.getLogicalProcessorCount()); + GlobalMemory globalMemory = OshiUtil.getMemory(); + jsonObject.put("osMoneyTotal", globalMemory.getTotal()); + VirtualMemory virtualMemory = globalMemory.getVirtualMemory(); + jsonObject.put("osSwapTotal", virtualMemory.getSwapTotal()); + jsonObject.put("osVirtualMax", virtualMemory.getVirtualMax()); + double[] systemLoadAverage = processor.getSystemLoadAverage(3); + jsonObject.put("osLoadAverage", systemLoadAverage); + FileSystem fileSystem = os.getFileSystem(); + List fileStores = fileSystem.getFileStores(); + long total = fileStores.stream().mapToLong(OSFileStore::getTotalSpace).sum(); + jsonObject.put("osFileStoreTotal", total); + // + return jsonObject; + } + + /** + * 获取信息简单的基础状态信息 + * + * @return json + */ + public static JSONObject getSimpleInfo(MonitorConfig monitorConfig) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("time", SystemClock.now()); + CpuInfo cpuInfo = OshiUtil.getCpuInfo(CPU_STAT_SLEEP); + jsonObject.put("cpu", cpuInfo.getUsed()); + // + GlobalMemory globalMemory = OshiUtil.getMemory(); + // 在不使用交换空间的情况下,启动一个新的应用最大可用内存的大小, + // 计算方式:MemFree+Active(file)+Inactive(file)-(watermark+min(watermark,Active(file)+Inactive(file)/2)) + // https://langzi989.github.io/2016/12/19/%E9%80%9A%E8%BF%87-proc-meminfo%E5%AE%9E%E6%97%B6%E8%8E%B7%E5%8F%96%E7%B3%BB%E7%BB%9F%E5%86%85%E5%AD%98%E4%BD%BF%E7%94%A8%E6%83%85%E5%86%B5/ + // https://www.cnblogs.com/aalan/p/17026258.html + // https://lotabout.me/2021/Linux-Available-Memory/ + jsonObject.put("memory", NumberUtil.div(globalMemory.getTotal() - globalMemory.getAvailable(), globalMemory.getTotal(), 2) * 100); + VirtualMemory virtualMemory = globalMemory.getVirtualMemory(); + long swapTotal = virtualMemory.getSwapTotal(); + if (swapTotal > 0) { + jsonObject.put("swapMemory", NumberUtil.div(virtualMemory.getSwapUsed(), swapTotal, 2) * 100); + } + long virtualMax = virtualMemory.getVirtualMax(); + if (virtualMax > 0) { + jsonObject.put("virtualMemory", NumberUtil.div(virtualMemory.getVirtualInUse(), virtualMax, 2) * 100); + } + // + FileSystem fileSystem = OshiUtil.getOs().getFileSystem(); + List fileStores = fileSystem.getFileStores(); + long total = 0, used = 0; + for (OSFileStore fs : fileStores) { + total += fs.getTotalSpace(); + used += (fs.getTotalSpace() - fs.getUsableSpace()); + } + jsonObject.put("disk", NumberUtil.div(used, total, 2) * 100); + // + + MonitorConfig.NetworkConfig networkConfig1 = Optional.ofNullable(monitorConfig) + .map(MonitorConfig::getNetwork).orElse(null); + NetIoInfo startNetInfo = getNetInfo(networkConfig1); + //暂停1秒 + Util.sleep(NET_STAT_SLEEP); + NetIoInfo endNetInfo = getNetInfo(networkConfig1); + jsonObject.put("netTxBytes", endNetInfo.getTxbyt() - startNetInfo.getTxbyt()); + jsonObject.put("netRxBytes", endNetInfo.getRxbyt() - startNetInfo.getRxbyt()); + jsonObject.put("monitorIfsNames", endNetInfo.getIfsNames()); + return jsonObject; + } + + private static NetIoInfo getNetInfo(MonitorConfig.NetworkConfig networkConfig) { + // + List statExcludeNames = Optional.ofNullable(networkConfig) + .map(MonitorConfig.NetworkConfig::getStatExcludeNames) + .map(s -> StrUtil.splitTrim(s, StrUtil.COMMA)) + .orElse(CollUtil.newArrayList()); + List statContainsOnlyNames = Optional.ofNullable(networkConfig) + .map(MonitorConfig.NetworkConfig::getStatContainsOnlyNames) + .map(s -> StrUtil.splitTrim(s, StrUtil.COMMA)) + .orElse(CollUtil.newArrayList()); + long rxBytesBegin = 0; + long txBytesBegin = 0; + long rxPacketsBegin = 0; + long txPacketsBegin = 0; + List listBegin = OshiUtil.getNetworkIFs(); + StringBuilder ifsNames = new StringBuilder(StrUtil.EMPTY); + if (listBegin != null) { + listBegin = listBegin.stream() + .filter(networkIF -> CollUtil.isEmpty(statExcludeNames) || !isMatch(statExcludeNames, networkIF.getName())) + .filter(networkIF -> CollUtil.isEmpty(statContainsOnlyNames) || isMatch(statContainsOnlyNames, networkIF.getName())) + .collect(Collectors.toList()); + for (int i = 0; i < listBegin.size(); i++) { + NetworkIF net = listBegin.get(i); + rxBytesBegin += net.getBytesRecv(); + txBytesBegin += net.getBytesSent(); + rxPacketsBegin += net.getPacketsRecv(); + txPacketsBegin += net.getPacketsSent(); + ifsNames.append(i == 0 ? net.getName() : "," + net.getName()); + } + } + NetIoInfo netIoInfo = new NetIoInfo(); + netIoInfo.setRxbyt(rxBytesBegin); + netIoInfo.setTxbyt(txBytesBegin); + netIoInfo.setRxpck(rxPacketsBegin); + netIoInfo.setTxpck(txPacketsBegin); + netIoInfo.setIfsNames(ifsNames.toString()); + return netIoInfo; + } + + /** + * 查询进程列表 + * + * @param name 进程名称 + * @param count 数量 + * @return list + */ + public static List getProcesses(String name, int count) { + OperatingSystem operatingSystem = OshiUtil.getOs(); + return operatingSystem.getProcesses( + osProcess -> StrUtil.equalsIgnoreCase(osProcess.getName(), name), + OperatingSystem.ProcessSorting.CPU_DESC, + count) + .stream() + .map(osProcess -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("processCpuLoadCumulative", osProcess.getProcessCpuLoadCumulative()); + jsonObject.put("hardOpenFileLimit", osProcess.getHardOpenFileLimit()); + jsonObject.put("softOpenFileLimit", osProcess.getSoftOpenFileLimit()); + jsonObject.put("openFiles", osProcess.getOpenFiles()); + jsonObject.put("processId", osProcess.getProcessID()); + jsonObject.put("bytesWritten", osProcess.getBytesWritten()); + jsonObject.put("bytesRead", osProcess.getBytesRead()); + jsonObject.put("startTime", osProcess.getStartTime()); + jsonObject.put("upTime", osProcess.getUpTime()); + jsonObject.put("userTime", osProcess.getUpTime()); + jsonObject.put("user", osProcess.getUser()); + jsonObject.put("state", osProcess.getState()); + jsonObject.put("name", osProcess.getName()); + jsonObject.put("virtualSize", osProcess.getVirtualSize()); + jsonObject.put("commandLine", osProcess.getCommandLine()); + jsonObject.put("priority", osProcess.getPriority()); + jsonObject.put("path", osProcess.getPath()); + jsonObject.put("residentSetSize", osProcess.getResidentSetSize()); + return jsonObject; + }) + .collect(Collectors.toList()); + } + + /** + * 获取文件系统信息 + * + * @return list + */ + public static List fileStores() { + FileSystem fileSystem = OshiUtil.getOs().getFileSystem(); + List fileStores = fileSystem.getFileStores(); + return fileStores.stream().map(osFileStore -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", osFileStore.getName()); + jsonObject.put("volume", osFileStore.getVolume()); + jsonObject.put("logicalVolume", osFileStore.getLogicalVolume()); + jsonObject.put("mount", osFileStore.getMount()); + jsonObject.put("label", osFileStore.getLabel()); + jsonObject.put("description", osFileStore.getDescription()); + jsonObject.put("type", osFileStore.getType()); + jsonObject.put("options", osFileStore.getOptions()); + jsonObject.put("uuid", osFileStore.getUUID()); + jsonObject.put("freeInodes", osFileStore.getFreeInodes()); + jsonObject.put("totalInodes", osFileStore.getTotalInodes()); + jsonObject.put("freeSpace", osFileStore.getFreeSpace()); + jsonObject.put("totalSpace", osFileStore.getTotalSpace()); + jsonObject.put("usableSpace", osFileStore.getUsableSpace()); + return jsonObject; + }).collect(Collectors.toList()); + } + + /** + * 硬盘存储 + * + * @return list + */ + public static List diskStores() { + HardwareAbstractionLayer hardware = OshiUtil.getHardware(); + List diskStores = hardware.getDiskStores(); + + return diskStores.stream() + .map(disk -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", disk.getName()); + jsonObject.put("model", disk.getModel()); + jsonObject.put("serial", disk.getSerial()); + jsonObject.put("size", disk.getSize()); + jsonObject.put("readBytes", disk.getReadBytes()); + jsonObject.put("reads", disk.getReads()); + jsonObject.put("writeBytes", disk.getWriteBytes()); + jsonObject.put("writes", disk.getWrites()); + jsonObject.put("currentQueueLength", disk.getCurrentQueueLength()); + jsonObject.put("transferTime", disk.getTransferTime()); + jsonObject.put("timeStamp", disk.getTimeStamp()); + + List partitions = disk.getPartitions(); + List partition = Optional.ofNullable(partitions) + .map(hwPartitions -> hwPartitions.stream() + .map(hwPartition -> { + JSONObject object = new JSONObject(); + object.put("identification", hwPartition.getIdentification()); + object.put("name", hwPartition.getName()); + object.put("type", hwPartition.getType()); + object.put("major", hwPartition.getMajor()); + object.put("minor", hwPartition.getMinor()); + object.put("size", hwPartition.getSize()); + object.put("mountPoint", hwPartition.getMountPoint()); + object.put("uuid", hwPartition.getUuid()); + return object; + }).collect(Collectors.toList())) + .orElse(new ArrayList<>()); + jsonObject.put("partition", partition); + return jsonObject; + }) + .collect(Collectors.toList()); + } + + public static List networkInterfaces() { + HardwareAbstractionLayer hardware = OshiUtil.getHardware(); + List networkIfs = hardware.getNetworkIFs(true); + return Optional.ofNullable(networkIfs) + .map(networkIfs2 -> networkIfs2.stream() + .map(networkIf -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", networkIf.getName()); + jsonObject.put("displayName", networkIf.getDisplayName()); + jsonObject.put("index", networkIf.getIndex()); + jsonObject.put("macaddr", networkIf.getMacaddr()); + jsonObject.put("timeStamp", networkIf.getTimeStamp()); + jsonObject.put("tnDrops", networkIf.getInDrops()); + jsonObject.put("inErrors", networkIf.getInErrors()); + jsonObject.put("bytesRecv", networkIf.getBytesRecv()); + jsonObject.put("bytesSent", networkIf.getBytesSent()); + jsonObject.put("packetsRecv", networkIf.getPacketsRecv()); + jsonObject.put("packetsSent", networkIf.getPacketsSent()); + jsonObject.put("collisions", networkIf.getCollisions()); + jsonObject.put("ifAlias", networkIf.getIfAlias()); + jsonObject.put("ifType", networkIf.getIfType()); + jsonObject.put("ifOperStatus", networkIf.getIfOperStatus()); + jsonObject.put("ipv4addr", networkIf.getIPv4addr()); + jsonObject.put("ipv6addr", networkIf.getIPv6addr()); + jsonObject.put("mtu", networkIf.getMTU()); + jsonObject.put("outErrors", networkIf.getOutErrors()); + jsonObject.put("speed", networkIf.getSpeed()); + jsonObject.put("subnetMasks", networkIf.getSubnetMasks()); + jsonObject.put("prefixLengths", networkIf.getPrefixLengths()); + jsonObject.put("knownVmMacAddr", networkIf.isKnownVmMacAddr()); + return jsonObject; + }).collect(Collectors.toList())) + .orElse(new ArrayList<>()); + } + + public static boolean isMatch(List list, String keyword) { + for (String pattern : list) { + if (pathMatcher.match(pattern, keyword)) { + return true; + } + } + return false; + } + + @Data + private static class NetIoInfo { + /** + * 接收的数据包,rxpck/s + */ + private Long rxpck; + + /** + * 发送的数据包,txpck/s + */ + private Long txpck; + + /** + * 接收的KB数,rxbit/s + */ + private Long rxbyt; + + /** + * 发送的KB数,txbit/s + */ + private Long txbyt; + /** + * ifaceNames + */ + private String ifsNames; + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/util/SocketSessionUtil.java b/modules/agent/src/main/java/org/dromara/jpom/util/SocketSessionUtil.java new file mode 100644 index 0000000000..c594d06dbe --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/util/SocketSessionUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import javax.websocket.Session; +import java.io.IOException; + +/** + * socket 会话对象 + * + * @author bwcx_jzy + * @since 2018/9/29 + */ +@Slf4j +public class SocketSessionUtil { + /** + * 锁 + */ + private static final KeyLock LOCK = new KeyLock<>(); + /** + * 错误尝试次数 + */ + private static final int ERROR_TRY_COUNT = 10; + + /** + * 发送消息 + * + * @param session 会话对象 + * @param msg 消息 + * @throws IOException 异常 + */ + public static void send(final Session session, String msg) throws IOException { + if (StrUtil.isEmpty(msg)) { + return; + } + Assert.state(session.isOpen(), "session close "); + try { + LOCK.lock(session.getId()); + IOException exception = null; + int tryCount = 0; + do { + tryCount++; + if (exception != null) { + // 上一次有异常、休眠 500 + ThreadUtil.sleep(500); + } + try { + session.getBasicRemote().sendText(msg); + exception = null; + break; + } catch (IOException e) { + log.error("{}{}", I18nMessageUtil.get("i18n.send_message_failure_prefix.6f8c"), tryCount, e); + exception = e; + } + } while (tryCount <= ERROR_TRY_COUNT); + if (exception != null) { + throw exception; + } + } finally { + LOCK.unlock(session.getId()); + } + } +} diff --git a/modules/agent/src/main/resources/application.yml b/modules/agent/src/main/resources/application.yml index 3bc5850bc3..0205d95a42 100644 --- a/modules/agent/src/main/resources/application.yml +++ b/modules/agent/src/main/resources/application.yml @@ -1,26 +1,88 @@ -#运行端口号 +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + # jpom 数据存储路径, 如果调试模式运行默认路径为【${user.home}/jpom/】,安装运行默认为jar包文件的父级 + path: + authorize: + # agent 端管理账号,必填。 + agent-name: jpomAgent + # agent 端管理密码,非必填,如果为空Jpom 会自动生成一串随机字符串当密码 + agent-pwd: + project: + # 停止、启动项目(项目状态检测)等待的时长 单位秒 + status-wait-time: 10 + # 项目文件备份保留个数,大于 0 才会备份 + file-backup-count: 1 + # 限制备份指定文件后缀(支持正则) + file-backup-suffix: [ '.jar','.html','^.+\\.(?i)(txt)$' ] + # 项目状态检测间隔时间 单位毫秒,最小为1毫秒 + status-detection-interval: 500 + log: + # 检测控制台日志周期,防止日志文件过大,目前暂只支持linux 不停服备份 + auto-backup-console-cron: 0 0/10 * * * ? + # 控制台日志文件保留大小 + auto-backup-size: 50MB + # 是否自动将控制台日志文件备份 + auto-backup-to-file: true + # 保存天数 配置错误或者没有,默认是7天 + save-days: 7 + # 日志文件的编码格式 (windows 的默认值为 GBK,其他系统默认均为 UTF8) + file-charset: + # 查看日志时初始读取最后多少行(默认10,0不读取) + init-read-line: 10 + system: + # cron 定时器是否开启匹配秒 + timer-match-second: false + # 旧包文件保留个数 + old-jars-count: 2 + # Check the url for the new version + remote-version-url: + # 系统日志编码格式 + log-charset: UTF-8 + # 控制台编码格式 + console-charset: + # 在线升级允许降级-操作 + allowed-downgrade: false + # 执行系统主要命名是否填充 sudo(sudo xxx) 使用前提需要配置 sudo 免密 + command-use-sudo: false + # 系统语言:zh-CN、en-US + lang: zh-CN + monitor: + network: + # 监控网络流量只统计对应的网卡,多个使用逗号分隔. 支持模糊匹配 + stat-contains-only-names: en* + # 监控网络流量排除对应的网卡,多个使用逗号分隔. 支持模糊匹配 + stat-exclude-names: lo* server: + #运行端口号 port: 2123 + servlet: + encoding: + charset: UTF-8 + force: true + enabled: true + forceRequest: true + forceResponse: true + tomcat: + uri-encoding: UTF-8 +spring: servlet: session: timeout: 1H cookie: name: JPOMID-AGENT -spring: - application: - name: jpomAgent - profiles: - active: dev - servlet: multipart: - max-request-size: 2GB - max-file-size: 1GB -banner: - msg: Jpom-Agent管理系统启动中 -# 启动完成自动初始化指定包 -preload: - packageName: io.jpom.system.init -# 强制去掉空格 -request: - trimAll: true - urlDecode: true + # 上传文件大小限制 12KB -- parses as 12 kilobytes 5MB -- parses as 5 megabytes 20 -- parses as 20 kilobytes + max-request-size: 20MB + max-file-size: 5MB + mvc: + throw-exception-if-no-handler-found: true + log-request-details: true diff --git a/modules/agent/src/main/resources/bin/extConfig.yml b/modules/agent/src/main/resources/bin/extConfig.yml deleted file mode 100644 index c9089605e4..0000000000 --- a/modules/agent/src/main/resources/bin/extConfig.yml +++ /dev/null @@ -1,42 +0,0 @@ -jpom: - # jpom 数据存储路径, 如果调试模式运行默认路径为【${user.home}/jpom/】,安装运行默认为jar包文件的父级 - path: - authorize: - # agent 端管理账号,必填。 - agentName: jpomAgent - # agent 端管理密码,非必填,如果为空Jpom 会自动生成一串随机字符串当密码 - agentPwd: - agent: - # 设置插件端id,配置自动注册服务端需要 - id: - # 当前节点插件端可以访问的url ,如果不设置将使用http://+本地IP+端口 - url: - server: - # 设置服务端的url - url: - # 服务器接口请求token - token: -whitelistDirectory: - # 白名单目录是否验证包含关系 - checkStartsWith: true -log: - # 自动备份控制台日志,防止日志文件过大,目前暂只支持linux 不停服备份 - autoBackConsoleCron: 0 0/10 * * * ? - # 当文件多大时自动备份 - autoBackSize: 50MB - # 保存天数 配置错误或者没有,默认是7天 - saveDays: 7 - # 日志文件的编码格式,如果没有指定就自动识别,自动识别可能出现不准确的情况 - fileCharset: - # 查看日志时初始读取最后多少行(默认10,0不读取) - intiReadLine: 10 -consoleLog: - # 是否记录接口请求日志 - reqXss: true - # 是否记录接口响应日志 - reqResponse: true -project: - # 是否禁用 使用jmx获取项目状态 - disableVirtualMachine: false - # 停止项目等待的时长 单位秒 - stopWaitTime: 10 diff --git a/modules/agent/src/main/resources/bin/jpomAgent.zip b/modules/agent/src/main/resources/bin/jpomAgent.zip deleted file mode 100644 index fd6420e4cd..0000000000 Binary files a/modules/agent/src/main/resources/bin/jpomAgent.zip and /dev/null differ diff --git a/modules/agent/src/main/resources/config_default/application.yml b/modules/agent/src/main/resources/config_default/application.yml new file mode 100644 index 0000000000..2aa0509349 --- /dev/null +++ b/modules/agent/src/main/resources/config_default/application.yml @@ -0,0 +1,86 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + # jpom 数据存储路径, 如果调试模式运行默认路径为【${user.home}/jpom/】,安装运行默认为jar包文件的父级 + path: + authorize: + # agent 端管理账号,必填。 + agent-name: jpomAgent + # agent 端管理密码,非必填,如果为空Jpom 会自动生成一串随机字符串当密码 + agent-pwd: + project: + # 停止、启动项目(项目状态检测)等待的时长 单位秒 + status-wait-time: 10 + # 项目文件备份保留个数,大于 0 才会备份 + file-backup-count: 0 + # 限制备份指定文件后缀(支持正则) + file-backup-suffix: [ '.jar','.html','^.+\\.(?i)(txt)$' ] + # 项目状态检测间隔时间 单位毫秒,最小为1毫秒 + status-detection-interval: 500 + log: + # 检测控制台日志周期,防止日志文件过大,目前暂只支持linux 不停服备份 + auto-backup-console-cron: 0 0/10 * * * ? + # 控制台日志文件保留大小 + auto-backup-size: 50MB + # 是否自动将控制台日志文件备份 + auto-backup-to-file: true + # 保存天数 配置错误或者没有,默认是7天 + save-days: 7 + # 日志文件的编码格式 (windows 的默认值为 GBK,其他系统默认均为 UTF8) + file-charset: + # 查看日志时初始读取最后多少行(默认10,0不读取) + init-read-line: 10 + system: + # cron 定时器是否开启匹配秒 + timer-match-second: false + # 旧包文件保留个数 + old-jars-count: 2 + # 系统日志编码格式 + log-charset: UTF-8 + # 控制台编码格式 + console-charset: + # 在线升级允许降级-操作 + allowed-downgrade: false + # 执行系统主要命名是否填充 sudo(sudo xxx) 使用前提需要配置 sudo 免密 + command-use-sudo: false + # 系统语言:zh-CN、en-US + lang: zh-CN + monitor: + network: + # 监控网络流量只统计对应的网卡,多个使用逗号分隔 + stat-contains-only-names: + # 监控网络流量排除对应的网卡,多个使用逗号分隔 + stat-exclude-names: +server: + #运行端口号 + port: 2123 + servlet: + encoding: + charset: UTF-8 + force: true + enabled: true + forceRequest: true + forceResponse: true + tomcat: + uri-encoding: UTF-8 +spring: + servlet: + session: + timeout: 1H + cookie: + name: JPOMID-AGENT + multipart: + # 上传文件大小限制 12KB -- parses as 12 kilobytes 5MB -- parses as 5 megabytes 20 -- parses as 20 kilobytes + max-request-size: 20MB + max-file-size: 5MB + mvc: + throw-exception-if-no-handler-found: true + log-request-details: true diff --git a/modules/agent/src/main/resources/config_default/logback.xml b/modules/agent/src/main/resources/config_default/logback.xml new file mode 100644 index 0000000000..abd26051e1 --- /dev/null +++ b/modules/agent/src/main/resources/config_default/logback.xml @@ -0,0 +1,73 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + destination + agent + + + + ${logPath}/${destination}.log + + + ${logPath}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log + + + 512MB + + 60 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/agent/src/main/resources/logback.xml b/modules/agent/src/main/resources/logback.xml new file mode 100644 index 0000000000..06c11fcb2a --- /dev/null +++ b/modules/agent/src/main/resources/logback.xml @@ -0,0 +1,76 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + destination + agent + + + + ${logPath}/${destination}.log + + + ${logPath}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log + + + 512MB + + 60 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/agent/src/test/java/Code.java b/modules/agent/src/test/java/Code.java index 62771494df..91b360b382 100644 --- a/modules/agent/src/test/java/Code.java +++ b/modules/agent/src/test/java/Code.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import org.junit.Test; @@ -26,7 +13,7 @@ /** * @author bwcx_jzy - * @date 2019/5/30 + * @since 2019/5/30 **/ public class Code { public static void main(String[] args) { diff --git a/modules/agent/src/test/java/DTest.java b/modules/agent/src/test/java/DTest.java index 8dcc73e316..5b4a560f4b 100644 --- a/modules/agent/src/test/java/DTest.java +++ b/modules/agent/src/test/java/DTest.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ //import cn.hutool.core.io.FileUtil; //import org.apache.commons.codec.binary.Base64; diff --git a/modules/agent/src/test/java/FileUtil.java b/modules/agent/src/test/java/FileUtil.java index 83dbaa411b..4fc2b9668a 100644 --- a/modules/agent/src/test/java/FileUtil.java +++ b/modules/agent/src/test/java/FileUtil.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ //import cn.hutool.core.util.StrUtil; //import org.apache.commons.compress.archivers.tar.TarArchiveEntry; @@ -265,4 +252,4 @@ // //System.out.println(unGZ("F:\\fileupload\\test.xml.gz", "F:\\fileupload\\")); // //System.out.println(unTarGZ("F:\\fileupload\\all.tar.gz", "F:\\fileupload\\")); // } -//} \ No newline at end of file +//} diff --git a/modules/agent/src/test/java/GetTomcatPort.java b/modules/agent/src/test/java/GetTomcatPort.java index 643453af97..2580583ed8 100644 --- a/modules/agent/src/test/java/GetTomcatPort.java +++ b/modules/agent/src/test/java/GetTomcatPort.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.XmlUtil; @@ -29,7 +16,7 @@ /** * @author bwcx_jzy - * @date 2019/5/30 + * @since 2019/5/30 **/ public class GetTomcatPort { public static void main(String[] args) { diff --git a/modules/agent/src/test/java/TestBigFileRead.java b/modules/agent/src/test/java/TestBigFileRead.java new file mode 100644 index 0000000000..db33745851 --- /dev/null +++ b/modules/agent/src/test/java/TestBigFileRead.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.io.file.FileMode; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.SystemUtil; +import org.dromara.jpom.util.FileSearchUtil; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.*; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Scanner; +import java.util.stream.IntStream; + +/** + * @author bwcx_jzy + * @since 2022/5/14 + */ +public class TestBigFileRead { + + private File testFile; + private Long startTime; + + @Before + public void before() { + testFile = FileUtil.file(SystemUtil.getUserInfo().getTempDir(), "jpom", "test-big-file.txt"); + startTime = System.currentTimeMillis(); + } + + @After + public void after() { + logMemory(); + long end = System.currentTimeMillis(); + System.out.println(DateUtil.formatBetween(end - startTime, BetweenFormatter.Level.MILLISECOND)); + } + + @Test + public void testWriter() { + + FileUtil.del(testFile); + System.out.println(FileUtil.getAbsolutePath(testFile)); + BufferedWriter bufferedWriter = FileUtil.getWriter(testFile, CharsetUtil.CHARSET_UTF_8, true); + int count = 1000000; + int len = (count + "").length(); + IntStream.range(0, count).forEach(value -> { + try { + + bufferedWriter.write(StrUtil.fillBefore((value + 1) + "", '0', len) + " => " + RandomUtil.randomString(2000)); + //新起一行 写数据 + bufferedWriter.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + IoUtil.close(bufferedWriter); + // + System.out.println(FileUtil.readableFileSize(testFile)); + + } + + private void logMemory() { + System.out.println(StrUtil.format("最大内存: {} 总内存: {}", FileUtil.readableFileSize(Runtime.getRuntime().maxMemory()), FileUtil.readableFileSize(Runtime.getRuntime().totalMemory()))); + System.out.println(StrUtil.format("空闲内存: {} 耗内存: {}", FileUtil.readableFileSize(Runtime.getRuntime().freeMemory()), FileUtil.readableFileSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()))); + } + + @Test + public void testJdkFiles() throws IOException { + Files.readAllLines(FileUtil.file(testFile).toPath()); + } + + @Test + public void testHutool() throws IOException { + FileUtil.readLines(testFile, CharsetUtil.CHARSET_UTF_8); + } + + @Test + public void testHutool2() throws IOException { + FileUtil.readLines(testFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line -> { + + }); + } + + @Test + public void testHutool3() throws IOException { + RandomAccessFile randomAccessFile = FileUtil.createRandomAccessFile(testFile, FileMode.rw); + FileUtil.readLines(randomAccessFile, CharsetUtil.CHARSET_UTF_8, line -> { + // System.out.println(line); + }); + } + + @Test + public void testTail() throws IOException { + FileUtil.tail(testFile, CharsetUtil.CHARSET_UTF_8, System.out::println); + } + + @Test + public void testLinNumber() throws IOException { + BufferedReader reader = FileUtil.getReader(testFile, CharsetUtil.CHARSET_UTF_8); + LineNumberReader lineNumberReader = new LineNumberReader(reader); + while (true) { + String readLine = lineNumberReader.readLine(); + if (readLine == null) { + System.out.println(lineNumberReader.getLineNumber()); + break; + } + } + } + + @Test + public void testScanner() throws Exception { + + try (FileInputStream inputStream = new FileInputStream(testFile); Scanner sc = new Scanner(inputStream, CharsetUtil.CHARSET_UTF_8.toString())) { + //一行一行读取数据 + while (sc.hasNextLine()) { + String line = sc.nextLine(); + //System.out.println(line); + } + if (sc.ioException() != null) { + throw sc.ioException(); + } + } + } + + @Test + public void testInput() { + try { + BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(testFile.toPath())); + BufferedReader in = new BufferedReader(new InputStreamReader(bis, CharsetUtil.CHARSET_UTF_8));//10M缓存 + + while (in.ready()) { + String line = in.readLine(); + + } + IoUtil.close(in); + + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + /** + * cat /private/var/folders/w0/gzmt450d1mq3j96ymfr_6rm40000gn/T/jpom/test-big-file.txt | tail -n 10 | grep -E '.*(0999996|0999995).*' -C1 + * + * @throws IOException io + */ + @Test + public void testLinNumberReadLast() throws IOException { + + + int cacheBeforeCount = 1; + int afterCount = 1; + String searchKey = ".*(0999996|0999995).*"; +// FileSearchUtil.searchList(strings, searchKey, cacheBeforeCount, afterCount, new Consumer() { +// @Override +// public void accept(Tuple objects) { +// System.out.println(objects.get(1) + ""); +// } +// }); + + FileSearchUtil.searchList(testFile, CharsetUtil.CHARSET_UTF_8, searchKey, + cacheBeforeCount, afterCount, 0, 10, false, + objects -> System.out.println(objects.get(1) + "")); + } + + + @Test + public void testLinNumberReadRange() throws IOException { + + int cacheBeforeCount = 1; + int afterCount = 1; + String searchKey = "abcdef"; + FileSearchUtil.searchList(testFile, CharsetUtil.CHARSET_UTF_8, searchKey, + cacheBeforeCount, afterCount, 0, 10, true, + objects -> System.out.println(objects.get(1) + "")); + } + + @Test + public void testCalculate() { + System.out.println(Arrays.toString(FileSearchUtil.calculate(0, 3, false))); + System.out.println(Arrays.toString(FileSearchUtil.calculate(0, 3, true))); + + System.out.println(Arrays.toString(FileSearchUtil.calculate(2, 3, false))); + System.out.println(Arrays.toString(FileSearchUtil.calculate(100, 100, false))); + System.out.println(Arrays.toString(FileSearchUtil.calculate(2, 3, true))); + + System.out.println(Arrays.toString(FileSearchUtil.calculate(20, 3, true))); + System.out.println(Arrays.toString(FileSearchUtil.calculate(20, 3, false))); + } + +} diff --git a/modules/agent/src/test/java/TestDelete.java b/modules/agent/src/test/java/TestDelete.java new file mode 100644 index 0000000000..a561c91df7 --- /dev/null +++ b/modules/agent/src/test/java/TestDelete.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.io.FileUtil; +import org.dromara.jpom.model.data.NodeProjectInfoModel; +import org.junit.Test; + +/** + * @author bwcx_jzy + * @since 2023/3/14 + */ +public class TestDelete { + + @Test + public void test() { + boolean del = FileUtil.del("C:\\Users\\bwcx_\\jpom\\agent\\data\\project_file_backup"); + System.out.println(del); + } + + @Test + public void test2() { + boolean del = FileUtil.del("D:\\data\\jpom\\a"); + System.out.println(del); + } + + @Test + public void testBeanCopy(){ + NodeProjectInfoModel nodeProjectInfoModel = new NodeProjectInfoModel(); + nodeProjectInfoModel.setName("ss"); + NodeProjectInfoModel nodeProjectInfoModel1 = new NodeProjectInfoModel(); + nodeProjectInfoModel1.setOutGivingProject(true); + BeanUtil.copyProperties(nodeProjectInfoModel, nodeProjectInfoModel1, CopyOptions.create().ignoreNullValue()); + System.out.println(nodeProjectInfoModel1.toString()); + } +} diff --git a/modules/agent/src/test/java/TestFile.java b/modules/agent/src/test/java/TestFile.java index c1fc66bb7f..7b37bc49d2 100644 --- a/modules/agent/src/test/java/TestFile.java +++ b/modules/agent/src/test/java/TestFile.java @@ -1,42 +1,37 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.io.unit.DataSizeUtil; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ZipUtil; +import org.junit.Test; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Stream; /** - * Created by jiangzeyin on 2019/4/22. + * Created by bwcx_jzy on 2019/4/22. */ public class TestFile { public static void main(String[] args) throws IOException { + File file = FileUtil.file("data123"); + System.out.println(FileUtil.isDirectory(file)); + InputStream inputStream = new FileInputStream("D:\\SystemDocument\\Desktop\\Desktop.zip"); - String code = IoUtil.readHex28Upper(inputStream); + String code = IoUtil.readHex64Upper(inputStream); System.out.println(code); System.out.println(FileUtil.getMimeType("D:\\SystemDocument\\Desktop\\Desktop.zip")); @@ -52,4 +47,32 @@ public static void main(String[] args) throws IOException { System.out.println(FileUtil.extName("test.zip")); } + + @Test + public void testReadFile() throws IOException { + + URL resource = ResourceUtil.getResource("."); + String fileStr = resource.getFile(); + File file = FileUtil.file(fileStr); + file = FileUtil.getParent(file, 2); + file = FileUtil.file(file, "log", "info.log"); + BufferedReader reader = FileUtil.getReader(file, CharsetUtil.CHARSET_UTF_8); + LineNumberReader lineNumberReader = new LineNumberReader(reader); + lineNumberReader.setLineNumber(20); + + System.out.println(lineNumberReader.getLineNumber() + " " + lineNumberReader.readLine()); + System.out.println(lineNumberReader.getLineNumber() + " " + lineNumberReader.readLine()); + + try (Stream lines = Files.lines(Paths.get(file.getAbsolutePath()))) { + String line32 = lines.skip(31).findFirst().get(); + System.out.println(line32); + System.out.println(lines.skip(32).findFirst().get()); + } +// FileUtil.tail(); + } + + @Test + public void test() { + System.out.println(DataSizeUtil.format(1000)); + } } diff --git a/modules/agent/src/test/java/TestIp.java b/modules/agent/src/test/java/TestIp.java index d3b93eb11d..9dcd0cf933 100644 --- a/modules/agent/src/test/java/TestIp.java +++ b/modules/agent/src/test/java/TestIp.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.net.NetUtil; import org.junit.Test; @@ -27,7 +14,7 @@ /** * @author bwcx_jzy - * @date 2019/12/27 + * @since 2019/12/27 */ public class TestIp { diff --git a/modules/agent/src/test/java/TestJdkTest.java b/modules/agent/src/test/java/TestJdkTest.java index bb1f902c45..38671a4e2a 100644 --- a/modules/agent/src/test/java/TestJdkTest.java +++ b/modules/agent/src/test/java/TestJdkTest.java @@ -1,45 +1,29 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -import cn.hutool.core.util.StrUtil; -import io.jpom.util.CommandUtil; -import io.jpom.util.FileUtils; import org.junit.Test; /** * @author bwcx_jzy - * @date 2019/10/30 + * @since 2019/10/30 */ public class TestJdkTest { @Test public void t() { String path = "C:\\Program Files\\Java\\jdk1.8.0_211"; - System.out.println(FileUtils.isJdkPath(path)); + //System.out.println(FileUtils.isJdkPath(path)); // - String version = FileUtils.getJdkVersion(path); - System.out.println(version); + //String version = FileUtils.getJdkVersion(path); + //System.out.println(version); } } diff --git a/modules/agent/src/test/java/TestJps.java b/modules/agent/src/test/java/TestJps.java index e91e0623f3..9978593129 100644 --- a/modules/agent/src/test/java/TestJps.java +++ b/modules/agent/src/test/java/TestJps.java @@ -1,44 +1,83 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.PatternPool; import cn.hutool.core.text.StrSplitter; +import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; -import io.jpom.util.CommandUtil; +import org.dromara.jpom.util.CommandUtil; import org.junit.Test; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author bwcx_jzy - * @date 2019/9/8 + * @since 2019/9/8 */ public class TestJps { - @Test - public void test() { - String execSystemCommand = CommandUtil.execSystemCommand("jps -v"); - List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); - for (String item : list) { - System.out.println("******************************"); - System.out.println(item); - } - } + + @Test + public void test() { + String execSystemCommand = CommandUtil.execSystemCommand("jps -v"); + List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); + for (String item : list) { + System.out.println("******************************"); + System.out.println(item); + } + } + + @Test + public void test1() { + String execSystemCommand = CommandUtil.execSystemCommand("jps"); + System.out.println(Arrays.toString(FileUtil.getLineSeparator().getBytes(StandardCharsets.UTF_8))); + System.out.println("======"); + System.out.println(execSystemCommand); + String regex = "^[^\\r\\n]*\\r?\\n(\\S)(\\S) (.*)"; + + Pattern r = Pattern.compile(regex); + Matcher m = r.matcher(execSystemCommand); + if (m.matches()) { + for (int i = 0; i < m.groupCount(); i++) { + System.out.println(m.group(i + 1)); + + } + } + + + String str = "afda\r\nad\r\nsss"; + r = Pattern.compile(regex); + m = r.matcher(str); + if (m.matches()) { + for (int i = 0; i < m.groupCount(); i++) { + System.out.println(m.group(i + 1)); + + } + } + + +// System.out.println(ReUtil.get(regex, execSystemCommand, 0)); +// System.out.println("----"); + final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL); + ReUtil.get(pattern, execSystemCommand, matcher -> System.out.println(matcher.group())); +// System.out.println(allGroups); + Map allGroupNames = ReUtil.getAllGroupNames(pattern, execSystemCommand); + for (Map.Entry stringStringEntry : allGroupNames.entrySet()) { + System.out.println(stringStringEntry.getKey() + " " + stringStringEntry.getValue()); + } + + // System.out.println("-----"); + // System.out.println(ReUtil.get("^[^\\r\\n]*\\r?\\n?(\\S{1})(\\S{1})$", execSystemCommand, 0)); + } } diff --git a/modules/agent/src/test/java/TestStr.java b/modules/agent/src/test/java/TestStr.java index dfbb553807..1db611b1bc 100644 --- a/modules/agent/src/test/java/TestStr.java +++ b/modules/agent/src/test/java/TestStr.java @@ -1,50 +1,88 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.PatternPool; import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ReUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSONObject; import org.junit.Test; +import java.util.TimeZone; +import java.util.regex.Pattern; + /** * @author bwcx_jzy * @since Created Time 2021/8/4 */ public class TestStr { - @Test - public void test() { - System.out.println(ReUtil.get(RegexPool.NUMBERS, "7499.1 total", 0)); - } + @Test + public void test() { + System.out.println(ReUtil.get(RegexPool.NUMBERS, "7499.1 total", 0)); + } + @Test + public void testJsonByte() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("byte", new byte[]{1}); + System.out.println(jsonObject); + } - @Test - public void testFile(){ + @Test + public void testFile() { // String path = "/www/server/panel/" - } + Pattern pattern = PatternPool.get("1", Pattern.DOTALL); + System.out.println(ReUtil.isMatch(".end", "123end")); + System.out.println(ReUtil.isMatch("^.+\\.(?i)(txt)$", "a.txt")); + } + + @Test + public void test2() { + System.out.printf("%.2f%n", (float) 1 / (float) 2 * 100); + System.out.println(NumberUtil.div(1, 2)); + } + + @Test + public void testParse() { + //String linuxCpu = LinuxSystemCommander.getLinuxCpu("%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\n"); + //System.out.println(linuxCpu); + } + + @Test + public void testHttp() { + String url = "http://127.0.0.1:3000/api/node/receive_push?token=67a8777929598040444f89bd6ab6938721d84f03&workspaceId=DEFAULT"; + UrlBuilder urlBuilder = UrlBuilder.ofHttp(url); + String build = urlBuilder.build(); + System.out.println(build); + HttpRequest httpRequest = HttpUtil.createGet(build); + System.out.println(httpRequest.form()); + } + + @Test + public void testNum() { + System.out.println(Convert.toInt("122+")); + System.out.println(NumberUtil.parseInt("1122+")); + } + + @Test + public void testTimeZone() { + System.out.println(TimeZone.getDefault().getID()); + } - @Test - public void test2(){ - System.out.println(String.format("%.2f", (float)1 / (float)2 * 100)); - System.out.println(NumberUtil.div(1,2)); - } + @Test + public void testList() { + System.out.println(CollUtil.addAll(null, CollUtil.newArrayList(""))); + } } diff --git a/modules/agent/src/test/java/TestVersion.java b/modules/agent/src/test/java/TestVersion.java index b9461d25d5..3e7716b477 100644 --- a/modules/agent/src/test/java/TestVersion.java +++ b/modules/agent/src/test/java/TestVersion.java @@ -1,33 +1,26 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.util.StrUtil; +import org.junit.Test; /** * @author bwcx_jzy - * @date 2019/8/4 + * @since 2019/8/4 */ public class TestVersion { public static void main(String[] args) { System.out.println(StrUtil.compareVersion("2.4.3", "2.4.2")); } + + @Test + public void test() { + System.out.println(StrUtil.compareVersion("2.11.5", "2.11.5.1")); + } } diff --git a/modules/agent/src/test/java/ZipUtil.java b/modules/agent/src/test/java/ZipUtil.java index a1a00d2756..49835a1360 100644 --- a/modules/agent/src/test/java/ZipUtil.java +++ b/modules/agent/src/test/java/ZipUtil.java @@ -1,26 +1,13 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -import io.jpom.util.CompressionFileUtil; +import org.dromara.jpom.util.CompressionFileUtil; import java.io.File; diff --git a/modules/agent/src/test/java/com/jinhill/pki/CertUtil.java b/modules/agent/src/test/java/com/jinhill/pki/CertUtil.java deleted file mode 100644 index ccd19cd1c5..0000000000 --- a/modules/agent/src/test/java/com/jinhill/pki/CertUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 com.jinhill.pki; - -import cn.hutool.core.io.resource.ResourceUtil; - -import java.io.InputStream; -import java.security.Security; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -public class CertUtil { - static { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); - } - - public static String getSubjectDN(InputStream bIn) { - String dn = ""; - try { - -// BouncyCastleProvider provider = new BouncyCastleProvider(); -// CertificateFactory cf = CertificateFactory.getInstance("X509", -// provider); - CertificateFactory cf = CertificateFactory.getInstance("X.509", - "SUN"); - //android 需采用bcprov -// CertificateFactory cf = CertificateFactory.getInstance("X.509", -// "BC"); - X509Certificate cert = (X509Certificate) cf - .generateCertificate(bIn); - dn = cert.getSubjectDN().getName(); - bIn.close(); - } catch (Exception e) { - e.printStackTrace(); - } - return dn; - } - - public static String parseCertDN(String dn, String type) { - type = type + "="; - String[] split = dn.split(","); - for (String x : split) { - if (x.contains(type)) { - x = x.trim(); - return x.substring(type.length()); - } - } - return null; - } - - /** - * @param args - */ - public static void main(String[] args) { - // TODO Auto-generated method stub - InputStream inputStream = ResourceUtil.getStream("D:\\jpom\\agent\\data\\temp\\系统管理员\\sdfasdf\\example.com.csr"); - getSubjectDN(inputStream); - } -} diff --git a/modules/agent/src/test/java/io/jpom/common/commander/impl/MacOSSystemCommanderTest.java b/modules/agent/src/test/java/io/jpom/common/commander/impl/MacOSSystemCommanderTest.java deleted file mode 100644 index 4c3769c386..0000000000 --- a/modules/agent/src/test/java/io/jpom/common/commander/impl/MacOSSystemCommanderTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.commander.impl; - -import cn.hutool.system.OsInfo; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import org.junit.Test; - -public class MacOSSystemCommanderTest { - private static OsInfo osInfo; - - static { - osInfo = SystemUtil.getOsInfo(); - } - - /** - * 测试 MacOS 相关 top 命令 - * top -b 参数无法在 MacOS 上执行 - */ - @Test - public void testGetAllMonitor() { - DefaultSystemLog.getLog().info("is Mac: {}", osInfo.isMac()); - DefaultSystemLog.getLog().info("is Linux: {}", osInfo.isLinux()); - DefaultSystemLog.getLog().info("is MacOSX: {}", osInfo.isMacOsX()); - // Mac OS - if (osInfo.isMac() || osInfo.isMacOsX()) { - // String result = CommandUtil.execSystemCommand("top -l 1 -n 1"); - String result = "Processes: 423 total, 2 running, 421 sleeping, 2080 threads\n" + - "2020/11/19 10:05:16\n" + - "Load Avg: 1.15, 1.62, 1.83\n" + - "CPU usage: 3.1% user, 11.5% sys, 85.92% idle\n" + - "SharedLibs: 279M resident, 44M data, 27M linkedit.\n" + - "MemRegions: 118945 total, 1481M resident, 133M private, 987M shared.\n" + - "PhysMem: 8036M used (2072M wired), 154M unused.\n" + - "VM: 2326G vsize, 2308M framework vsize, 11940793(0) swapins, 12858959(0) swapouts.\n" + - "Networks: packets: 4774826/5179M in, 4179746/2989M out.\n" + - "Disks: 5626236/140G read, 1788986/89G written.\n" + - "\n" + - "PID COMMAND %CPU TIME #TH #WQ #PORTS MEM PURG CMPRS PGRP PPID STATE BOOSTS %CPU_ME %CPU_OTHRS UID FAULTS COW MSGSENT MSGRECV SYSBSD SYSMACH CSW PAGEINS IDLEW POWER INSTRS CYCLES USER #MREGS RPRVT VPRVT VSIZE KPRVT KSHRD\n" + - "35069 top 0.0 00:00.24 1/1 0 14 3428K 0B 0B 35069 28803 running *0[1] 0.00000 0.00000 0 1603 91 269374 134686 1834 138100 28 0 0 0.0 0 0 root N/A N/A N/A N/A N/A N/A"; - - DefaultSystemLog.getLog().info(result); - - DefaultSystemLog.getLog().info("-----------------------------"); - - MacOSSystemCommander macOSSystemCommander = new MacOSSystemCommander(); - macOSSystemCommander.getAllMonitor(); - } - } -} \ No newline at end of file diff --git a/modules/agent/src/test/java/org/dromara/jpom/OshiNetworkTest.java b/modules/agent/src/test/java/org/dromara/jpom/OshiNetworkTest.java new file mode 100644 index 0000000000..5567f99855 --- /dev/null +++ b/modules/agent/src/test/java/org/dromara/jpom/OshiNetworkTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.system.oshi.OshiUtil; +import org.dromara.jpom.util.OshiUtils; +import org.junit.Test; +import oshi.hardware.NetworkIF; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy1 + * @since 2024/4/25 + */ +public class OshiNetworkTest { + + @Test + public void test() { + List listBegin = OshiUtil.getNetworkIFs(); + System.out.println(listBegin.size()); + List statExcludeNames = CollUtil.newArrayList("lo*", "ap*"); + List statContainsOnlyNames = CollUtil.newArrayList("en*"); + listBegin = listBegin.stream() + .filter(networkIF -> CollUtil.isEmpty(statExcludeNames) || !OshiUtils.isMatch(statExcludeNames, networkIF.getName())) + .filter(networkIF -> CollUtil.isEmpty(statContainsOnlyNames) || OshiUtils.isMatch(statContainsOnlyNames, networkIF.getName())) + .collect(Collectors.toList()); + for (NetworkIF anIf : listBegin) { + System.out.println(anIf.getName()); + } +// System.out.println(listBegin); + } +} diff --git a/modules/agent/src/test/java/org/dromara/jpom/common/commander/impl/MacOSSystemCommanderTest.java b/modules/agent/src/test/java/org/dromara/jpom/common/commander/impl/MacOSSystemCommanderTest.java new file mode 100644 index 0000000000..49f74ce6d0 --- /dev/null +++ b/modules/agent/src/test/java/org/dromara/jpom/common/commander/impl/MacOSSystemCommanderTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.commander.impl; + +import cn.hutool.system.OsInfo; +import cn.hutool.system.SystemUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +@Slf4j +public class MacOSSystemCommanderTest { + private static OsInfo osInfo; + + static { + osInfo = SystemUtil.getOsInfo(); + } + + /** + * 测试 MacOS 相关 top 命令 + * top -b 参数无法在 MacOS 上执行 + */ + @Test + public void testGetAllMonitor() { + log.info("is Mac: {}", osInfo.isMac()); + log.info("is Linux: {}", osInfo.isLinux()); + log.info("is MacOSX: {}", osInfo.isMacOsX()); + // Mac OS + if (osInfo.isMac() || osInfo.isMacOsX()) { + // String result = CommandUtil.execSystemCommand("top -l 1 -n 1"); + String result = "Processes: 423 total, 2 running, 421 sleeping, 2080 threads\n" + + "2020/11/19 10:05:16\n" + + "Load Avg: 1.15, 1.62, 1.83\n" + + "CPU usage: 3.1% user, 11.5% sys, 85.92% idle\n" + + "SharedLibs: 279M resident, 44M data, 27M linkedit.\n" + + "MemRegions: 118945 total, 1481M resident, 133M private, 987M shared.\n" + + "PhysMem: 8036M used (2072M wired), 154M unused.\n" + + "VM: 2326G vsize, 2308M framework vsize, 11940793(0) swapins, 12858959(0) swapouts.\n" + + "Networks: packets: 4774826/5179M in, 4179746/2989M out.\n" + + "Disks: 5626236/140G read, 1788986/89G written.\n" + + "\n" + + "PID COMMAND %CPU TIME #TH #WQ #PORTS MEM PURG CMPRS PGRP PPID STATE BOOSTS %CPU_ME %CPU_OTHRS UID FAULTS COW MSGSENT MSGRECV SYSBSD SYSMACH CSW PAGEINS IDLEW POWER INSTRS CYCLES USER #MREGS RPRVT VPRVT VSIZE KPRVT KSHRD\n" + + "35069 top 0.0 00:00.24 1/1 0 14 3428K 0B 0B 35069 28803 running *0[1] 0.00000 0.00000 0 1603 91 269374 134686 1834 138100 28 0 0 0.0 0 0 root N/A N/A N/A N/A N/A N/A"; + + log.info(result); + + log.info("-----------------------------"); + +// MacOsSystemCommander macOSSystemCommander = new MacOsSystemCommander(); +// macOSSystemCommander.getAllMonitor(); + } + } +} diff --git a/modules/agent/src/test/java/oshi/SystemInfoTest.java b/modules/agent/src/test/java/oshi/SystemInfoTest.java new file mode 100644 index 0000000000..9c3ff6097b --- /dev/null +++ b/modules/agent/src/test/java/oshi/SystemInfoTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package oshi; + +import cn.hutool.core.date.DateUtil; +import lombok.extern.slf4j.Slf4j; +import oshi.hardware.*; +import oshi.software.os.FileSystem; +import oshi.software.os.NetworkParams; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.FormatUtil; +import oshi.util.Util; + +import java.util.Arrays; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +@Slf4j +public class SystemInfoTest { + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + // Options: ERROR > WARN > INFO > DEBUG > TRACE + log.info("Initializing System..."); + SystemInfo si = new SystemInfo(); + + HardwareAbstractionLayer hal = si.getHardware(); + OperatingSystem os = si.getOperatingSystem(); + + System.out.println(os); + + log.info("Checking computer system..."); + printComputerSystem(hal.getComputerSystem()); + + log.info("Checking Processor..."); + printProcessor(hal.getProcessor()); + + log.info("Checking Memory..."); + printMemory(hal.getMemory()); + + log.info("Checking CPU..."); + printCpu(hal.getProcessor()); + + log.info("Checking Processes..."); + printProcesses(os, hal.getMemory()); + + log.info("Checking Sensors..."); + printSensors(hal.getSensors()); + + log.info("Checking Power sources..."); + printPowerSources(hal.getPowerSources()); + + log.info("Checking Disks..."); + printDisks(hal.getDiskStores()); + + log.info("Checking File System..."); + printFileSystem(os.getFileSystem()); + + log.info("Checking Network interfaces..."); + printNetworkInterfaces(hal.getNetworkIFs()); + + log.info("Checking Network parameterss..."); + printNetworkParameters(os.getNetworkParams()); + + // hardware: displays + log.info("Checking Displays..."); + printDisplays(hal.getDisplays()); + + // hardware: USB devices + log.info("Checking USB Devices..."); + printUsbDevices(hal.getUsbDevices(true)); + } + + private static void printComputerSystem(final ComputerSystem computerSystem) { + + System.out.println("manufacturer: " + computerSystem.getManufacturer()); + System.out.println("model: " + computerSystem.getModel()); + System.out.println("serialnumber: " + computerSystem.getSerialNumber()); + final Firmware firmware = computerSystem.getFirmware(); + System.out.println("firmware:"); + System.out.println(" manufacturer: " + firmware.getManufacturer()); + System.out.println(" name: " + firmware.getName()); + System.out.println(" description: " + firmware.getDescription()); + System.out.println(" version: " + firmware.getVersion()); + System.out.println(" release date: " + (firmware.getReleaseDate() == null ? "unknown" + : firmware.getReleaseDate() == null ? "unknown" : DateUtil.parse(firmware.getReleaseDate()))); + final Baseboard baseboard = computerSystem.getBaseboard(); + System.out.println("baseboard:"); + System.out.println(" manufacturer: " + baseboard.getManufacturer()); + System.out.println(" model: " + baseboard.getModel()); + System.out.println(" version: " + baseboard.getVersion()); + System.out.println(" serialnumber: " + baseboard.getSerialNumber()); + } + + private static void printProcessor(CentralProcessor processor) { + System.out.println(processor); + System.out.println(" " + processor.getPhysicalPackageCount() + " physical CPU package(s)"); + System.out.println(" " + processor.getPhysicalProcessorCount() + " physical CPU core(s)"); + System.out.println(" " + processor.getLogicalProcessorCount() + " logical CPU(s)"); + +// System.out.println("Identifier: " + processor.getIdentifier()); +// System.out.println("ProcessorID: " + processor.getProcessorID()); + } + + private static void printMemory(GlobalMemory memory) { +// System.out.println("Memory: " + FormatUtil.formatBytes(memory.getAvailable()) + "/" +// + FormatUtil.formatBytes(memory.getTotal())); +// System.out.println("Swap used: " + FormatUtil.formatBytes(memory.getSwapUsed()) + "/" +// + FormatUtil.formatBytes(memory.getSwapTotal())); + } + + private static void printCpu(CentralProcessor processor) { +// System.out.println("Uptime: " + FormatUtil.formatElapsedSecs(processor.getSystemUptime())); + System.out.println( + "Context Switches/Interrupts: " + processor.getContextSwitches() + " / " + processor.getInterrupts()); + + long[] prevTicks = processor.getSystemCpuLoadTicks(); + System.out.println("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks)); + // Wait a second... + Util.sleep(1000); + long[] ticks = processor.getSystemCpuLoadTicks(); + System.out.println("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks)); +// long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; +// long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; +// long sys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; +// long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; +// long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; +// long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; +// long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; +// long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; +// long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal; + +// System.out.format( +// "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%%n", +// 100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu, +// 100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu); +// System.out.format("CPU load: %.1f%% (counting ticks)%n", processor.getSystemCpuLoadBetweenTicks() * 100); +// System.out.format("CPU load: %.1f%% (OS MXBean)%n", processor.getSystemCpuLoad() * 100); +// double[] loadAverage = processor.getSystemLoadAverage(3); +// System.out.println("CPU load averages:" + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0])) +// + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1])) +// + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2]))); +// // per core CPU +// StringBuilder procCpu = new StringBuilder("CPU load per processor:"); +// double[] load = processor.getProcessorCpuLoadBetweenTicks(); +// for (double avg : load) { +// procCpu.append(String.format(" %.1f%%", avg * 100)); +// } +// System.out.println(procCpu.toString()); + } + + private static void printProcesses(OperatingSystem os, GlobalMemory memory) { +// System.out.println("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount()); +// // Sort by highest CPU +// List procs = Arrays.asList(os.getProcesses(5, ProcessSort.CPU)); +// +// System.out.println(" PID %CPU %MEM VSZ RSS Name"); +// for (int i = 0; i < procs.size() && i < 5; i++) { +// OSProcess p = procs.get(i); +// System.out.format(" %5d %5.1f %4.1f %9s %9s %s%n", p.getProcessID(), +// 100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(), +// 100d * p.getResidentSetSize() / memory.getTotal(), FormatUtil.formatBytes(p.getVirtualSize()), +// FormatUtil.formatBytes(p.getResidentSetSize()), p.getName()); +// } + } + + private static void printSensors(Sensors sensors) { + System.out.println("Sensors:"); + System.out.format(" CPU Temperature: %.1f°C%n", sensors.getCpuTemperature()); + System.out.println(" Fan Speeds: " + Arrays.toString(sensors.getFanSpeeds())); + System.out.format(" CPU Voltage: %.1fV%n", sensors.getCpuVoltage()); + } + + private static void printPowerSources(List powerSources) { +// StringBuilder sb = new StringBuilder("Power: "); +// if (powerSources.length == 0) { +// sb.append("Unknown"); +// } else { +// double timeRemaining = powerSources[0].getTimeRemaining(); +// if (timeRemaining < -1d) { +// sb.append("Charging"); +// } else if (timeRemaining < 0d) { +// sb.append("Calculating time remaining"); +// } else { +// sb.append(String.format("%d:%02d remaining", (int) (timeRemaining / 3600), +// (int) (timeRemaining / 60) % 60)); +// } +// } +// for (PowerSource pSource : powerSources) { +// sb.append(String.format("%n %s @ %.1f%%", pSource.getName(), pSource.getRemainingCapacity() * 100d)); +// } +// System.out.println(sb.toString()); + } + + private static void printDisks(List diskStores) { + System.out.println("Disks:"); + for (HWDiskStore disk : diskStores) { + boolean readwrite = disk.getReads() > 0 || disk.getWrites() > 0; + System.out.format(" %s: (model: %s - S/N: %s) size: %s, reads: %s (%s), writes: %s (%s), xfer: %s ms%n", + disk.getName(), disk.getModel(), disk.getSerial(), + disk.getSize() > 0 ? FormatUtil.formatBytesDecimal(disk.getSize()) : "?", + readwrite ? disk.getReads() : "?", readwrite ? FormatUtil.formatBytes(disk.getReadBytes()) : "?", + readwrite ? disk.getWrites() : "?", readwrite ? FormatUtil.formatBytes(disk.getWriteBytes()) : "?", + readwrite ? disk.getTransferTime() : "?"); + List partitions = disk.getPartitions(); + if (partitions == null) { + // TODO Remove when all OS's implemented + continue; + } + for (HWPartition part : partitions) { + System.out.format(" |-- %s: %s (%s) Maj:Min=%d:%d, size: %s%s%n", part.getIdentification(), + part.getName(), part.getType(), part.getMajor(), part.getMinor(), + FormatUtil.formatBytesDecimal(part.getSize()), + part.getMountPoint().isEmpty() ? "" : " @ " + part.getMountPoint()); + } + } + } + + private static void printFileSystem(FileSystem fileSystem) { + System.out.println("File System:"); + + System.out.format(" File Descriptors: %d/%d%n", fileSystem.getOpenFileDescriptors(), + fileSystem.getMaxFileDescriptors()); + + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) { + long usable = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + System.out.format( + " %s (%s) [%s] %s of %s free (%.1f%%) is %s " + + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s") + + " and is mounted at %s%n", + fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(), + FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total, + fs.getVolume(), fs.getLogicalVolume(), fs.getMount()); + } + } + + private static void printNetworkInterfaces(List networkIFs) { + System.out.println("Network interfaces:"); + for (NetworkIF net : networkIFs) { + System.out.format(" Name: %s (%s)%n", net.getName(), net.getDisplayName()); + System.out.format(" MAC Address: %s %n", net.getMacaddr()); + System.out.format(" MTU: %s, Speed: %s %n", net.getMTU(), FormatUtil.formatValue(net.getSpeed(), "bps")); + System.out.format(" IPv4: %s %n", Arrays.toString(net.getIPv4addr())); + System.out.format(" IPv6: %s %n", Arrays.toString(net.getIPv6addr())); + boolean hasData = net.getBytesRecv() > 0 || net.getBytesSent() > 0 || net.getPacketsRecv() > 0 + || net.getPacketsSent() > 0; + System.out.format(" Traffic: received %s/%s%s; transmitted %s/%s%s %n", + hasData ? net.getPacketsRecv() + " packets" : "?", + hasData ? FormatUtil.formatBytes(net.getBytesRecv()) : "?", + hasData ? " (" + net.getInErrors() + " err)" : "", + hasData ? net.getPacketsSent() + " packets" : "?", + hasData ? FormatUtil.formatBytes(net.getBytesSent()) : "?", + hasData ? " (" + net.getOutErrors() + " err)" : ""); + } + } + + private static void printNetworkParameters(NetworkParams networkParams) { + System.out.println("Network parameters:"); + System.out.format(" Host name: %s%n", networkParams.getHostName()); + System.out.format(" Domain name: %s%n", networkParams.getDomainName()); + System.out.format(" DNS servers: %s%n", Arrays.toString(networkParams.getDnsServers())); + System.out.format(" IPv4 Gateway: %s%n", networkParams.getIpv4DefaultGateway()); + System.out.format(" IPv6 Gateway: %s%n", networkParams.getIpv6DefaultGateway()); + } + + private static void printDisplays(List displays) { + System.out.println("Displays:"); + int i = 0; + for (Display display : displays) { + System.out.println(" Display " + i + ":"); + System.out.println(display.toString()); + i++; + } + } + + private static void printUsbDevices(List usbDevices) { + System.out.println("USB Devices:"); + for (UsbDevice usbDevice : usbDevices) { + System.out.println(usbDevice.toString()); + } + } +} diff --git a/modules/agent/src/test/java/oshi/TestInfo.java b/modules/agent/src/test/java/oshi/TestInfo.java new file mode 100644 index 0000000000..6bdc0e5b34 --- /dev/null +++ b/modules/agent/src/test/java/oshi/TestInfo.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package oshi; + +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.util.OshiUtils; +import org.junit.Test; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +public class TestInfo { + + @Test + public void test() { + JSONObject systemInfo = OshiUtils.getSystemInfo(); + + //System.out.println(processor); + System.out.println(systemInfo); + } + + @Test + public void test2() { + System.out.println(1L << 10); + System.out.println(1_000L); + } +} diff --git a/modules/agent/src/test/java/oshi/TestProcessesList.java b/modules/agent/src/test/java/oshi/TestProcessesList.java new file mode 100644 index 0000000000..4b0aa44a2c --- /dev/null +++ b/modules/agent/src/test/java/oshi/TestProcessesList.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package oshi; + +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.util.OshiUtils; +import org.junit.Test; + +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/2/12 + */ +public class TestProcessesList { + + @Test + public void test() { + List processes = OshiUtils.getProcesses("java", 20); + System.out.println(processes); + } +} diff --git a/modules/common/pom.xml b/modules/common/pom.xml index 800572c258..c5454f428a 100644 --- a/modules/common/pom.xml +++ b/modules/common/pom.xml @@ -1,33 +1,149 @@ + jpom-parent - io.jpom - 2.8.0 + org.dromara.jpom + 2.11.6.6 ../../pom.xml 4.0.0 - Jpom 公共模块 + Jpom Common common - 2.8.0 + 2.11.6.6 - + + + org.springframework.boot + spring-boot-starter-web + + + tomcat-embed-core + org.apache.tomcat.embed + + + tomcat-embed-el + org.apache.tomcat.embed + + + tomcat-embed-websocket + org.apache.tomcat.embed + + + snakeyaml + org.yaml + + + + + + snakeyaml + org.yaml + + + + tomcat-embed-core + org.apache.tomcat.embed + + + + tomcat-embed-el + org.apache.tomcat.embed + + + + tomcat-embed-websocket + org.apache.tomcat.embed + + + + com.alibaba.fastjson2 + fastjson2 + + + + cn.hutool + hutool-core + + + + cn.hutool + hutool-http + + + + cn.hutool + hutool-system + + + + cn.hutool + hutool-extra + + + + cn.hutool + hutool-crypto + + + + cn.hutool + hutool-cron + + + - net.sourceforge.jchardet - jchardet - 1.0 + org.springframework.boot + spring-boot-starter-aop + + + org.apache.commons + commons-compress + 1.26.0 + + + + cn.keepbx.jpom + jpom-core-common + 1.0.5 + + + + org.apache.commons + commons-exec + 1.4.0 + + + + org.dromara.jpom.plugins + encrypt + ${project.version} + + - - - release-plugin-profile - - - install-plugin-profile - - + + + + src/main/resources + + i18n/words.json + + + + diff --git a/modules/common/src/main/java/io/jpom/JpomApplication.java b/modules/common/src/main/java/io/jpom/JpomApplication.java deleted file mode 100644 index f1194efc6e..0000000000 --- a/modules/common/src/main/java/io/jpom/JpomApplication.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom; - -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.ApplicationBuilder; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.validator.ParameterInterceptor; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import io.jpom.common.Const; -import io.jpom.common.JpomApplicationEvent; -import io.jpom.common.JpomManifest; -import io.jpom.common.Type; -import io.jpom.common.interceptor.PluginFeatureInterceptor; -import io.jpom.plugin.PluginFactory; -import io.jpom.util.CommandUtil; -import org.springframework.core.env.Environment; -import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.util.Assert; - -import java.io.File; -import java.nio.charset.Charset; -import java.util.concurrent.TimeUnit; - -/** - * Jpom - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public class JpomApplication extends ApplicationBuilder { - - /** - * - */ - public static final String SYSTEM_ID = "system"; - - protected static String[] args; - /** - * 应用类型 - */ - private static Type appType; - private static Charset charset; - - private static Class appClass; - - /** - * 获取程序命令行参数 - * - * @return 数组 - */ - public static String[] getArgs() { - return args; - } - - public JpomApplication(Type appType, Class appClass, String[] args) throws Exception { - super(appClass); - // - checkEvent(args); - JpomApplication.appType = appType; - JpomApplication.appClass = appClass; - JpomApplication.args = args; - // 检查 type 中的 applicationClass 配置是否正确 - String applicationClass = appType.getApplicationClass(); - Assert.state(StrUtil.equals(applicationClass, appClass.getName()), "当前允许的类和配置的类名不一致:io.jpom.common.Type#getApplicationClass()"); - - addHttpMessageConverter(new StringHttpMessageConverter(CharsetUtil.CHARSET_UTF_8)); - - // - ObjectMapper build = createJackson(); - addHttpMessageConverter(new MappingJackson2HttpMessageConverter(build)); - - // 参数拦截器 - addInterceptor(ParameterInterceptor.class); - addInterceptor(PluginFeatureInterceptor.class); - // - addApplicationEventClient(new JpomApplicationEvent()); - // 添加初始化监听 - this.application().addInitializers(new PluginFactory()); - } - - /** - * jackson 配置 - * - * @return mapper - */ - private static ObjectMapper createJackson() { - Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = Jackson2ObjectMapperBuilder.json(); - jackson2ObjectMapperBuilder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN); - ObjectMapper build = jackson2ObjectMapperBuilder.build(); - // 忽略空 - build.setSerializationInclusion(JsonInclude.Include.NON_NULL); - // 驼峰转下划线 - // build.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy()); - // long to String - SimpleModule simpleModule = new SimpleModule(); - simpleModule.addSerializer(Long.class, ToStringSerializer.instance); - simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); - build.registerModule(simpleModule); - // - build.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); -// build.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); - - return build; - } - - private void checkEvent(String[] args) throws Exception { - new JpomClose().main(args); - } - - /** - * 获取当前系统编码 - * - * @return charset - */ - public static Charset getCharset() { - if (charset == null) { - if (SystemUtil.getOsInfo().isLinux()) { - charset = CharsetUtil.CHARSET_UTF_8; - } else if (SystemUtil.getOsInfo().isMac()) { - charset = CharsetUtil.CHARSET_UTF_8; - } else { - charset = CharsetUtil.CHARSET_GBK; - } - } - return charset; - } - - /** - * 获取当前程序的类型 - * - * @return Agent 或者 Server - */ - public static Type getAppType() { - if (appType == null) { - // 从配置文件中获取 - Environment environment = JpomApplication.getEnvironment(); - String property = environment.getProperty(Const.APPLICATION_NAME); - property = StrUtil.removeAll(property, "jpom"); - Assert.hasLength(property, "请配置程序类型:" + Const.APPLICATION_NAME); - appType = Type.valueOf(property); - } - return appType; - } - - public static Class getAppClass() { - if (appClass == null) { - return JpomApplication.class; - } - return appClass; - } - - /** - * 重启自身 - * 分发会延迟2秒执行正式升级 重启命令 - */ - public static void restart() { - File scriptFile = JpomManifest.getScriptFile(); - ThreadUtil.execute(() -> { - // Waiting for method caller,For example, the interface response - ThreadUtil.sleep(2, TimeUnit.SECONDS); - try { - String command = SystemUtil.getOsInfo().isWindows() ? StrUtil.EMPTY : CommandUtil.SUFFIX; - - command += " " + FileUtil.getAbsolutePath(scriptFile) + " restart upgrade"; - if (SystemUtil.getOsInfo().isWindows()) { - CommandUtil.execSystemCommand(command, scriptFile.getParentFile()); - } else { - CommandUtil.asyncExeLocalCommand(scriptFile.getParentFile(), command); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("重启自身异常", e); - } - }); - } -} diff --git a/modules/common/src/main/java/io/jpom/JpomClose.java b/modules/common/src/main/java/io/jpom/JpomClose.java deleted file mode 100644 index ffac8c7045..0000000000 --- a/modules/common/src/main/java/io/jpom/JpomClose.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom; - -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import io.jpom.util.CommandUtil; -import io.jpom.util.JvmUtil; -import io.jpom.util.StringUtil; - -import java.io.IOException; - -/** - * 命令行关闭Jpom - * - * @author jiangzeyin - * @date 2019/4/7 - */ -public class JpomClose { - private static JpomClose jpomManager; - - public void main(String[] args) throws Exception { - String tag = StringUtil.getArgsValue(args, "jpom.applicationTag"); - if (StrUtil.isEmpty(tag)) { - return; - } - // 事件 - String event = StringUtil.getArgsValue(args, "event"); - if ("stop".equalsIgnoreCase(event)) { - String status = JpomClose.getInstance().status(tag); - if (!status.contains(StrUtil.COLON)) { - Console.error("Jpom并没有运行"); - } else { - String msg = JpomClose.getInstance().stop(tag); - Console.log(msg); - } - System.exit(0); - } else if ("status".equalsIgnoreCase(event)) { - String status = JpomClose.getInstance().status(tag); - Console.log(status); - System.exit(0); - } - } - - /** - * 单利模式 - * - * @return JpomClose - */ - public static JpomClose getInstance() { - if (jpomManager != null) { - return jpomManager; - } - if (SystemUtil.getOsInfo().isLinux()) { - jpomManager = new Linux(); - } else { - jpomManager = new Windows(); - } - return jpomManager; - } - - - public String stop(String tag) throws IOException { - Integer pid = JvmUtil.getPidByTag(tag); - return ObjectUtil.toString(pid); - } - - public String status(String tag) throws IOException { - Integer pid = JvmUtil.getPidByTag(tag); - if (pid == null) { - return "Jpom并没有运行"; - } - return "Jpom运行中:" + pid; - } - - - private static class Windows extends JpomClose { - - @Override - public String stop(String tag) throws IOException { - String pid = super.stop(tag); - if (pid == null) { - return "stop"; - } - String cmd = String.format("taskkill /F /PID %s", pid); - return CommandUtil.execSystemCommand(cmd); - } - } - - private static class Linux extends JpomClose { - - @Override - public String stop(String tag) throws IOException { - String pid = super.stop(tag); - if (pid == null) { - return "stop"; - } - String cmd = String.format("kill %s", pid); - return CommandUtil.execSystemCommand(cmd); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/JpomLogo.java b/modules/common/src/main/java/io/jpom/JpomLogo.java deleted file mode 100644 index 4950640e4d..0000000000 --- a/modules/common/src/main/java/io/jpom/JpomLogo.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom; - -import cn.hutool.core.io.FileUtil; -import io.jpom.common.JpomManifest; -import io.jpom.util.VersionUtils; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationListener; - -/** - * @author Hotstrip - * load Jpom version and print Jpom logo - */ -//@Order(LoggingApplicationListener.DEFAULT_ORDER + 1) -public class JpomLogo implements ApplicationListener { - - @Override - public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { - System.out.println(buildBannerText()); - } - - private static final String JPOM_LOGO = "\n" + - " _ \n" + - " | | \n" + - " | |_ __ ___ _ __ ___ \n" + - " _ | | '_ \\ / _ \\| '_ ` _ \\ \n" + - " | |__| | |_) | (_) | | | | | |\n" + - " \\____/| .__/ \\___/|_| |_| |_|\n" + - " | | \n" + - " |_| \n"; - - /** - * @return jpom logo banner - * @see JpomManifest#getVersion() - */ - private String buildBannerText() { - String lineSeparator = FileUtil.getLineSeparator(); - return lineSeparator - + JPOM_LOGO - + lineSeparator - + " :: Jpom :: (v" + VersionUtils.getVersion(getClass(), JpomManifest.getInstance().getVersion()) + ")" - + lineSeparator; - } - -} diff --git a/modules/common/src/main/java/io/jpom/common/BaseDataService.java b/modules/common/src/main/java/io/jpom/common/BaseDataService.java deleted file mode 100644 index 79f724af4c..0000000000 --- a/modules/common/src/main/java/io/jpom/common/BaseDataService.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.BaseModel; -import io.jpom.system.ConfigBean; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.JsonFileUtil; - -import java.io.FileNotFoundException; - -/** - * 公共文件操作Service - * - * @author jiangzeyin - * @date 2019/1/16 - */ -public abstract class BaseDataService { - - /** - * 获取数据文件的路径,如果文件不存在,则创建一个 - * - * @param filename 文件名 - * @return path - */ - protected String getDataFilePath(String filename) { - return FileUtil.normalize(ConfigBean.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); - JSONObject data = allData.getJSONObject(key); - // 判断是否存在数据 - if (MapUtil.isEmpty(data)) { - throw new JpomRuntimeException("项目名称存不在!"); - } else { - 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/common/src/main/java/io/jpom/common/BaseJpomController.java b/modules/common/src/main/java/io/jpom/common/BaseJpomController.java deleted file mode 100644 index b9cb965435..0000000000 --- a/modules/common/src/main/java/io/jpom/common/BaseJpomController.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.controller.base.AbstractController; - -/** - * controller - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public abstract class BaseJpomController extends AbstractController { - /** - * 路径安全格式化 - * - * @param path 路径 - * @return 去掉 提权字符串 - */ - public static String pathSafe(String path) { - if (path == null) { - return null; - } - String newPath = path.replace("../", StrUtil.EMPTY); - newPath = newPath.replace("..\\", StrUtil.EMPTY); - newPath = newPath.replace("+", StrUtil.EMPTY); - return FileUtil.normalize(newPath); - } - - protected boolean checkPathSafe(String path) { - if (path == null) { - return false; - } - String newPath = path.replace("../", StrUtil.EMPTY); - newPath = newPath.replace("..\\", StrUtil.EMPTY); - newPath = newPath.replace("+", StrUtil.EMPTY); - return newPath.equals(path); - } -} diff --git a/modules/common/src/main/java/io/jpom/common/BaseOperService.java b/modules/common/src/main/java/io/jpom/common/BaseOperService.java deleted file mode 100644 index aa66f32a4d..0000000000 --- a/modules/common/src/main/java/io/jpom/common/BaseOperService.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.util.ClassUtil; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.BaseModel; -import io.jpom.util.JsonFileUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * 标准操作Service - * - * @author jiangzeyin - * @date 2019/3/14 - */ -public abstract class BaseOperService extends BaseDataService { - - private final String fileName; - private final Class typeArgument; - - public BaseOperService(String fileName) { - this.fileName = fileName; - this.typeArgument = ClassUtil.getTypeArgument(this.getClass()); - } - - /** - * 获取所有数据 - * - * @return list - */ - public List list() { - return (List) list(typeArgument); - } - - public List list(Class cls) { - JSONObject jsonObject = getJSONObject(); - if (jsonObject == null) { - return new ArrayList<>(); - } - JSONArray jsonArray = JsonFileUtil.formatToArray(jsonObject); - return jsonArray.toJavaList(cls); - } - - public JSONObject getJSONObject() { - Objects.requireNonNull(fileName, "没有配置fileName"); - return getJSONObject(fileName); - } - - /** - * 工具id 获取 实体 - * - * @param id 数据id - * @return T - */ - public T getItem(String id) { - Objects.requireNonNull(fileName, "没有配置fileName"); - return (T) getJsonObjectById(fileName, id, typeArgument); - } - - - /** - * 添加实体 - * - * @param t 实体 - */ - public void addItem(T t) { - Objects.requireNonNull(fileName, "没有配置fileName"); - saveJson(fileName, t); - } - - /** - * 删除实体 - * - * @param id 数据id - */ - public void deleteItem(String id) { - Objects.requireNonNull(fileName, "没有配置fileName"); - deleteJson(fileName, id); - } - - /** - * 修改实体 - * - * @param t 实体 - */ - public void updateItem(T t) { - Objects.requireNonNull(fileName, "没有配置fileName"); - updateJson(fileName, t); - } - - -} diff --git a/modules/common/src/main/java/io/jpom/common/Const.java b/modules/common/src/main/java/io/jpom/common/Const.java deleted file mode 100644 index f0db0be861..0000000000 --- a/modules/common/src/main/java/io/jpom/common/Const.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -/** - * @author Hotstrip - * Const class - */ -public class Const { - /** - * String const - */ - public static final String ID_STR = "id"; - public static final String GROUP_STR = "group"; - - /** - * 应用程序类型的配置 key - */ - public static final String APPLICATION_NAME = "spring.application.name"; - - /** - * String get - */ - public static final String GET_STR = "get"; - - - /** - * id_rsa - */ - public static final String ID_RSA = "_id_rsa"; - /** - * sshkey - */ - public static final String SSH_KEY = "sshkey"; - - /** - * SQL backup default directory name - * 数据库备份默认目录名称 - */ - public static final String BACKUP_DIRECTORY_NAME = "backup"; - /** - * h2 数据库表名字段 - */ - public static final String TABLE_NAME = "TABLE_NAME"; - /** - * 备份 SQL 文件 后缀 - */ - public static final String SQL_FILE_SUFFIX = ".sql"; - /** - * 升级提示语 - */ - public static final String UPGRADE_MSG = "升级(重启)中大约需要30秒~2分钟左右"; - - /** - * 请求 header - */ - public static final String WORKSPACEID_REQ_HEADER = "workspaceId"; -} diff --git a/modules/common/src/main/java/io/jpom/common/JpomApplicationEvent.java b/modules/common/src/main/java/io/jpom/common/JpomApplicationEvent.java deleted file mode 100644 index a28b34360d..0000000000 --- a/modules/common/src/main/java/io/jpom/common/JpomApplicationEvent.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import cn.jiangzeyin.common.spring.event.ApplicationEventClient; -import cn.jiangzeyin.common.spring.event.ApplicationEventLoad; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.system.ConfigBean; -import io.jpom.system.ExtConfigBean; -import io.jpom.util.JsonFileUtil; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.event.ContextClosedEvent; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.util.List; - -/** - * 启动 、关闭监听 - * - * @author jiangzeyin - * @date 2019/4/7 - */ -public class JpomApplicationEvent implements ApplicationEventClient { - private FileLock lock; - private FileOutputStream fileOutputStream; - private FileChannel fileChannel; - - - @Override - public void onApplicationEvent(ApplicationEvent event) { - // 启动最后的预加载 - if (event instanceof ApplicationReadyEvent) { - // - checkPath(); - // 清理旧进程新文件 - File dataDir = FileUtil.file(ConfigBean.getInstance().getDataPath()); - List files = FileUtil.loopFiles(dataDir, 1, pathname -> pathname.getName().startsWith("pid.")); - files.forEach(FileUtil::del); - DefaultSystemLog.getLog().debug("clear old pid file success"); - try { - this.lockFile(); - } catch (IOException e) { - DefaultSystemLog.getLog().error("lockFile", e); - } - DefaultSystemLog.getLog().debug("lock pid file success"); - // 写入Jpom 信息 - JpomManifest jpomManifest = JpomManifest.getInstance(); - // 写入全局信息 - File appJpomFile = ConfigBean.getInstance().getApplicationJpomInfo(JpomApplication.getAppType()); - FileUtil.writeString(jpomManifest.toString(), appJpomFile, CharsetUtil.CHARSET_UTF_8); - // 检查更新文件 - checkUpdate(); - // - if (ApplicationEventLoad.class.isAssignableFrom(JpomApplication.getAppClass())) { - ApplicationEventLoad eventLoad = (ApplicationEventLoad) SpringUtil.getBean(JpomApplication.getAppClass()); - eventLoad.applicationLoad(); - } - Console.log("Jpom Successful start preparation. start loading module"); - } else if (event instanceof ContextClosedEvent) { - // 应用关闭 - this.unLockFile(); - // - FileUtil.del(ConfigBean.getInstance().getPidFile()); - // - File appJpomFile = ConfigBean.getInstance().getApplicationJpomInfo(JpomApplication.getAppType()); - FileUtil.del(appJpomFile); - } - } - - /** - * 解锁进程文件 - */ - private void unLockFile() { - if (lock != null) { - try { - lock.release(); - } catch (IOException ignored) { - } - } - IoUtil.close(lock); - IoUtil.close(fileChannel); - IoUtil.close(fileOutputStream); - } - - /** - * 锁住进程文件 - * - * @throws IOException IO - */ - private void lockFile() throws IOException { - this.fileOutputStream = new FileOutputStream(ConfigBean.getInstance().getPidFile(), true); - this.fileChannel = fileOutputStream.getChannel(); - while (true) { - try { - lock = fileChannel.lock(); - break; - } catch (OverlappingFileLockException | IOException e) { - DefaultSystemLog.getLog().warn("获取进程文件锁失败:" + e.getMessage()); - } - ThreadUtil.sleep(100); - } - } - - private static void checkPath() { - String path = ExtConfigBean.getInstance().getPath(); - String extConfigPath = null; - try { - extConfigPath = ExtConfigBean.getResource().getURL().toString(); - } catch (IOException ignored) { - } - File file = FileUtil.file(path); - try { - FileUtil.mkdir(file); - file = FileUtil.createTempFile("jpom", ".temp", file, true); - } catch (Exception e) { - DefaultSystemLog.getLog().error(StrUtil.format("Jpom创建数据目录失败,目录位置:{},请检查当前用户是否有此目录权限或修改配置文件:{}中的jpom.path为可创建目录的路径", path, extConfigPath), e); - System.exit(-1); - } - FileUtil.del(file); - // Console.log("", path); - Console.log("Jpom[{}] 当前数据路径:{} 外部配置文件路径:{}", JpomManifest.getInstance().getVersion(), path, extConfigPath); - } - - private static void checkUpdate() { - File runFile = JpomManifest.getRunPath().getParentFile(); - String upgrade = FileUtil.file(runFile, ConfigBean.UPGRADE).getAbsolutePath(); - JSONObject jsonObject = null; - try { - jsonObject = (JSONObject) JsonFileUtil.readJson(upgrade); - } catch (FileNotFoundException ignored) { - } - if (jsonObject == null) { - return; - } - String beforeJar = jsonObject.getString("beforeJar"); - if (StrUtil.isEmpty(beforeJar)) { - return; - } - File beforeJarFile = FileUtil.file(runFile, beforeJar); - if (beforeJarFile.exists()) { - File oldJars = FileUtil.file(runFile, "oldJars"); - FileUtil.mkdir(oldJars); - FileUtil.move(beforeJarFile, oldJars, true); - DefaultSystemLog.getLog().info("备份旧程序包:" + beforeJar); - } - // windows 备份日志 - // if (SystemUtil.getOsInfo().isWindows()) { - // boolean logBack = jsonObject.getBooleanValue("logBack"); - // String oldLogName = jsonObject.getString("oldLogName"); - // if (logBack && StrUtil.isNotEmpty(oldLogName)) { - // File scriptFile = JpomManifest.getScriptFile(); - // File oldLog = FileUtil.file(scriptFile.getParentFile(), oldLogName); - // if (oldLog.exists()) { - // File logBackDir = FileUtil.file(scriptFile.getParentFile(), "log"); - // FileUtil.move(oldLog, logBackDir, true); - // } - // } - // } - } - -} diff --git a/modules/common/src/main/java/io/jpom/common/JpomManifest.java b/modules/common/src/main/java/io/jpom/common/JpomManifest.java deleted file mode 100644 index 861d46d7e1..0000000000 --- a/modules/common/src/main/java/io/jpom/common/JpomManifest.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.BetweenFormatter; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.io.ManifestUtil; -import cn.hutool.core.lang.JarClassLoader; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.ZipUtil; -import cn.hutool.http.GlobalHeaders; -import cn.hutool.http.Header; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.system.ConfigBean; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.CommandUtil; -import io.jpom.util.JsonFileUtil; -import io.jpom.util.VersionUtils; -import org.springframework.util.Assert; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * Jpom 的运行信息 - * - * @author jiangzeyin - * @date 2019/4/7 - */ -public class JpomManifest { - - private volatile static JpomManifest JPOM_MANIFEST; - /** - * 当前版本 - */ - private String version = "dev"; - /** - * 打包时间 - */ - private String timeStamp; - /** - * 进程id - */ - private long pid = SystemUtil.getCurrentPID(); - /** - * 当前运行类型 - */ - private final Type type = JpomApplication.getAppType(); - /** - * 端口号 - */ - private int port; - /** - * Jpom 的数据目录 - */ - private String dataPath; - /** - * 系统名称 - */ - private final String osName = SystemUtil.getOsInfo().getName(); - - private static void init() { - if (JPOM_MANIFEST == null) { - synchronized (JpomManifest.class) { - if (JPOM_MANIFEST == null) { - JPOM_MANIFEST = new JpomManifest(); - File jarFile = getRunPath(); - Tuple jarVersion = getJarVersion(jarFile); - if (jarVersion != null) { - JPOM_MANIFEST.setVersion(jarVersion.get(0)); - JPOM_MANIFEST.setTimeStamp(jarVersion.get(1)); - } - } - String jpomTag = StrUtil.format("Jpom {}/{}", JPOM_MANIFEST.getType(), JPOM_MANIFEST.getVersion()); - GlobalHeaders.INSTANCE.header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 " + jpomTag, true); - } - } - } - - /** - * 根据 jar 文件解析 jpom 版本信息 - * - * @param jarFile 文件 - * @return 版本, 打包时间, mainClass - */ - private static Tuple getJarVersion(File jarFile) { - Manifest manifest = ManifestUtil.getManifest(jarFile); - if (manifest != null) { - Attributes attributes = manifest.getMainAttributes(); - String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); - if (version != null) { - // @see VersionUtils#getVersion() - String timeStamp = attributes.getValue("Jpom-Timestamp"); - timeStamp = parseJpomTime(timeStamp); - String mainClass = attributes.getValue(Attributes.Name.MAIN_CLASS); - return new Tuple(version, timeStamp, mainClass, jarFile); - } - } - return null; - } - - - private JpomManifest() { - } - - /** - * 单利模式获取Jpom 信息 - * - * @return this - */ - public static JpomManifest getInstance() { - init(); - return JPOM_MANIFEST; - } - - public Type getType() { - return type; - } - - public long getPid() { - return pid; - } - - public void setPid(int pid) { - this.pid = pid; - } - - /** - * 获取当前运行的版本号 - * - * @return 返回当前版本号 - * @see VersionUtils#getVersion() - */ - public String getVersion() { - return version; - } - - /** - * 判断当前是否为调试模式 - * - * @return jar 为非调试模式 - */ - public boolean isDebug() { - return "dev".equals(getVersion()); - } - - public void setVersion(String version) { - if (StrUtil.isNotEmpty(version)) { - this.version = version; - } - } - - public String getTimeStamp() { - if (timeStamp == null) { - long uptime = SystemUtil.getRuntimeMXBean().getUptime(); - long statTime = System.currentTimeMillis() - uptime; - return new DateTime(statTime).toString(); - } - return timeStamp; - } - - /** - * 装换打包时间 - * - * @param timeStamp utc时间 - */ - public void setTimeStamp(String timeStamp) { - this.timeStamp = timeStamp; - } - - public void setPort(int port) { - this.port = port; - } - - /** - * 程序运行的端口 - * - * @return 端口 - */ - public int getPort() { - if (port == 0) { - port = ConfigBean.getInstance().getPort(); - } - return port; - } - - public String getDataPath() { - if (StrUtil.isEmpty(dataPath)) { - dataPath = ConfigBean.getInstance().getDataPath(); - } - return dataPath; - } - - public void setDataPath(String dataPath) { - this.dataPath = dataPath; - } - - public String getUpTime() { - long uptime = SystemUtil.getRuntimeMXBean().getUptime(); - return DateUtil.formatBetween(uptime, BetweenFormatter.Level.SECOND); - } - - public String getOsName() { - return osName; - } - - @Override - public String toString() { - return JSON.toJSONString(this); - } - - /** - * 获取当前运行的路径 - * - * @return jar 或者classPath - */ - public static File getRunPath() { - URL location = ClassUtil.getLocation(JpomApplication.getAppClass()); - String file = location.getFile(); - String before = StrUtil.subBefore(file, "!", false); - return FileUtil.file(before); - } - - /** - * 转化时间 - * - * @param timeStamp time - * @return 默认使用utc - */ - private static String parseJpomTime(String timeStamp) { - if (StrUtil.isNotEmpty(timeStamp)) { - try { - DateTime dateTime = DateUtil.parseUTC(timeStamp); - return dateTime.toStringDefaultTimeZone(); - } catch (Exception e) { - return timeStamp; - } - } else { - return "dev"; - } - } - - /** - * 检查是否为jpom包 - * - * @param path 路径 - * @param type 类型 - * @return 结果消息 - * @see Type#getApplicationClass() - */ - public static JsonMessage checkJpomJar(String path, Type type) { - return checkJpomJar(path, type, true); - } - - /** - * 检查是否为jpom包 - * - * @param path 路径 - * @param type 类型 - * @param checkRepeat 是否检查版本重复 - * @return 结果消息 - * @see Type#getApplicationClass() - */ - public static JsonMessage checkJpomJar(String path, Type type, boolean checkRepeat) { - String version; - File jarFile = new File(path); - Tuple jarVersion = getJarVersion(jarFile); - if (jarVersion == null) { - return new JsonMessage<>(405, "jar 包文件不合法"); - } - try (JarFile jarFile1 = new JarFile(jarFile)) { - //Manifest manifest = jarFile1.getManifest(); - //Attributes attributes = manifest.getMainAttributes(); - String mainClass = jarVersion.get(2); - if (mainClass == null) { - return new JsonMessage<>(405, "清单文件中没有找到对应的MainClass属性"); - } - JarClassLoader jarClassLoader = JarClassLoader.load(jarFile); - try { - jarClassLoader.loadClass(mainClass); - } catch (ClassNotFoundException notFound) { - return new JsonMessage<>(405, "中没有找到对应的MainClass:" + mainClass); - } - String applicationClass = type.getApplicationClass(); - ZipEntry entry = jarFile1.getEntry(StrUtil.format("BOOT-INF/classes/{}.class", - StrUtil.replace(applicationClass, ".", StrUtil.SLASH))); - if (entry == null) { - return new JsonMessage<>(405, "此包不是Jpom【" + type.name() + "】包"); - } - version = jarVersion.get(0); - String timeStamp = jarVersion.get(1); - if (StrUtil.hasEmpty(version, timeStamp)) { - return new JsonMessage<>(405, "此包没有版本号或者打包时间"); - } - - if (checkRepeat && StrUtil.equals(version, JpomManifest.getInstance().getVersion()) && - StrUtil.equals(timeStamp, JpomManifest.getInstance().getTimeStamp())) { - return new JsonMessage<>(405, "新包和正在运行的包一致"); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("解析jar", e); - return new JsonMessage<>(500, " 解析错误:" + e.getMessage()); - } - return new JsonMessage<>(200, "", jarVersion); - } - - /** - * 发布包到对应运行路径 - * - * @param path 文件路径 - * @param version 新版本号 - */ - public static void releaseJar(String path, String version) { - releaseJar(path, version, false); - } - - public static void releaseJar(String path, String version, boolean override) { - File runFile = getRunPath(); - File runPath = runFile.getParentFile(); - if (!runPath.isDirectory()) { - throw new JpomRuntimeException(runPath.getAbsolutePath() + " error"); - } - String upgrade = FileUtil.file(runPath, ConfigBean.UPGRADE).getAbsolutePath(); - JSONObject jsonObject = null; - try { - jsonObject = (JSONObject) JsonFileUtil.readJson(upgrade); - } catch (FileNotFoundException ignored) { - } - if (jsonObject == null) { - jsonObject = new JSONObject(); - } - jsonObject.put("beforeJar", runFile.getName()); - // 如果升级的版本号一致 - if (StrUtil.equals(version, JpomManifest.getInstance().getVersion())) { - version = StrUtil.format("{}_{}", version, System.currentTimeMillis()); - } - String newFile = JpomApplication.getAppType().name() + "-" + version + FileUtil.JAR_FILE_EXT; - File to = FileUtil.file(runPath, newFile); - if (to.exists() && !override) { - throw new JpomRuntimeException(newFile + " 已经存在啦"); - } - FileUtil.move(new File(path), to, true); - jsonObject.put("newJar", newFile); - jsonObject.put("updateTime", new DateTime().toString()); - // 更新管理命令 - List newData = new LinkedList<>(); - // - String typeName = JpomApplication.getAppType().name().toLowerCase(); - final String[] oldName = new String[]{typeName + ".log"}; - final boolean[] logBack = {true}; - FileUtil.readLines(getScriptFile(), JpomApplication.getCharset(), (LineHandler) line -> { - if (!line.startsWith(String.valueOf(StrUtil.C_TAB)) && - !line.startsWith(String.valueOf(StrUtil.C_SPACE))) { - if (StrUtil.containsAny(line, "RUNJAR=")) { - // jar 包 - if ("sh".equals(CommandUtil.SUFFIX)) { - newData.add(StrUtil.format("RUNJAR=\"{}\"", newFile)); - } else if ("bat".equals(CommandUtil.SUFFIX)) { - newData.add(StrUtil.format("set RUNJAR={}", newFile)); - } else { - newData.add(line); - } - } else if (SystemUtil.getOsInfo().isWindows()) { - // windows 控制台文件相关 - if (StrUtil.containsAny(line, "set LogName=")) { - // - oldName[0] = CharSequenceUtil.splitToArray(line, "=")[0]; - newData.add(StrUtil.format("set LogName={}_{}.log", typeName, System.currentTimeMillis())); - } else if (StrUtil.containsAny(line, "set LogBack=")) { - // 记忆logBack - logBack[0] = Convert.toBool(CharSequenceUtil.splitToArray(line, "=")[1], true); - newData.add(line); - } else { - newData.add(line); - } - } else { - newData.add(line); - } - } else { - newData.add(line); - } - }); - // 新增升级次数 @author jzy 2021-08-04 - jsonObject.put("upgradeCount", jsonObject.getIntValue("upgradeCount")); - jsonObject.put("oldLogName", oldName[0]); - jsonObject.put("logBack", logBack[0]); - // - JsonFileUtil.saveJson(upgrade, jsonObject); - FileUtil.writeLines(newData, getScriptFile(), JpomApplication.getCharset()); - } - - /** - * 获取当前的管理名文件 - * - * @return file - */ - public static File getScriptFile() { - File runPath = getRunPath().getParentFile().getParentFile(); - String type = JpomApplication.getAppType().name(); - File scriptFile = FileUtil.file(runPath, StrUtil.format("{}.{}", type, CommandUtil.SUFFIX)); - if (!scriptFile.exists() || scriptFile.isDirectory()) { - throw new JpomRuntimeException("当前服务中没有命令脚本:" + StrUtil.format("{}.{}", type, CommandUtil.SUFFIX)); - } - return scriptFile; - } - - /** - * 解析 jpom 安装包 - * - * @param path 文件路径 - * @param type 查找类型 - * @param savePath 保存对文件夹 - * @return 结果文件 - */ - public static File zipFileFind(String path, Type type, String savePath) throws IOException { - String extName = FileUtil.extName(path); - if (StrUtil.endWithIgnoreCase(extName, "zip")) { - try (ZipFile zipFile = ZipUtil.toZipFile(FileUtil.file(path), CharsetUtil.CHARSET_UTF_8)) { - Optional first = zipFile.stream().filter((Predicate) zipEntry -> { - String name = zipEntry.getName().toLowerCase(); - String typeName = type.name().toLowerCase(); - return StrUtil.startWith(name, "lib/" + typeName) && StrUtil.endWith(name, ".jar"); - }).findFirst(); - Assert.state(first.isPresent(), "上传的压缩包不是 Jpom [" + type + "] 包"); - // - ZipEntry zipEntry = first.get(); - InputStream stream = ZipUtil.getStream(zipFile, zipEntry); - String name = FileUtil.getName(zipEntry.getName()); - return FileUtil.writeFromStream(stream, FileUtil.file(savePath, name)); - } - } else if (StrUtil.endWithIgnoreCase(extName, "jar")) { - return FileUtil.file(path); - } - throw new IllegalArgumentException("此文件不是 jpom 安装包"); - } -} diff --git a/modules/common/src/main/java/io/jpom/common/RemoteVersion.java b/modules/common/src/main/java/io/jpom/common/RemoteVersion.java deleted file mode 100644 index 82d1cf0c98..0000000000 --- a/modules/common/src/main/java/io/jpom/common/RemoteVersion.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpStatus; -import cn.hutool.http.HttpUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.system.ConfigBean; -import org.springframework.util.Assert; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * 远程的版本信息 - * - * - *
- * {
- * "tag_name": "v2.6.4",
- * "agentUrl": "",
- * "serverUrl": "",
- * "changelog": ""
- * }
- * 
- * - * @author bwcx_jzy - * @since 2021/9/19 - */ -public class RemoteVersion { - - /** - * 主 url 用于拉取远程版本信息 - */ - private static final String URL = "https://jpom.io/docs/release-versions.json"; - /** - * 检查间隔时间 - */ - private static final int CHECK_INTERVAL = 24; - - /** - * 版本信息 - */ - private String tagName; - /** - * 插件端下载地址 - */ - private String agentUrl; - /** - * 服务端下载地址 - */ - private String serverUrl; - /** - * 更新日志 (远程url) - */ - private String changelogUrl; - /** - * 更新日志 - */ - private String changelog; - /** - * 上次获取时间 - */ - private Long lastTime; - - /** - * 是否有新版本 - */ - private Boolean upgrade; - - public Boolean getUpgrade() { - return upgrade; - } - - public void setUpgrade(Boolean upgrade) { - this.upgrade = upgrade; - } - - public String getTagName() { - return tagName; - } - - public void setTagName(String tagName) { - this.tagName = tagName; - } - - public String getAgentUrl() { - return agentUrl; - } - - public void setAgentUrl(String agentUrl) { - this.agentUrl = agentUrl; - } - - public String getServerUrl() { - return serverUrl; - } - - public void setServerUrl(String serverUrl) { - this.serverUrl = serverUrl; - } - - public String getChangelog() { - return changelog; - } - - public void setChangelog(String changelog) { - this.changelog = changelog; - } - - public Long getLastTime() { - return lastTime; - } - - public void setLastTime(Long lastTime) { - this.lastTime = lastTime; - } - - public String getChangelogUrl() { - return changelogUrl; - } - - public void setChangelogUrl(String changelogUrl) { - this.changelogUrl = changelogUrl; - } - - @Override - public String toString() { - return JSONObject.toJSONString(this); - } - - /** - * 获取远程最新版本 - * - * @return 版本信息 - */ - public static RemoteVersion loadRemoteInfo() { - String body = StrUtil.EMPTY; - try { - // 获取缓存中到信息 - RemoteVersion remoteVersion = null; - // 远程获取 - String transitUrl = RemoteVersion.loadTransitUrl(); - if (StrUtil.isNotEmpty(transitUrl)) { - HttpRequest request = HttpUtil.createGet(transitUrl); - body = request.execute().body(); - // - remoteVersion = JSONObject.parseObject(body, RemoteVersion.class); - } - - if (remoteVersion == null || StrUtil.isEmpty(remoteVersion.getTagName())) { - // 没有版本信息 - return null; - } - // 缓存信息 - RemoteVersion.cacheLoadTime(remoteVersion); - return remoteVersion; - } catch (Exception e) { - DefaultSystemLog.getLog().warn("获取远程版本信息失败:{} {}", e.getMessage(), body); - return null; - } - } - - /** - * 获取第一层信息,用于中转 - * - * @return 中转URL - */ - private static String loadTransitUrl() { - String body = StrUtil.EMPTY; - try { - HttpRequest request = HttpUtil.createGet(URL); - body = request.execute().body(); - // - JSONObject jsonObject = JSONObject.parseObject(body); - return jsonObject.getString("url"); - } catch (Exception e) { - DefaultSystemLog.getLog().warn("获取远程版本信息失败:{} {}", e.getMessage(), body); - return null; - } - } - - /** - * 缓存信息 - * - * @param remoteVersion 远程版本信息 - */ - private static void cacheLoadTime(RemoteVersion remoteVersion) { - remoteVersion = ObjectUtil.defaultIfNull(remoteVersion, new RemoteVersion()); - remoteVersion.setLastTime(SystemClock.now()); - // 判断是否可以升级 - JpomManifest instance = JpomManifest.getInstance(); - if (!instance.isDebug()) { - String version = instance.getVersion(); - String tagName = remoteVersion.getTagName(); - tagName = StrUtil.removePrefixIgnoreCase(tagName, "v"); - remoteVersion.setUpgrade(StrUtil.compareVersion(version, tagName) < 0); - } else { - remoteVersion.setUpgrade(false); - } - // 检查是否存在下载地址 - Type type = instance.getType(); - String remoteUrl = type.getRemoteUrl(remoteVersion); - if (StrUtil.isEmpty(remoteUrl)) { - remoteVersion.setUpgrade(false); - } - // 获取 changelog - String changelogUrl = remoteVersion.getChangelogUrl(); - if (StrUtil.isNotEmpty(changelogUrl)) { - String body = HttpUtil.createGet(changelogUrl).execute().body(); - remoteVersion.setChangelog(body); - } - // - FileUtil.writeUtf8String(remoteVersion.toString(), getFile()); - } - - /** - * 当前缓存中的 远程版本信息 - * - * @return RemoteVersion - */ - public static RemoteVersion cacheInfo() { - if (!FileUtil.isFile(getFile())) { - return null; - } - RemoteVersion remoteVersion = null; - String fileStr = StrUtil.EMPTY; - try { - fileStr = FileUtil.readUtf8String(getFile()); - if (StrUtil.isEmpty(fileStr)) { - return null; - } - remoteVersion = JSONObject.parseObject(fileStr, RemoteVersion.class); - } catch (Exception e) { - DefaultSystemLog.getLog().warn("解析远程版本信息失败:{} {}", e.getMessage(), fileStr); - } - // 判断上次获取时间 - Long lastTime = remoteVersion == null ? 0 : remoteVersion.getLastTime(); - lastTime = ObjectUtil.defaultIfNull(lastTime, 0L); - long interval = SystemClock.now() - lastTime; - return interval >= TimeUnit.HOURS.toMillis(CHECK_INTERVAL) ? null : remoteVersion; - } - - /** - * 下载 - * - * @param savePath 下载文件保存路径 - * @param type 类型 - * @return 保存的全路径 - * @throws IOException 异常 - */ - public static Tuple download(String savePath, Type type) throws IOException { - RemoteVersion remoteVersion = loadRemoteInfo(); - Assert.notNull(remoteVersion, "没有可用的新版本升级:-1"); - // 检查是否存在下载地址 - String remoteUrl = type.getRemoteUrl(remoteVersion); - Assert.hasText(remoteUrl, "存在新版本,下载地址不可用"); - // 下载 - File downloadFileFromUrl = HttpUtil.downloadFileFromUrl(remoteUrl, savePath); - // 解析压缩包 - File file = JpomManifest.zipFileFind(FileUtil.getAbsolutePath(downloadFileFromUrl), type, savePath); - // 检查 - JsonMessage error = JpomManifest.checkJpomJar(FileUtil.getAbsolutePath(file), type); - Assert.state(error.getCode() == HttpStatus.HTTP_OK, error.getMsg()); - return error.getData(); - } - - /** - * 升级 - * - * @param savePath 下载文件保存路径 - * @throws IOException 异常 - */ - public static void upgrade(String savePath) throws IOException { - Type type = JpomManifest.getInstance().getType(); - // 下载 - Tuple data = download(savePath, type); - File file = data.get(3); - // 基础检查 - String path = FileUtil.getAbsolutePath(file); - String version = data.get(0); - JpomManifest.releaseJar(path, version); - // - JpomApplication.restart(); - } - - /** - * 保存的文件 - * - * @return file - */ - private static File getFile() { - return FileUtil.file(ConfigBean.getInstance().getDataPath(), ConfigBean.REMOTE_VERSION); - } -} diff --git a/modules/common/src/main/java/io/jpom/common/ServerOpenApi.java b/modules/common/src/main/java/io/jpom/common/ServerOpenApi.java deleted file mode 100644 index 80b1a4dad1..0000000000 --- a/modules/common/src/main/java/io/jpom/common/ServerOpenApi.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -/** - * Server 开发接口api 列表 - * - * @author bwcx_jzy - * @date 2019/8/5 - */ -public class ServerOpenApi { - - public static final String HEAD = "JPOM-TOKEN"; - - /** - * 用户的token - */ - public static final String USER_TOKEN_HEAD = "JPOM-USER-TOKEN"; - - /** - * 存放token的http head - */ - public static final String HTTP_HEAD_AUTHORIZATION = "Authorization"; - - public static final String API = "/api/"; - - public static final String UPDATE_NODE_INFO = API + "node/update"; - - /** - * 安装id - */ - public static final String INSTALL_ID = API + "/installId"; - - /** - * 触发构建, 第一级构建id,第二级token - */ - public static final String BUILD_TRIGGER_BUILD = API + "/build/{id}/{token}"; - - /** - * 触发构建(新), 第一级构建id,第二级token - */ - public static final String BUILD_TRIGGER_BUILD2 = API + "/build2/{id}/{token}"; -} diff --git a/modules/common/src/main/java/io/jpom/common/Type.java b/modules/common/src/main/java/io/jpom/common/Type.java deleted file mode 100644 index a906e61bf2..0000000000 --- a/modules/common/src/main/java/io/jpom/common/Type.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import java.util.function.Function; - -/** - * Jpom 程序类型 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -public enum Type { - /** - * 插件端 - */ - Agent("io.jpom.JpomAgentApplication", RemoteVersion::getAgentUrl, "KeepBx-Agent-System-JpomAgentApplication"), - /** - * 中心服务端 - */ - Server("io.jpom.JpomServerApplication", RemoteVersion::getServerUrl, "KeepBx-System-JpomServerApplication"), - ; - - private final Function remoteUrl; - private final String applicationClass; - private final String tag; - - Type(String applicationClass, Function remoteUrl, String tag) { - this.applicationClass = applicationClass; - this.remoteUrl = remoteUrl; - this.tag = tag; - } - - public String getRemoteUrl(RemoteVersion remoteVersion) { - return remoteUrl.apply(remoteVersion); - } - - public String getApplicationClass() { - return applicationClass; - } - - public String getTag() { - return tag; - } -} diff --git a/modules/common/src/main/java/io/jpom/common/interceptor/PluginFeatureInterceptor.java b/modules/common/src/main/java/io/jpom/common/interceptor/PluginFeatureInterceptor.java deleted file mode 100644 index 2033228d02..0000000000 --- a/modules/common/src/main/java/io/jpom/common/interceptor/PluginFeatureInterceptor.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.jiangzeyin.common.interceptor.BaseInterceptor; -import cn.jiangzeyin.common.interceptor.InterceptorPattens; -import io.jpom.plugin.*; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -/** - * 插件方法回调拦截器 - * - * @author bwcx_jzy - * @date 2019/8/13 - */ -@InterceptorPattens(sort = Integer.MAX_VALUE) -public class PluginFeatureInterceptor extends BaseInterceptor { - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { - super.postHandle(request, response, handler, modelAndView); - HandlerMethod handlerMethod = getHandlerMethod(handler); - if (handlerMethod == null) { - return; - } - Feature methodAnnotation = handlerMethod.getMethodAnnotation(Feature.class); - Feature annotation = handlerMethod.getBeanType().getAnnotation(Feature.class); - if (methodAnnotation != null && annotation != null) { - if (methodAnnotation.method() != MethodFeature.NULL && annotation.cls() != ClassFeature.NULL) { - List featureCallbacks = PluginFactory.getFeatureCallbacks(); - featureCallbacks.forEach(featureCallback -> featureCallback.postHandle(request, annotation.cls(), methodAnnotation.method())); - } - } - } -} diff --git a/modules/common/src/main/java/io/jpom/model/AfterOpt.java b/modules/common/src/main/java/io/jpom/model/AfterOpt.java deleted file mode 100644 index 7e1983b26e..0000000000 --- a/modules/common/src/main/java/io/jpom/model/AfterOpt.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * @author bwcx_jzy - * @date 2020/3/21 - */ -public enum AfterOpt implements BaseEnum { - /** - * 操作 - */ - No(0, "不做任何操作"), - /** - * 并发执行项目分发 - */ - Restart(1, "并发重启"), - /** - * 顺序执行项目分发 - */ - Order_Must_Restart(2, "完整顺序重启(有重启失败将结束本次)"), - /** - * 顺序执行项目分发 - */ - Order_Restart(3, "顺序重启(有重启失败将继续)"), - ; - private final int code; - private final String desc; - - AfterOpt(int code, String desc) { - this.code = code; - this.desc = desc; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } -} diff --git a/modules/common/src/main/java/io/jpom/model/AgentFileModel.java b/modules/common/src/main/java/io/jpom/model/AgentFileModel.java deleted file mode 100644 index 3467aa1c99..0000000000 --- a/modules/common/src/main/java/io/jpom/model/AgentFileModel.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * @author lf - */ -public class AgentFileModel extends BaseModel { - - /** - * 保存Agent文件 - */ - public static final String ID = "AGENT_FILE"; - - /** - * 文件大小 - */ - private long size = 0; - /** - * 保存路径 - */ - private String savePath; - /** - * 版本号 - */ - private String version; - /** - * jar 打包时间 - */ - private String timeStamp; - - public String getTimeStamp() { - return timeStamp; - } - - public void setTimeStamp(String timeStamp) { - this.timeStamp = timeStamp; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public String getSavePath() { - return savePath; - } - - public void setSavePath(String savePath) { - this.savePath = savePath; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } -} diff --git a/modules/common/src/main/java/io/jpom/model/BaseEnum.java b/modules/common/src/main/java/io/jpom/model/BaseEnum.java deleted file mode 100644 index c8a07023ca..0000000000 --- a/modules/common/src/main/java/io/jpom/model/BaseEnum.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * 基础枚举接口 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -public interface BaseEnum { - /** - * 缓存 - */ - class Cache { - private static final Map, Map> CLASS_MAP_MAP = new HashMap<>(); - private static final Map, JSONArray> JSON_ARRAY_MAP = new HashMap<>(); - } - - /** - * 枚举的code - * - * @return int - */ - int getCode(); - - /** - * 枚举的描述 - * - * @return 描述 - */ - String getDesc(); - - /** - * 将枚举转换为map - * - * @param t class - * @return mao - */ - static Map getMap(Class t) { - return Cache.CLASS_MAP_MAP.computeIfAbsent(t, aClass -> { - Map map1 = new HashMap<>(20); - try { - Method method = t.getMethod("values"); - BaseEnum[] baseEnums = (BaseEnum[]) method.invoke(null); - for (BaseEnum item : baseEnums) { - map1.put(item.getCode(), item); - } - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - DefaultSystemLog.getLog().error("enum error", e); - return null; - } - return map1; - }); - } - - /** - * 根据枚举获取枚举对象 - * - * @param t 枚举类型 - * @param code code - * @param 泛型 - * @return 对应的枚举 - */ - static T getEnum(Class t, int code) { - Map map = getMap(t); - if (map == null) { - return null; - } - return (T) map.get(code); - } - - /** - * 根据 code 获取描述 - * - * @param t class - * @param code code - * @return desc - */ - static String getDescByCode(Class t, int code) { - BaseEnum baseEnums = getEnum(t, code); - if (baseEnums == null) { - return null; - } - return baseEnums.getDesc(); - } - - /** - * 获取 json - * - * @param baseEnum 枚举对象 - * @return json - * @throws InvocationTargetException e - * @throws IllegalAccessException e - */ - static JSONObject toJSONObject(Enum baseEnum) throws InvocationTargetException, IllegalAccessException { - Class itemCls = baseEnum.getClass(); - Method[] methods = itemCls.getMethods(); - JSONObject jsonObject = new JSONObject(); - for (Method method : methods) { - String name = method.getName(); - if (!name.startsWith("get")) { - continue; - } - name = name.substring(3); - name = name.substring(0, 1).toLowerCase() + name.substring(1); - try { - itemCls.getDeclaredField(name); - } catch (NoSuchFieldException e) { - continue; - } - jsonObject.put(name, method.invoke(baseEnum)); - } - return jsonObject; - } - - /** - * 将枚举转化为数组 - * 包括里面所有属性 - * - * @param cls cls - * @return array - */ - static JSONArray toJSONArray(Class cls) { - if (!cls.isEnum()) { - throw new IllegalArgumentException("不是枚举"); - } - JSONArray getJsonArray = Cache.JSON_ARRAY_MAP.computeIfAbsent(cls, aClass -> { - JSONArray jsonArray = new JSONArray(); - try { - Method values = aClass.getMethod("values"); - Object[] objects = (Object[]) values.invoke(null); - for (Object item : objects) { - JSONObject jsonObject = toJSONObject((Enum) item); - jsonArray.add(jsonObject); - } - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - DefaultSystemLog.getLog().error("enum error", e); - return null; - } - return jsonArray; - }); - Objects.requireNonNull(getJsonArray); - return (JSONArray) getJsonArray.clone(); - } -} diff --git a/modules/common/src/main/java/io/jpom/model/BaseJsonModel.java b/modules/common/src/main/java/io/jpom/model/BaseJsonModel.java deleted file mode 100644 index 1c657b93b4..0000000000 --- a/modules/common/src/main/java/io/jpom/model/BaseJsonModel.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; - -import java.io.Serializable; - -/** - * json方法基础类 - * - * @author jiangzeyin - * @date 2019/4/10 - */ -public abstract class BaseJsonModel implements Serializable { - - @Override - public String toString() { - return JSON.toJSONString(this); - } - - public JSONObject toJson() { - return (JSONObject) JSONObject.toJSON(this); - } -} diff --git a/modules/common/src/main/java/io/jpom/model/BaseModel.java b/modules/common/src/main/java/io/jpom/model/BaseModel.java deleted file mode 100644 index 91608abffe..0000000000 --- a/modules/common/src/main/java/io/jpom/model/BaseModel.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * 基础实体(带id) - * - * @author jiangzeyin - * @date 2019/3/14 - */ -public abstract class BaseModel extends BaseJsonModel { - - private String id; - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } -} diff --git a/modules/common/src/main/java/io/jpom/model/RunMode.java b/modules/common/src/main/java/io/jpom/model/RunMode.java deleted file mode 100644 index d725873eee..0000000000 --- a/modules/common/src/main/java/io/jpom/model/RunMode.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * 项目的运行方式 - * - * @author jiangzeyin - * @date 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, -} diff --git a/modules/common/src/main/java/io/jpom/model/WebSocketMessageModel.java b/modules/common/src/main/java/io/jpom/model/WebSocketMessageModel.java deleted file mode 100644 index efdc89b56c..0000000000 --- a/modules/common/src/main/java/io/jpom/model/WebSocketMessageModel.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -import com.alibaba.fastjson.JSONObject; - -/** - * websocket发送和接收消息Model - * - * @author lf - */ -public class WebSocketMessageModel { - - private String command; - private String nodeId; - private Object params; - private Object data; - - public WebSocketMessageModel(String command, String nodeId) { - this.command = command; - this.nodeId = nodeId; - this.data = ""; - } - - public static WebSocketMessageModel getInstance(String message) { - JSONObject commandObj = JSONObject.parseObject(message); - String command = commandObj.getString("command"); - String nodeId = commandObj.getString("nodeId"); - WebSocketMessageModel model = new WebSocketMessageModel(command, nodeId); - model.setParams(commandObj.get("params")); - model.setData(commandObj.get("data")); - - return model; - } - - public String getCommand() { - return command; - } - - public void setCommand(String command) { - this.command = command; - } - - public Object getData() { - return data; - } - - public WebSocketMessageModel setData(Object data) { - this.data = data; - return this; - } - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public Object getParams() { - return params; - } - - public void setParams(Object params) { - this.params = params; - } - - @Override - public String toString() { - JSONObject result = new JSONObject(); - result.put("command", this.command); - result.put("nodeId", this.nodeId); - result.put("params", this.params); - result.put("data", this.data); - return result.toJSONString(); - } -} diff --git a/modules/common/src/main/java/io/jpom/model/data/AgentWhitelist.java b/modules/common/src/main/java/io/jpom/model/data/AgentWhitelist.java deleted file mode 100644 index dcc5fe2666..0000000000 --- a/modules/common/src/main/java/io/jpom/model/data/AgentWhitelist.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.collection.CollStreamUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseJpomController; -import io.jpom.model.BaseJsonModel; -import io.jpom.system.ExtConfigBean; -import org.springframework.util.Assert; - -import java.io.File; -import java.nio.charset.Charset; -import java.util.*; - -/** - * 白名单 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public class AgentWhitelist extends BaseJsonModel { - /** - * 项目目录白名单、日志文件白名单 - */ - private List project; - /** - * ssl 证书文件白名单 - */ - private List certificate; - /** - * nginx 配置文件 白名单 - */ - private List nginx; - - /** - * 运行编辑的后缀文件 - */ - private List allowEditSuffix; - - /** - * 运行远程下载的 host - */ - private Set allowRemoteDownloadHost; - - public Set getAllowRemoteDownloadHost() { - return allowRemoteDownloadHost; - } - - public void setAllowRemoteDownloadHost(Set allowRemoteDownloadHost) { - this.allowRemoteDownloadHost = allowRemoteDownloadHost; - } - - public List getAllowEditSuffix() { - return allowEditSuffix; - } - - public void setAllowEditSuffix(List allowEditSuffix) { - this.allowEditSuffix = allowEditSuffix; - } - - public List getProject() { - return project; - } - - public void setProject(List project) { - this.project = project; - } - - public List getCertificate() { - return certificate; - } - - public void setCertificate(List certificate) { - this.certificate = certificate; - } - - public List getNginx() { - return nginx; - } - - public void setNginx(List nginx) { - this.nginx = nginx; - } - - /** - * 格式化,判断是否与jpom 数据路径冲突 - * - * @param list list - * @return null 是有冲突的 - */ - public static List covertToArray(List list, String errorMsg) { - if (list == null) { - return null; - } - List array = new ArrayList<>(); - for (String s : list) { - String val = String.format("/%s/", s); - val = BaseJpomController.pathSafe(val); - if (StrUtil.SLASH.equals(val)) { - continue; - } - if (array.contains(val)) { - continue; - } - // 判断是否保护jpom 路径 - if (val == null || val.startsWith(ExtConfigBean.getInstance().getPath())) { - throw new IllegalArgumentException(errorMsg); - } - array.add(val); - } - return array; - } - - /** - * 转换为字符串 - * - * @param jsonArray jsonArray - * @return str - */ - public static String convertToLine(Collection jsonArray) { - try { - return CollUtil.join(jsonArray, StrUtil.CRLF); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - } - return ""; - } - - /** - * 判断是否在白名单列表中 - * - * @param list list - * @param path 对应项 - * @return false 不在列表中 - */ - public static boolean checkPath(List list, String path) { - if (list == null) { - return false; - } - if (StrUtil.isEmpty(path)) { - return false; - } - File file1, file2 = FileUtil.file(path); - for (String item : list) { - file1 = FileUtil.file(item); - if (FileUtil.pathEquals(file1, file2)) { - return true; - } - } - return false; - } - - /** - * 解析出json 中的白名单字段 - * - * @param projectInfo 项目的json对象 - * @param path 要比较的白名单 - * @return null 不是该白名单 - */ - public static String getItemWhitelistDirectory(JSONObject projectInfo, String path) { - String lib = projectInfo.getString("lib"); - if (lib.startsWith(path)) { - String itemWhitelistDirectory = lib.substring(0, path.length()); - lib = lib.substring(path.length()); - - projectInfo.put("lib", lib); - return itemWhitelistDirectory; - } - return null; - } - - /** - * 将字符串转为 list - * - * @param value 字符串 - * @param errorMsg 错误消息 - * @return list - */ - public static List parseToList(String value, String errorMsg) { - return parseToList(value, false, errorMsg); - } - - /** - * 将字符串转为 list - * - * @param value 字符串 - * @param required 是否为必填 - * @param errorMsg 错误消息 - * @return list - */ - public static List parseToList(String value, boolean required, String errorMsg) { - if (required) { - Assert.hasLength(value, errorMsg); - } else { - if (StrUtil.isEmpty(value)) { - return null; - } - } - List list = StrSplitter.splitTrim(value, StrUtil.LF, true); - Assert.notEmpty(list, errorMsg); - return list; - } - - /** - * 获取文件可以编辑的 文件编码格式 - * - * @param filename 文件名 - * @return charset 不能编辑情况会抛出异常 - */ - public static Charset checkFileSuffix(List allowEditSuffix, String filename) { - Assert.notEmpty(allowEditSuffix, "没有配置可允许编辑的后缀"); - Charset charset = AgentWhitelist.parserFileSuffixMap(allowEditSuffix, filename); - Assert.notNull(charset, "不允许编辑的文件后缀"); - return charset; - } - - /** - * 静默判断是否可以编辑对应的文件 - * - * @param filename 文件名 - * @return true 可以编辑 - */ - public static boolean checkSilentFileSuffix(List allowEditSuffix, String filename) { - if (CollUtil.isEmpty(allowEditSuffix)) { - return false; - } - Charset charset = AgentWhitelist.parserFileSuffixMap(allowEditSuffix, filename); - return charset != null; - } - - /** - * 根据文件名 和 可以配置列表 获取编码格式 - * - * @param allowEditSuffix 允许编辑的配置 - * @param filename 文件名 - * @return 没有匹配到 返回 null,没有配置编码格式即使用系统默认编码格式 - */ - private static Charset parserFileSuffixMap(List allowEditSuffix, String filename) { - Map map = CollStreamUtil.toMap(allowEditSuffix, s -> { - List split = StrUtil.split(s, StrUtil.AT); - return CollUtil.getFirst(split); - }, s -> { - List split = StrUtil.split(s, StrUtil.AT); - if (split.size() > 1) { - String last = CollUtil.getLast(split); - return CharsetUtil.charset(last); - } else { - return CharsetUtil.defaultCharset(); - } - }); - Set> entries = map.entrySet(); - for (Map.Entry entry : entries) { - if (StrUtil.endWithIgnoreCase(filename, StrUtil.DOT + entry.getKey())) { - return entry.getValue(); - } - } - return null; - } -} diff --git a/modules/common/src/main/java/io/jpom/model/system/AgentAutoUser.java b/modules/common/src/main/java/io/jpom/model/system/AgentAutoUser.java deleted file mode 100644 index a82603ac34..0000000000 --- a/modules/common/src/main/java/io/jpom/model/system/AgentAutoUser.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.system; - -import io.jpom.model.BaseJsonModel; - -/** - * agent 端自动生成的密码实体 - * - * @author jiangzeyin - * @date 2019/4/18 - */ -public class AgentAutoUser extends BaseJsonModel { - - private String agentName; - private String agentPwd; - - public String getAgentName() { - return agentName; - } - - public void setAgentName(String agentName) { - this.agentName = agentName; - } - - public String getAgentPwd() { - return agentPwd; - } - - public void setAgentPwd(String agentPwd) { - this.agentPwd = agentPwd; - } -} diff --git a/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java b/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java deleted file mode 100644 index a9994007c5..0000000000 --- a/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.plugin; - -/** - * 功能模块 - * - * @author bwcx_jzy - * @date 2019/8/13 - */ -public enum ClassFeature { - /** - * 没有 - */ - NULL(""), - NODE("节点管理"), - UPGRADE_NODE_LIST("节点升级"), - SEARCH_PROJECT("搜索项目"), - SSH("SSH管理"), - SSH_FILE("SSH文件管理"), - SSH_TERMINAL("SSH终端"), - SSH_TERMINAL_LOG("SSH终端日志"), - OUTGIVING("分发管理"), - OUTGIVING_LOG("分发日志"), - OUTGIVING_CONFIG_WHITELIST("分发白名单配置"), - MONITOR("项目监控"), - MONITOR_LOG("监控日志"), - OPT_MONITOR("操作监控"), - /** - * ssh - */ - BUILD("在线构建"), - BUILD_LOG("构建日志"), - BUILD_REPOSITORY("仓库信息"), - USER("用户管理"), - USER_LOG("操作日志"), - SYSTEM_EMAIL("邮箱配置"), - SYSTEM_CACHE("系统缓存"), - SYSTEM_LOG("系统日志"), - SYSTEM_UPGRADE("在线升级"), - SYSTEM_CONFIG("系统配置"), - SYSTEM_CONFIG_IP("系统配置IP白名单"), - SYSTEM_BACKUP("数据库备份"), - SYSTEM_WORKSPACE("工作空间"), - - //****************************************** 节点管理功能 - PROJECT("项目管理", ClassFeature.NODE), - PROJECT_FILE("项目文件管理", ClassFeature.NODE), - PROJECT_LOG("项目日志", ClassFeature.NODE), - PROJECT_CONSOLE("项目控制台", ClassFeature.NODE), - JDK_LIST("JDK管理", ClassFeature.NODE), - SCRIPT("脚本模板", ClassFeature.NODE), - TOMCAT("Tomcat", ClassFeature.NODE), - TOMCAT_FILE("Tomcat file", ClassFeature.NODE), - TOMCAT_LOG("Tomcat log", ClassFeature.NODE), - - NGINX("Nginx", ClassFeature.NODE), - SSL("ssl证书", ClassFeature.NODE), - NODE_CONFIG_WHITELIST("节点白名单配置", ClassFeature.NODE), - NODE_CONFIG("节点白名单配置", ClassFeature.NODE), - NODE_CACHE("节点缓存", ClassFeature.NODE), - NODE_LOG("节点系统日志", ClassFeature.NODE), - NODE_UPGRADE("节点在线升级", ClassFeature.NODE), - - -// PROJECT_RECOVER("项目回收", ClassFeature.NODE), - - ; - - private final String name; - - private ClassFeature parent; - - public String getName() { - return name; - } - - public ClassFeature getParent() { - return parent; - } - - ClassFeature(String name) { - this.name = name; - } - - ClassFeature(String name, ClassFeature parent) { - this.name = name; - this.parent = parent; - } -} diff --git a/modules/common/src/main/java/io/jpom/plugin/Feature.java b/modules/common/src/main/java/io/jpom/plugin/Feature.java deleted file mode 100644 index 3677b60ea2..0000000000 --- a/modules/common/src/main/java/io/jpom/plugin/Feature.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.plugin; - -import java.lang.annotation.*; - -/** - * 功能 - * - * @author bwcx_jzy - * @date 2019/8/13 - */ -@Documented -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Feature { - - /** - * 类 - * - * @return ClassFeature - */ - ClassFeature cls() default ClassFeature.NULL; - - /** - * 方法 - * - * @return MethodFeature - */ - MethodFeature method() default MethodFeature.NULL; -} diff --git a/modules/common/src/main/java/io/jpom/plugin/FeatureCallback.java b/modules/common/src/main/java/io/jpom/plugin/FeatureCallback.java deleted file mode 100644 index 3e289c1d56..0000000000 --- a/modules/common/src/main/java/io/jpom/plugin/FeatureCallback.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.plugin; - -import javax.servlet.http.HttpServletRequest; - -/** - * 功能回调 - * - * @author bwcx_jzy - * @date 2019/8/13 - */ -public interface FeatureCallback { - - /** - * 方法执行 - * - * @param request 请求对象 - * @param classFeature 类方法 - * @param methodFeature 方法 - * @param pars 辅助参数 - */ - void postHandle(HttpServletRequest request, - ClassFeature classFeature, - MethodFeature methodFeature, - Object... pars); -} diff --git a/modules/common/src/main/java/io/jpom/plugin/MethodFeature.java b/modules/common/src/main/java/io/jpom/plugin/MethodFeature.java deleted file mode 100644 index 623f312f70..0000000000 --- a/modules/common/src/main/java/io/jpom/plugin/MethodFeature.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.plugin; - -/** - * 功能方法 - * - * @author bwcx_jzy - * @date 2019/8/13 - */ -public enum MethodFeature { - /** - * 没有 - */ - NULL(""), - /** - * 文件管理 - */ -// FILE("文件管理"), - EDIT("修改、添加数据"), - DEL("删除数据"), - // INSTALL("安装"), - LIST("列表、查询"), - // TERMINAL("终端"), - DOWNLOAD("下载"), - // LOG("日志"), - UPLOAD("上传"), - // WHITELIST("白名单"), - EXECUTE("执行"), - // DEL_FILE("删除文件"), - // CACHE("缓存"), -// DEL_LOG("删除日志"), -// CONFIG("配置"), -// READ_FILE("读取文件"), - // GET_FILE_FOMAT("获取在线编辑文件格式"), -// UPDATE_CONFIG_FILE("更新文件"), - REMOTE_DOWNLOAD("下载远程文件"), - ; - - private final String name; - - public String getName() { - return name; - } - - MethodFeature(String name) { - this.name = name; - } -} diff --git a/modules/common/src/main/java/io/jpom/plugin/PluginFactory.java b/modules/common/src/main/java/io/jpom/plugin/PluginFactory.java deleted file mode 100644 index 7953c874e7..0000000000 --- a/modules/common/src/main/java/io/jpom/plugin/PluginFactory.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.plugin; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.JarClassLoader; -import cn.hutool.core.util.ClassLoaderUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.common.JpomManifest; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; - -import java.io.File; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; - -/** - * 插件工厂 - * - * @author bwcx_jzy - * @date 2019/8/13 - */ -public class PluginFactory implements ApplicationContextInitializer { - - private static final List FEATURE_CALLBACKS = new ArrayList<>(); - - /** - * 添加回调事件 - * - * @param featureCallback 回调 - */ - public static void addFeatureCallback(FeatureCallback featureCallback) { - FEATURE_CALLBACKS.add(featureCallback); - } - - public static List getFeatureCallbacks() { - return FEATURE_CALLBACKS; - } - - /** - * 正式环境添加依赖 - */ - private static void init() { - if (JpomManifest.getInstance().isDebug()) { - return; - } - File runPath = JpomManifest.getRunPath().getParentFile(); - File plugin = FileUtil.file(runPath, "plugin"); - if (!plugin.exists() || plugin.isFile()) { - return; - } - File[] files = plugin.listFiles(File::isDirectory); - if (files == null) { - return; - } - for (File file : files) { - File lib = FileUtil.file(file, "lib"); - if (!lib.exists() || lib.isFile()) { - continue; - } - File[] listFiles = lib.listFiles((dir, name) -> StrUtil.endWith(name, FileUtil.JAR_FILE_EXT, true)); - if (listFiles == null || listFiles.length <= 0) { - continue; - } - addPlugin(file.getName(), lib); - } - } - - private static void addPlugin(String pluginName, File file) { - DefaultSystemLog.getLog().info("加载:{}插件", pluginName); - ClassLoader contextClassLoader = ClassLoaderUtil.getClassLoader(); - JarClassLoader.loadJar((URLClassLoader) contextClassLoader, file); - } - - @Override - public void initialize(ConfigurableApplicationContext applicationContext) { - init(); - } -} diff --git a/modules/common/src/main/java/io/jpom/socket/ConsoleCommandOp.java b/modules/common/src/main/java/io/jpom/socket/ConsoleCommandOp.java deleted file mode 100644 index 9a5b13b87b..0000000000 --- a/modules/common/src/main/java/io/jpom/socket/ConsoleCommandOp.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -/** - * 控制台socket 操作枚举 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public enum ConsoleCommandOp { - /** - * 启动 - */ - start, - stop, - restart, - status, - /** - * 运行日志 - */ - showlog, - /** - * 查看内存信息 - */ - top, - /** - * 心跳 - */ - heart -} diff --git a/modules/common/src/main/java/io/jpom/system/AopLogInterface.java b/modules/common/src/main/java/io/jpom/system/AopLogInterface.java deleted file mode 100644 index a11735772c..0000000000 --- a/modules/common/src/main/java/io/jpom/system/AopLogInterface.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import org.aspectj.lang.ProceedingJoinPoint; - -/** - * 日志接口 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -public interface AopLogInterface { - /** - * 进入前 - * - * @param joinPoint point - */ - void before(ProceedingJoinPoint joinPoint); - - /** - * 执行后 - * - * @param value 结果 - */ - void afterReturning(Object value); -} diff --git a/modules/common/src/main/java/io/jpom/system/ConfigBean.java b/modules/common/src/main/java/io/jpom/system/ConfigBean.java deleted file mode 100644 index 259123e68d..0000000000 --- a/modules/common/src/main/java/io/jpom/system/ConfigBean.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.JpomApplication; -import io.jpom.common.JpomManifest; -import io.jpom.common.Type; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.config.ConfigFileApplicationListener; -import org.springframework.context.annotation.Configuration; - -import java.io.File; - -/** - * 配置项 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@Configuration -public class ConfigBean { - - /** - * 用户名header - */ - public static final String JPOM_SERVER_USER_NAME = "Jpom-Server-UserName"; - - public static final String JPOM_AGENT_AUTHORIZE = "Jpom-Agent-Authorize"; - - public static final String DATA = "data"; - - public static final int AUTHORIZE_ERROR = 900; - - /** - * 授权信息 - */ - public static final String AUTHORIZE = "agent_authorize.json"; - /** - * - */ - public static final String AUTHORIZE_USER_KEY = "jpom.authorize.agentName"; - /** - * - */ - public static final String AUTHORIZE_AUTHORIZE_KEY = "jpom.authorize.agentPwd"; - /** - * 程序升级信息文件 - */ - public static final String UPGRADE = "upgrade.json"; - /** - * 远程版本信息 - */ - public static final String REMOTE_VERSION = "remote_version.json"; - /** - * Jpom 程序运行的 application 标识 - */ - @Value("${jpom.applicationTag:}") - public String applicationTag; - /** - * 程序端口 - */ - @Value("${server.port}") - private int port; - - @Value("${" + ConfigFileApplicationListener.ACTIVE_PROFILES_PROPERTY + "}") - private String active; - - private volatile static ConfigBean configBean; - - /** - * 单利模式 - * - * @return config - */ - public static ConfigBean getInstance() { - if (configBean == null) { - synchronized (ConfigBean.class) { - if (configBean == null) { - configBean = SpringUtil.getBean(ConfigBean.class); - } - } - } - return configBean; - } - - public int getPort() { - return port; - } - - /** - * 获取项目运行数据存储文件夹路径 - * - * @return 文件夹路径 - */ - public String getDataPath() { - String dataPath = FileUtil.normalize(ExtConfigBean.getInstance().getPath() + StrUtil.SLASH + DATA); - FileUtil.mkdir(dataPath); - return dataPath; - } - - /** - * 获取pid文件 - * - * @return file - */ - public File getPidFile() { - return new File(getDataPath(), StrUtil.format("pid.{}.{}", - JpomApplication.getAppType().name(), JpomManifest.getInstance().getPid())); - } - - /** - * 获取当前项目全局 运行信息文件路径 - * - * @param type 程序类型 - * @return file - */ - public File getApplicationJpomInfo(Type type) { - return FileUtil.file(SystemUtil.getUserInfo().getTempDir(), "jpom", type.name()); - } - - /** - * 获取 agent 端自动生成的授权文件路径 - * - * @param dataPath 指定数据路径 - * @return file - */ - public String getAgentAutoAuthorizeFile(String dataPath) { - return FileUtil.normalize(dataPath + StrUtil.SLASH + ConfigBean.AUTHORIZE); - } - - /** - * 是否为 pro 模式运行 - * - * @return pro - */ - public boolean isPro() { - return StrUtil.equals(this.active, "pro"); - } - - /** - * 获取临时文件存储路径 - * - * @return file - */ - public File getTempPath() { - File file = new File(ConfigBean.getInstance().getDataPath()); - file = FileUtil.file(file, "temp"); - //new File(file.getPath() + "/temp/"); - FileUtil.mkdir(file); - return file; - } -} diff --git a/modules/common/src/main/java/io/jpom/system/ExtConfigBean.java b/modules/common/src/main/java/io/jpom/system/ExtConfigBean.java deleted file mode 100644 index a2e7227722..0000000000 --- a/modules/common/src/main/java/io/jpom/system/ExtConfigBean.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.JpomApplication; -import io.jpom.common.JpomManifest; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; - -import java.io.File; -import java.nio.charset.Charset; - -/** - * 外部资源配置 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@Configuration -public class ExtConfigBean { - - public static final String FILE_NAME = "extConfig.yml"; - - private static Resource resource; - /** - * 请求日志 - */ - @Value("${consoleLog.reqXss:true}") - private boolean consoleLogReqXss; - /** - * 请求响应 - */ - @Value("${consoleLog.reqResponse:true}") - private boolean consoleLogReqResponse; - - /** - * 日志文件的编码格式,如果没有指定就自动识别,自动识别可能出现不准确的情况 - */ - @Value("${log.fileCharset:}") - private String logFileCharset; - /** - * 初始读取日志文件行号 - */ - @Value("${log.intiReadLine:10}") - private int logInitReadLine; - /** - * - */ - private Charset logFileCharsets; - - public int getLogInitReadLine() { - if (logInitReadLine < 0) { - return 10; - } - return logInitReadLine; - } - - public Charset getLogFileCharset() { - return logFileCharsets; - } - - public boolean isConsoleLogReqResponse() { - return consoleLogReqResponse; - } - - public boolean isConsoleLogReqXss() { - return consoleLogReqXss; - } - - private static ExtConfigBean extConfigBean; - - /** - * 动态获取外部配置文件的 resource - * - * @return File - */ - public static Resource getResource() { - if (resource != null) { - return resource; - } - File file = JpomManifest.getRunPath(); - if (file.isFile()) { - file = file.getParentFile().getParentFile(); - file = new File(file, FILE_NAME); - if (file.exists() && file.isFile()) { - resource = new FileSystemResource(file); - return ExtConfigBean.resource; - } - } - resource = new ClassPathResource("/bin/" + FILE_NAME); - return ExtConfigBean.resource; - } - - public static File getResourceFile() { - File file = JpomManifest.getRunPath(); - file = file.getParentFile().getParentFile(); - file = new File(file, FILE_NAME); - return file; - } - - /** - * 单例 - * - * @return this - */ - public static ExtConfigBean getInstance() { - if (extConfigBean == null) { - extConfigBean = SpringUtil.getBean(ExtConfigBean.class); - // 读取配置的编码格式 - if (StrUtil.isNotBlank(extConfigBean.logFileCharset)) { - try { - extConfigBean.logFileCharsets = CharsetUtil.charset(extConfigBean.logFileCharset); - } catch (Exception ignored) { - } - } - } - return extConfigBean; - } - - /** - * 项目运行存储路径 - */ - @Value("${jpom.path}") - private String path; - - public String getPath() { - if (StrUtil.isEmpty(path)) { - if (JpomManifest.getInstance().isDebug()) { - // 调试模式 为根路径的 jpom文件 - String oldPath = ((SystemUtil.getOsInfo().isMac() ? "~" : "") + "/jpom/" + JpomApplication.getAppType().name() + StrUtil.SLASH).toLowerCase(); - File newFile = FileUtil.file(FileUtil.getUserHomeDir(), "jpom", JpomApplication.getAppType().name().toLowerCase()); - String absolutePath = FileUtil.getAbsolutePath(newFile); - if (FileUtil.exist(oldPath) && !FileUtil.equals(newFile, FileUtil.file(oldPath))) { - FileUtil.move(FileUtil.file(oldPath), newFile, true); - Console.log("数据目录位置发生变化:{} => {}", oldPath, absolutePath); - } - //Console.log("本地运行存储的数据:{}", absolutePath); - path = absolutePath; - } else { - // 获取当前项目运行路径的父级 - File file = JpomManifest.getRunPath(); - if (!file.exists() && !file.isFile()) { - throw new JpomRuntimeException("请配置运行路径属性【jpom.path】"); - } - File parentFile = file.getParentFile().getParentFile(); - path = FileUtil.getAbsolutePath(parentFile); - } - } - return path; - } - -// public String getAbsolutePath() { -// return FileUtil.getAbsolutePath(FileUtil.file(getPath())); -// } -} diff --git a/modules/common/src/main/java/io/jpom/system/ExtConfigEnvironmentPostProcessor.java b/modules/common/src/main/java/io/jpom/system/ExtConfigEnvironmentPostProcessor.java deleted file mode 100644 index 3e54d75bb6..0000000000 --- a/modules/common/src/main/java/io/jpom/system/ExtConfigEnvironmentPostProcessor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.lang.Console; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.env.EnvironmentPostProcessor; -import org.springframework.boot.env.YamlPropertySourceLoader; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.Resource; - -import java.util.List; - -/** - * 动态读取外部配置文件 - * - * @author jiangzeyin - * @date 2019/3/5 - */ -public class ExtConfigEnvironmentPostProcessor implements EnvironmentPostProcessor { - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); - Resource resource = ExtConfigBean.getResource(); - try { - List> propertySources = yamlPropertySourceLoader.load(ExtConfigBean.FILE_NAME, resource); - propertySources.forEach(propertySource -> environment.getPropertySources().addLast(propertySource)); - } catch (Exception e) { - Console.error(e.getMessage()); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/system/JpomRuntimeException.java b/modules/common/src/main/java/io/jpom/system/JpomRuntimeException.java deleted file mode 100644 index b2293731d8..0000000000 --- a/modules/common/src/main/java/io/jpom/system/JpomRuntimeException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.util.StrUtil; - -/** - * Jpom 运行错误 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public class JpomRuntimeException extends RuntimeException { - - public JpomRuntimeException(String message) { - super(message); - } - - public JpomRuntimeException(String message, Throwable throwable) { - super(StrUtil.format("{} {}", message, StrUtil.emptyToDefault(throwable.getMessage(), StrUtil.EMPTY)), throwable); - } -} diff --git a/modules/common/src/main/java/io/jpom/system/WebAopLog.java b/modules/common/src/main/java/io/jpom/system/WebAopLog.java deleted file mode 100644 index ad74f4f2b8..0000000000 --- a/modules/common/src/main/java/io/jpom/system/WebAopLog.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import ch.qos.logback.core.PropertyDefinerBase; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.interceptor.BaseCallbackController; -import io.jpom.JpomApplication; -import io.jpom.common.JpomManifest; -import io.jpom.util.StringUtil; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; -import org.springframework.stereotype.Component; - -import java.io.File; - -/** - * 自动记录日志 - * - * @author jiangzeyin - * @date 2017/5/11 - */ -@Aspect -@Component -public class WebAopLog extends PropertyDefinerBase { - - private static volatile AopLogInterface aopLogInterface; - - synchronized public static void setAopLogInterface(AopLogInterface aopLogInterface) { - WebAopLog.aopLogInterface = aopLogInterface; - } - - @Pointcut("execution(public * io.jpom.controller..*.*(..))") - public void webLog() { - // - } - - @Around(value = "webLog()", argNames = "joinPoint") - public Object around(ProceedingJoinPoint joinPoint) throws Throwable { - // 接收到请求,记录请求内容 - boolean consoleLogReqResponse = ExtConfigBean.getInstance().isConsoleLogReqResponse(); - Object proceed; - Object logResult = null; - try { - if (aopLogInterface != null) { - aopLogInterface.before(joinPoint); - } - proceed = joinPoint.proceed(); - logResult = proceed; - } catch (Throwable e) { - logResult = e; - throw e; - } finally { - if (aopLogInterface != null) { - aopLogInterface.afterReturning(logResult); - } - } - if (consoleLogReqResponse && logResult != null) { - DefaultSystemLog.getLog().info(BaseCallbackController.getRequestAttributes().getRequest().getRequestURI() + " :" + logResult); - } - return proceed; - } - - @Override - public String getPropertyValue() { - String path = StringUtil.getArgsValue(JpomApplication.getArgs(), "jpom.log"); - if (StrUtil.isEmpty(path)) { - // - File file = JpomManifest.getRunPath(); - if (file.isFile()) { - // jar 运行模式 - file = file.getParentFile().getParentFile(); - } else { - // 本地调试模式 @author jzy 2021-08-02 程序运行时候不影响打包 - file = FileUtil.file(FileUtil.getParent(file, 2), "log"); - Console.log("当前日志文件存储路径:" + FileUtil.getAbsolutePath(file)); - } - path = FileUtil.getAbsolutePath(file); - } - // 配置默认日志路径 - // DefaultSystemLog.configPath(path, false); - return path; - } -} diff --git a/modules/common/src/main/java/io/jpom/system/init/CheckDuplicateRun.java b/modules/common/src/main/java/io/jpom/system/init/CheckDuplicateRun.java deleted file mode 100644 index 819b10ef2a..0000000000 --- a/modules/common/src/main/java/io/jpom/system/init/CheckDuplicateRun.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.util.ObjectUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.JpomApplication; -import io.jpom.common.JpomManifest; -import io.jpom.util.JvmUtil; - -/** - * @author bwcx_jzy - * @date 2019/9/5 - */ -class CheckDuplicateRun { - - static void check() { - try { - Class appClass = JpomApplication.getAppClass(); - String pid = String.valueOf(JpomManifest.getInstance().getPid()); - Integer mainClassPid = JvmUtil.findMainClassPid(appClass.getName()); - if (mainClassPid == null || pid.equals(ObjectUtil.toString(mainClassPid))) { - return; - } - DefaultSystemLog.getLog().warn("Jpom 程序建议一个机器上只运行一个对应的程序:" + JpomApplication.getAppType() + " pid:" + mainClassPid); - } catch (Exception e) { - DefaultSystemLog.getLog().error("检查异常", e); - } - } -} - diff --git a/modules/common/src/main/java/io/jpom/system/init/CheckPath.java b/modules/common/src/main/java/io/jpom/system/init/CheckPath.java deleted file mode 100644 index e0ffe4509e..0000000000 --- a/modules/common/src/main/java/io/jpom/system/init/CheckPath.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import io.jpom.system.ConfigBean; -import io.jpom.system.ExtConfigBean; -import org.springframework.http.HttpMethod; - -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.nio.file.AccessDeniedException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * 数据目录权限检查 - * - * @author jiangzeyin - * @date 2019/3/26 - */ -@PreLoadClass(value = Integer.MIN_VALUE) -public class CheckPath { -// /** -// * 待检查的类 -// */ -// private static final String[] CLASS_NAME = new String[]{"sun.jvmstat.monitor.MonitorException", "com.sun.tools.attach.VirtualMachine"}; -// -// -// @PreLoadMethod(1) -// private static void checkToolsJar() { -// try { -// for (String item : CLASS_NAME) { -// ClassUtil.loadClass(item, false); -// } -// } catch (Exception e) { -// File file = StringUtil.getToolsJar(); -// if (file.exists() && file.isFile()) { -// DefaultSystemLog.getLog().error("Jpom未能正常加载tools.jar,请检查当前系统环境变量是否配置:JAVA_HOME,或者检查Jpom管理命令是否正确", e); -// } else { -// DefaultSystemLog.getLog().error("当前JDK中没有找到tools.jar,请检查当前JDK是否安装完整,文件完整路径是:" + file.getAbsolutePath(), e); -// } -// System.exit(-1); -// } -// } - - /** - * 判断是否重复运行 - */ - @PreLoadMethod(2) - private static void checkDuplicateRun() { - CheckDuplicateRun.check(); - } - - @PreLoadMethod(3) - private static void reqXssLog() { - if (!ExtConfigBean.getInstance().isConsoleLogReqXss()) { - // 不在控制台记录请求日志信息 - DefaultSystemLog.setLogCallback(new DefaultSystemLog.LogCallback() { - @Override - public void log(DefaultSystemLog.LogType type, Object... log) { - // - if (type == DefaultSystemLog.LogType.REQUEST_ERROR) { - DefaultSystemLog.getLog().info(Arrays.toString(log)); - } - } - - @Override - public void logStart(HttpServletRequest request, String id, String url, HttpMethod httpMethod, String ip, Map parameters, Map header) { - - } - - @Override - public void logError(String id, int status) { - - } - - @Override - public void logTimeOut(String id, long time) { - - } - }); - } - } - - @PreLoadMethod(4) - private static void clearTemp() { - File file = ConfigBean.getInstance().getTempPath(); - /** - * @author Hotstrip - * use Hutool's FileUtil.del method just put file as param not file's path - * or else, may be return Accessdenied exception - */ - try { - FileUtil.del(file); - } catch (Exception e) { - // Try again jzy 2021-07-31 - DefaultSystemLog.getLog().warn("尝试删除临时文件夹失败,尝试处理只读权限:{}", e.getMessage()); - List files = FileUtil.loopFiles(file); - long count = files.stream().map(file12 -> file12.setWritable(true)).filter(aBoolean -> aBoolean).count(); - DefaultSystemLog.getLog().warn("临时文件夹累计文件数:{},处理成功数:{}", CollUtil.size(files), count); - try { - FileUtil.del(file.toPath()); - } catch (Exception e1) { - e1.addSuppressed(e); - boolean causedBy = ExceptionUtil.isCausedBy(e1, AccessDeniedException.class); - if (causedBy) { - DefaultSystemLog.getLog().error("清除临时文件失败,请手动清理:" + FileUtil.getAbsolutePath(file), e); - return; - } - DefaultSystemLog.getLog().error("清除临时文件失败,请检查目录:" + FileUtil.getAbsolutePath(file), e); - } - } - } -} diff --git a/modules/common/src/main/java/io/jpom/system/init/ConsoleStartSuccess.java b/modules/common/src/main/java/io/jpom/system/init/ConsoleStartSuccess.java deleted file mode 100644 index fc039f4f58..0000000000 --- a/modules/common/src/main/java/io/jpom/system/init/ConsoleStartSuccess.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.lang.Console; -import cn.hutool.core.net.NetUtil; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import io.jpom.common.JpomManifest; -import io.jpom.common.Type; -import io.jpom.system.ConfigBean; - -/** - * @author bwcx_jzy - * @since Created Time 2021/8/2 - */ -@PreLoadClass(value = Integer.MAX_VALUE) -public class ConsoleStartSuccess { - - /** - * 输出启动成功的 日志 - */ - @PreLoadMethod(value = Integer.MAX_VALUE) - private static void success() { - Type type = JpomManifest.getInstance().getType(); - if (type == Type.Server) { - Console.log(type + "启动成功,Can use happily => http://{}:{} 【The current address is for reference only】", NetUtil.getLocalhostStr(), ConfigBean.getInstance().getPort()); - } else if (type == Type.Agent) { - Console.log(type + "启动成功,请到服务端配置使用,当前节点地址 => http://{}:{} 【The current address is for reference only】", NetUtil.getLocalhostStr(), ConfigBean.getInstance().getPort()); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/system/init/package-info.java b/modules/common/src/main/java/io/jpom/system/init/package-info.java deleted file mode 100644 index ee862d28bd..0000000000 --- a/modules/common/src/main/java/io/jpom/system/init/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; \ No newline at end of file diff --git a/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java b/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java deleted file mode 100644 index 2d63cbc253..0000000000 --- a/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.system.JpomRuntimeException; -import org.springframework.web.socket.WebSocketSession; - -import javax.websocket.Session; -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * 文件跟随器工具 - * - * @author bwcx_jzy - * @date 2019/7/21 - */ -public abstract class BaseFileTailWatcher { - - protected FileTailWatcherRun tailWatcherRun; - protected File logFile; - - /** - * 所有会话 - */ - protected final Set socketSessions = new HashSet<>(); - - public BaseFileTailWatcher(File logFile) throws IOException { - this.logFile = logFile; - this.tailWatcherRun = new FileTailWatcherRun(logFile, this::sendAll); - } - - protected void send(T session, String msg) { - try { - if (session instanceof Session) { - SocketSessionUtil.send((Session) session, msg); - } else if (session instanceof WebSocketSession) { - SocketSessionUtil.send((WebSocketSession) session, msg); - } else { - throw new JpomRuntimeException("没有对应类型"); - } - } catch (IOException ignored) { - } - } - - /** - * 有新的日志 - * - * @param msg 日志 - */ - private void sendAll(String msg) { - Iterator iterator = socketSessions.iterator(); - while (iterator.hasNext()) { - T socketSession = iterator.next(); - try { - this.send(socketSession, msg); - } catch (Exception e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - iterator.remove(); - } - } - if (this.socketSessions.isEmpty()) { - this.close(); - } - } - - /** - * 添加监听会话 - * - * @param name 文件名 - * @param session 会话 - */ - protected void add(T session, String name) { - if (this.socketSessions.contains(session) || this.socketSessions.add(session)) { - LimitQueue limitQueue = this.tailWatcherRun.getLimitQueue(); - if (limitQueue.size() <= 0) { - this.send(session, "日志文件为空"); - return; - } - this.send(session, StrUtil.format("监听{}日志成功,目前共有{}人正在查看", name, this.socketSessions.size())); - // 开发发送头信息 - for (String s : limitQueue) { - this.send(session, s); - } - } - // else { - // this.send(session, "添加日志监听失败"); - // } - } - - /** - * 关闭 - */ - protected void close() { - this.tailWatcherRun.close(); - } -} diff --git a/modules/common/src/main/java/io/jpom/util/CharsetDetector.java b/modules/common/src/main/java/io/jpom/util/CharsetDetector.java deleted file mode 100644 index f3d20d944e..0000000000 --- a/modules/common/src/main/java/io/jpom/util/CharsetDetector.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import org.mozilla.intl.chardet.nsDetector; -import org.mozilla.intl.chardet.nsICharsetDetectionObserver; -import org.mozilla.intl.chardet.nsPSMDetector; - -import java.io.*; - -/** - * 文件编码识别器 - * - * @author Administrator - */ -public class CharsetDetector implements nsICharsetDetectionObserver { - - private boolean found = false; - private String result; - - public String detectChineseCharset(File file) throws IOException { - if (!file.exists()) { - throw new FileNotFoundException(file.getAbsolutePath()); - } - String[] val = detectChineseCharset(new FileInputStream(file)); - if (val == null || val.length <= 0) { - return null; - } - return val[0]; - } - - private String[] detectChineseCharset(InputStream in) throws IOException { - // Initalize the nsDetector() ; - nsDetector det = new nsDetector(nsPSMDetector.CHINESE); - // Set an observer... - // The Notify() will be called when a matching charset is found. - det.Init(this); - BufferedInputStream imp = new BufferedInputStream(in); - byte[] buf = new byte[1024]; - int len; - boolean isAscii = true; - while ((len = imp.read(buf, 0, buf.length)) != -1) { - // Check if the stream is only ascii. - if (isAscii) { - isAscii = det.isAscii(buf, len); - } - // DoIt if non-ascii and not done yet. - if (!isAscii) { - if (det.DoIt(buf, len, false)) { - break; - } - } - } - imp.close(); - in.close(); - det.DataEnd(); - String[] prob; - if (isAscii) { - found = true; - prob = new String[]{"ASCII"}; - } else if (found) { - prob = new String[]{result}; - } else { - prob = det.getProbableCharsets(); - } - return prob; - } - - @Override - public void Notify(String charset) { - found = true; - result = charset; - } -} \ No newline at end of file diff --git a/modules/common/src/main/java/io/jpom/util/CommandUtil.java b/modules/common/src/main/java/io/jpom/util/CommandUtil.java deleted file mode 100644 index f9a51ff58f..0000000000 --- a/modules/common/src/main/java/io/jpom/util/CommandUtil.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RuntimeUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.DefaultSystemLog; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * 命令行工具 - * - * @author jiangzeyin - * @date 2019/4/15 - */ -public class CommandUtil { - /** - * 系统命令 - */ - private static final List COMMAND = new ArrayList<>(); - /** - * 文件后缀 - */ - public static final String SUFFIX; - - static { - if (SystemUtil.getOsInfo().isLinux()) { - //执行linux系统命令 - COMMAND.add("/bin/sh"); - COMMAND.add("-c"); - } else if (SystemUtil.getOsInfo().isMac()) { - COMMAND.add("/bin/sh"); - COMMAND.add("-c"); - } else { - COMMAND.add("cmd"); - COMMAND.add("/c"); - } - - if (SystemUtil.getOsInfo().isWindows()) { - SUFFIX = "bat"; - } else { - SUFFIX = "sh"; - } - } - - public static List getCommand() { - return ObjectUtil.clone(COMMAND); - } - - /*public static String execCommand(String command) { - String newCommand = StrUtil.replace(command, StrUtil.CRLF, StrUtil.SPACE); - newCommand = StrUtil.replace(newCommand, StrUtil.LF, StrUtil.SPACE); - String result = "error"; - try { - result = exec(new String[]{newCommand}, null); - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行命令异常", e); - result += e.getMessage(); - } - return result; - }*/ - - public static String execSystemCommand(String command) { - return execSystemCommand(command, null); - } - - /** - * 在指定文件夹下执行命令 - * - * @param command 命令 - * @param file 文件夹 - * @return msg - */ - public static String execSystemCommand(String command, File file) { - String newCommand = StrUtil.replace(command, StrUtil.CRLF, StrUtil.SPACE); - newCommand = StrUtil.replace(newCommand, StrUtil.LF, StrUtil.SPACE); - String result = "error"; - try { - List commands = getCommand(); - commands.add(newCommand); - String[] cmd = commands.toArray(new String[]{}); - result = exec(cmd, file); - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行命令异常", e); - result += e.getMessage(); - } - return result; - } - - /** - * 执行命令 - * - * @param cmd 命令行 - * @return 结果 - * @throws IOException IO - */ - private static String exec(String[] cmd, File file) throws IOException { - DefaultSystemLog.getLog().info(Arrays.toString(cmd)); - Process process = new ProcessBuilder(cmd).directory(file).redirectErrorStream(true).start(); - return RuntimeUtil.getResult(process); - } - - /** - * 异步执行命令 - * - * @param file 文件夹 - * @param command 命令 - * @throws IOException 异常 - */ - public static void asyncExeLocalCommand(File file, String command) throws Exception { - String newCommand = StrUtil.replace(command, StrUtil.CRLF, StrUtil.SPACE); - newCommand = StrUtil.replace(newCommand, StrUtil.LF, StrUtil.SPACE); - // - DefaultSystemLog.getLog().info(newCommand); - List commands = getCommand(); - commands.add(newCommand); - ProcessBuilder pb = new ProcessBuilder(commands); - if (file != null) { - pb.directory(file); - } - pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); - pb.redirectError(ProcessBuilder.Redirect.INHERIT); - pb.redirectInput(ProcessBuilder.Redirect.INHERIT); - pb.start(); - } - - /** - * 判断是否包含删除命令 - * - * @param script 命令行 - * @return true 包含 - */ - public static boolean checkContainsDel(String script) { - // 判断删除 - String[] commands = StrUtil.splitToArray(script, StrUtil.LF); - for (String commandItem : commands) { - if (checkContainsDelItem(commandItem)) { - return true; - } - } - return false; - } - - private static boolean checkContainsDelItem(String script) { - String[] split = StrUtil.splitToArray(script, StrUtil.SPACE); - if (SystemUtil.getOsInfo().isWindows()) { - for (String s : split) { - if (StrUtil.startWithAny(s, "rd", "del")) { - return true; - } - if (StrUtil.containsAnyIgnoreCase(s, " rd", " del")) { - return true; - } - } - } else { - for (String s : split) { - if (StrUtil.startWithAny(s, "rm", "\\rm")) { - return true; - } - if (StrUtil.containsAnyIgnoreCase(s, " rm", " \\rm", "&rm", "&\\rm")) { - return true; - } - } - } - return false; - } -} diff --git a/modules/common/src/main/java/io/jpom/util/CronUtils.java b/modules/common/src/main/java/io/jpom/util/CronUtils.java deleted file mode 100644 index 88dd1a3770..0000000000 --- a/modules/common/src/main/java/io/jpom/util/CronUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.cron.CronUtil; -import cn.hutool.cron.Scheduler; - -/** - * @author bwcx_jzy - * @date 2019/7/12 - **/ -public class CronUtils { - - /** - * - */ - public static void start() { - // 开启秒级 - //CronUtil.setMatchSecond(true); - // - Scheduler scheduler = CronUtil.getScheduler(); - if (!scheduler.isStarted()) { - CronUtil.start(); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/util/FileTailWatcherRun.java b/modules/common/src/main/java/io/jpom/util/FileTailWatcherRun.java deleted file mode 100644 index d2186cd151..0000000000 --- a/modules/common/src/main/java/io/jpom/util/FileTailWatcherRun.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.io.file.FileMode; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.system.ExtConfigBean; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnsupportedCharsetException; - -/** - * @author bwcx_jzy - * @date 2019/7/21 - */ -public class FileTailWatcherRun implements Runnable { - /** - * 缓存近10条 - */ - private final LimitQueue limitQueue = new LimitQueue<>(ExtConfigBean.getInstance().getLogInitReadLine()); - private final RandomAccessFile randomFile; - /** - * 是否已经开始执行 - */ - private boolean start = false; - private final Charset charset; - private final LineHandler lineHandler; - - public LimitQueue getLimitQueue() { - return limitQueue; - } - - FileTailWatcherRun(File file, LineHandler lineHandler) throws IOException { - this.lineHandler = lineHandler; - this.randomFile = new RandomAccessFile(file, FileMode.r.name()); - Charset detSet = ExtConfigBean.getInstance().getLogFileCharset(); - if (detSet == null) { - String charsetName = new CharsetDetector().detectChineseCharset(file); - try { - detSet = CharsetUtil.charset(charsetName); - } catch (UnsupportedCharsetException e) { - DefaultSystemLog.getLog().warn("自动识别文件编码格式错误:{},{}", charsetName, e.getMessage()); - detSet = CharsetUtil.CHARSET_UTF_8; - } - detSet = (detSet == StandardCharsets.US_ASCII) ? CharsetUtil.CHARSET_UTF_8 : detSet; - } - this.charset = detSet; - if (file.length() > 0) { - // 开始读取 - this.startRead(); - } - } - - private void startRead() throws IOException { - if (ExtConfigBean.getInstance().getLogInitReadLine() == 0) { - // 不初始读取 - return; - } - long len = randomFile.length(); - long start = randomFile.getFilePointer(); - long nextEnd = start + len - 1; - randomFile.seek(nextEnd); - int c; - while (nextEnd > start) { - // 满 - if (limitQueue.full()) { - break; - } - c = randomFile.read(); - if (c == CharUtil.LF || c == CharUtil.CR) { - this.readLine(); - nextEnd--; - } - nextEnd--; - randomFile.seek(nextEnd); - if (nextEnd == 0) { - // 当文件指针退至文件开始处,输出第一行 - this.readLine(); - break; - } - } - // 移动到尾部 - randomFile.seek(len); - } - - private void readLine() throws IOException { - String line = randomFile.readLine(); - if (line != null) { - line = CharsetUtil.convert(line, CharsetUtil.CHARSET_ISO_8859_1, charset); - limitQueue.offerFirst(line); - } - } - - - /** - * 读取文件内容 - * - * @throws IOException IO - */ - private void read() throws IOException { - final long currentLength = randomFile.length(); - final long position = randomFile.getFilePointer(); - if (0 == currentLength || currentLength == position) { - // 内容长度不变时忽略此次 - return; - } else if (currentLength < position) { - // 如果内容变短,说明文件做了删改,回到内容末尾 - randomFile.seek(currentLength); - return; - } - String tmp; - while ((tmp = randomFile.readLine()) != null) { - tmp = CharsetUtil.convert(tmp, CharsetUtil.CHARSET_ISO_8859_1, charset); - limitQueue.offer(tmp); - lineHandler.handle(tmp); - } - // 记录当前读到的位置 - this.randomFile.seek(currentLength); - } - - - /** - * 开始监听 - */ - public void start() { - if (this.start) { - return; - } - this.start = true; - ThreadUtil.execute(this); - - } - - @Override - public void run() { - while (this.start) { - try { - this.read(); - } catch (IOException e) { - DefaultSystemLog.getLog().error("读取文件发送异常", e); - lineHandler.handle("读取文件发生异常:" + e.getMessage()); - break; - } - ThreadUtil.sleep(500); - } - this.close(); - } - - public void close() { - this.start = false; - IoUtil.close(this.randomFile); - } -} diff --git a/modules/common/src/main/java/io/jpom/util/JsonFileUtil.java b/modules/common/src/main/java/io/jpom/util/JsonFileUtil.java deleted file mode 100644 index 7e8a00a82c..0000000000 --- a/modules/common/src/main/java/io/jpom/util/JsonFileUtil.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.system.JpomRuntimeException; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.Set; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * json 文件读写工具 - * - * @author jiangzeyin - * @date 2017/5/15 - */ -public class JsonFileUtil { - private static final ReentrantReadWriteLock FILE_LOCK = new ReentrantReadWriteLock(); - private final static ReentrantReadWriteLock.ReadLock READ_LOCK = FILE_LOCK.readLock(); - private final static ReentrantReadWriteLock.WriteLock WRITE_LOCK = FILE_LOCK.writeLock(); - - /** - * 读取json 文件,同步 - * - * @param path 路径 - * @return JSON - * @throws FileNotFoundException 文件异常 - */ - public static JSON readJson(String path) throws FileNotFoundException { - File file = new File(path); - if (!file.exists()) { - throw new FileNotFoundException("没有找到对应配置文件:" + path); - } - READ_LOCK.lock(); - // 防止多线程操作文件异常 - try { - String json = FileUtil.readString(file, CharsetUtil.UTF_8); - if (StrUtil.isEmpty(json)) { - return new JSONObject(); - } - try { - return (JSON) JSON.parse(json); - } catch (Exception e) { - throw new JpomRuntimeException("数据文件内容错误,请检查文件是否被非法修改:" + path, e); - } - } finally { - READ_LOCK.unlock(); - } - } - - /** - * 保存json 文件,同步 - * - * @param path 路径 - * @param json 新的json内容 - */ - public static void saveJson(String path, JSON json) { - WRITE_LOCK.lock(); - try { - // 输出格式化后的json 字符串 - String newsJson = JSON.toJSONString(json, true); - FileUtil.writeString(newsJson, path, CharsetUtil.UTF_8); - } finally { - WRITE_LOCK.unlock(); - } - } - - public static JSONObject arrayToObjById(JSONArray array) { - JSONObject jsonObject = new JSONObject(); - array.forEach(o -> { - JSONObject jsonObject1 = (JSONObject) o; - jsonObject.put(jsonObject1.getString("id"), jsonObject1); - }); - return jsonObject; - } - - public static JSONArray formatToArray(JSONObject jsonObject) { - if (jsonObject == null) { - return new JSONArray(); - } - Set setKey = jsonObject.keySet(); - JSONArray jsonArray = new JSONArray(); - for (String key : setKey) { - jsonArray.add(jsonObject.getJSONObject(key)); - } - return jsonArray; - } -} diff --git a/modules/common/src/main/java/io/jpom/util/JvmUtil.java b/modules/common/src/main/java/io/jpom/util/JvmUtil.java deleted file mode 100644 index f3b9fd24dd..0000000000 --- a/modules/common/src/main/java/io/jpom/util/JvmUtil.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.StrUtil; - -import java.io.File; -import java.util.List; -import java.util.Optional; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -/** - * jvm jmx 工具 - * - * @author jiangzeyin - * @date 2019/4/13 - */ -public class JvmUtil { - - /** - * 旧版jpom进程标记 - */ - private static final String OLD_JPOM_PID_TAG = "Dapplication"; - /** - * 旧版jpom进程标记 - */ - private static final String OLD2_JPOM_PID_TAG = "Jpom.application"; - private static final String POM_PID_TAG = "DJpom.application"; - - /** - * 获取进程标识 - * - * @param id i - * @param path 路径 - * @return str - */ - public static String getJpomPidTag(String id, String path) { - return String.format("-%s=%s -DJpom.basedir=%s", POM_PID_TAG, id, path); - } - - - /** - * 获取当前系统运行的java 程序个数 - * - * @return 如果发生异常则返回0 - */ - public static int getJavaVirtualCount() { - String execSystemCommand = CommandUtil.execSystemCommand("jps -l"); - List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); - return Math.max(CollUtil.size(list) - 1, 0); - } - - /** - * 根据pid 获取jvm - * - * @param pid 进程id - * @return command line info - */ - public static String getVirtualMachineInfo(int pid) { - String execSystemCommand = CommandUtil.execSystemCommand("jps -mv"); - List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); - Optional any = list.stream().filter(s -> { - List split = StrUtil.split(s, StrUtil.SPACE); - return StrUtil.equals(pid + "", CollUtil.getFirst(split)); - }).findAny(); - return any.orElse(null); - } - - /** - * 工具Jpom运行项目的id 获取进程ID - * - * @param tag 项目id - * @return 进程ID - */ - 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); - } - - /** - * 判断命令行是否为jpom 标识 - * - * @param commandLine 命令行 - * @param tag 标识 - * @return true - */ - public static boolean checkCommandLineIsJpom(String commandLine, String tag) { - if (StrUtil.isEmpty(commandLine)) { - return false; - } - String[] split = StrUtil.splitToArray(commandLine, StrUtil.SPACE); - String appTag = String.format("-%s=%s", JvmUtil.POM_PID_TAG, tag); - String appTag2 = String.format("-%s=%s", JvmUtil.OLD_JPOM_PID_TAG, tag); - String appTag3 = String.format("-%s=%s", JvmUtil.OLD2_JPOM_PID_TAG, tag); - for (String item : split) { - if (StrUtil.equalsAnyIgnoreCase(item, appTag, appTag2, appTag3)) { - return true; - } - } - return false; - } - - /** - * 工具指定的 mainClass 获取对应所有的的 MonitoredVm对象 - * - * @param mainClass 程序运行主类 - * @return pid - */ - public static Integer findMainClassPid(String mainClass) { - String execSystemCommand = CommandUtil.execSystemCommand("jps -l"); - List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); - Optional any = list.stream().map(s -> { - List split = StrUtil.split(s, StrUtil.SPACE); - return new Tuple(CollUtil.getFirst(split), CollUtil.getLast(split)); - }).filter(tuple -> { - String fileName = tuple.get(1); - return StrUtil.equals(mainClass, fileName) || checkFile(fileName, mainClass); - }).findAny(); - return any.map(tuple -> Convert.toInt(tuple.get(0))).orElse(null); - } - - private static boolean checkFile(String fileName, String mainClass) { - try { - File file = FileUtil.file(fileName); - if (!file.exists() || file.isDirectory()) { - return false; - } - try (JarFile jarFile1 = new JarFile(file)) { - Manifest manifest = jarFile1.getManifest(); - Attributes attributes = manifest.getMainAttributes(); - String jarMainClass = attributes.getValue(Attributes.Name.MAIN_CLASS); - String jarStartClass = attributes.getValue("Start-Class"); - return StrUtil.equals(mainClass, jarMainClass) || StrUtil.equals(mainClass, jarStartClass); - } - } catch (Exception e) { - return false; - } - } -} diff --git a/modules/common/src/main/java/io/jpom/util/KeyLock.java b/modules/common/src/main/java/io/jpom/util/KeyLock.java deleted file mode 100644 index c6f3c5ddf8..0000000000 --- a/modules/common/src/main/java/io/jpom/util/KeyLock.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * 根据执行KEY 多线程锁 - * - * @author jiangzeyin - * @date 2018/8/24. - */ -public class KeyLock { - /** - * 保存所有锁定的KEY及其信号量 - */ - private final ConcurrentMap map = new ConcurrentHashMap<>(); - - /** - * 获取锁的数量 - * - * @return key总数 - */ - public int getLockKeyCount() { - return map.size(); - } - - /** - * 根据key 获取等待的线程数 - * - * @param k k - * @return 总数 - */ - public int getLockCount(K k) { - LockInfo lockInfo = map.get(k); - return lockInfo == null ? 0 : lockInfo.getLockCount(); - } - - - /** - * 释放key,唤醒其他等待此key的线程 - * - * @param key key - */ - public void unlock(K key) { - if (key == null) { - return; - } - LockInfo lockInfo = map.get(key); - if (lockInfo == null) { - return; - } - // 释放许可 - lockInfo.release(); - if (lockInfo.getLockCount() <= 0) { - // 清除锁 - map.remove(key); - } - } - - /** - * 锁定key,其他等待此key的线程将进入等待,直到调用{@link KeyLock#unlock} - * 使用hashcode和equals来判断key是否相同,因此key必须实现{@link #hashCode()}和 - * {@link #equals(Object)}方法 - * - * @param key key - */ - public void lock(K key) { - if (key == null) { - return; - } - LockInfo lockInfo = map.computeIfAbsent(key, k -> new LockInfo()); - lockInfo.lock(); - } - - /** - * 锁定多个key - * 建议在调用此方法前先对keys进行排序,使用相同的锁定顺序,防止死锁发生 - * - * @param keys keys - */ - public void lock(K[] keys) { - if (keys == null) { - return; - } - for (K key : keys) { - lock(key); - } - } - - /** - * 释放多个key - * - * @param keys 多个keys - */ - public void unlock(K[] keys) { - if (keys == null) { - return; - } - for (K key : keys) { - unlock(key); - } - } - - /** - * 锁的信息 - */ - private static class LockInfo { - private final Semaphore semaphore; - private final AtomicInteger lockCount = new AtomicInteger(0); - - private LockInfo() { - this.semaphore = new Semaphore(1); - } - - private void lock() { - lockCount.getAndIncrement(); - semaphore.acquireUninterruptibly(); - } - - private void release() { - semaphore.release(); - lockCount.getAndDecrement(); - } - - private int getLockCount() { - return lockCount.get(); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/util/LayuiTreeUtil.java b/modules/common/src/main/java/io/jpom/util/LayuiTreeUtil.java deleted file mode 100644 index 017be34148..0000000000 --- a/modules/common/src/main/java/io/jpom/util/LayuiTreeUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.io.FileUtil; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import java.io.File; - -/** - * layui - * - * @author bwcx_jzy - * @date 2019/7/21 - */ -public class LayuiTreeUtil { - - /** - * 获取树的json - * - * @param path 文件名 - * @return jsonArray - */ - public static JSONArray getTreeData(String path) { - File file = FileUtil.file(path); - return readTree(file, path); - } - - private static JSONArray readTree(File file, String logFile) { - File[] files = file.listFiles(); - if (files == null) { - return null; - } - JSONArray jsonArray = new JSONArray(); - for (File file1 : files) { - JSONObject jsonObject = new JSONObject(); - String path = StringUtil.delStartPath(file1, logFile, true); - jsonObject.put("title", file1.getName()); - jsonObject.put("path", path); - if (file1.isDirectory()) { - JSONArray children = readTree(file1, logFile); - jsonObject.put("children", children); - // - jsonObject.put("spread", true); - } - jsonArray.add(jsonObject); - } - return jsonArray; - } -} diff --git a/modules/common/src/main/java/io/jpom/util/LimitQueue.java b/modules/common/src/main/java/io/jpom/util/LimitQueue.java deleted file mode 100644 index 30c5e18f60..0000000000 --- a/modules/common/src/main/java/io/jpom/util/LimitQueue.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import java.util.concurrent.ConcurrentLinkedDeque; - -/** - * 定长队列 - * - * @author jiangzeyin - * @date 2019/3/16 - */ -public class LimitQueue extends ConcurrentLinkedDeque { - private final int limit; - - public LimitQueue(int limit) { - if (limit == 0) { - this.limit = 10; - } else { - this.limit = limit; - } - } - - @Override - public boolean offerFirst(E s) { - if (full()) { - pollLast(); - } - return super.offerFirst(s); - } - - @Override - public boolean offerLast(E s) { - if (full()) { - pollFirst(); - } - return super.offerLast(s); - } - - public boolean full() { - return size() > limit; - } -} diff --git a/modules/common/src/main/java/io/jpom/util/SocketSessionUtil.java b/modules/common/src/main/java/io/jpom/util/SocketSessionUtil.java deleted file mode 100644 index 7beac19567..0000000000 --- a/modules/common/src/main/java/io/jpom/util/SocketSessionUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import javax.websocket.Session; -import java.io.IOException; - -/** - * socket 会话对象 - * - * @author jiangzeyin - * @date 2018/9/29 - */ -public class SocketSessionUtil { - /** - * 锁 - */ - private static final KeyLock LOCK = new KeyLock<>(); - /** - * 错误尝试次数 - */ - private static final int ERROR_TRY_COUNT = 10; - - /** - * 发送消息 - * - * @param session 会话对象 - * @param msg 消息 - * @throws IOException 异常 - */ - public static void send(final Session session, String msg) throws IOException { - if (StrUtil.isEmpty(msg)) { - return; - } - if (!session.isOpen()) { - throw new RuntimeException("session close "); - } - try { - LOCK.lock(session.getId()); - IOException exception = null; - int tryCount = 0; - do { - tryCount++; - if (exception != null) { - // 上一次有异常、休眠 500 - ThreadUtil.sleep(500); - } - try { - session.getBasicRemote().sendText(msg); - exception = null; - break; - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败:" + tryCount, e); - exception = e; - } - } while (tryCount <= ERROR_TRY_COUNT); - if (exception != null) { - throw exception; - } - } finally { - LOCK.unlock(session.getId()); - } - } - - public static void send(WebSocketSession session, String msg) throws IOException { - if (StrUtil.isEmpty(msg)) { - return; - } - if (!session.isOpen()) { - throw new RuntimeException("session close "); - } - try { - LOCK.lock(session.getId()); - IOException exception = null; - int tryCount = 0; - do { - tryCount++; - if (exception != null) { - // 上一次有异常、休眠 500 - ThreadUtil.sleep(500); - } - try { - session.sendMessage(new TextMessage(msg)); - exception = null; - break; - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败:" + tryCount, e); - exception = e; - } - } while (tryCount <= ERROR_TRY_COUNT); - if (exception != null) { - throw exception; - } - } finally { - LOCK.unlock(session.getId()); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/util/StringUtil.java b/modules/common/src/main/java/io/jpom/util/StringUtil.java deleted file mode 100644 index 27e7f062a2..0000000000 --- a/modules/common/src/main/java/io/jpom/util/StringUtil.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.date.DateField; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.hutool.system.SystemUtil; -import com.alibaba.fastjson.JSON; - -import java.io.File; -import java.util.List; - -/** - * main 方法运行参数工具 - * - * @author jiangzeyin - * @date 2019/4/7 - */ -public class StringUtil { - - /** - * 支持的压缩包格式 - */ - public static final String[] PACKAGE_EXT = new String[]{"tar.bz2", "tar.gz", "tar", "bz2", "zip", "gz"}; - - /** - * 获取启动参数 - * - * @param args 所有参数 - * @param name 参数名 - * @return 值 - */ - public static String getArgsValue(String[] args, String name) { - if (args == null) { - return null; - } - for (String item : args) { - item = StrUtil.trim(item); - if (item.startsWith("--" + name + "=")) { - return item.substring(name.length() + 3); - } - } - return null; - } - - /** - * id输入规则 - * - * @param value 值 - * @param min 最短 - * @param max 最长 - * @return true - */ - public static boolean isGeneral(CharSequence value, int min, int max) { - String reg = "^[a-zA-Z0-9_-]{" + min + "," + max + "}$"; - return Validator.isMatchRegex(reg, value); - } - - /** - * 删除文件开始的路径 - * - * @param file 要删除的文件 - * @param startPath 开始的路径 - * @param inName 是否返回文件名 - * @return /test/a.txt /test/ a.txt - */ - public static String delStartPath(File file, String startPath, boolean inName) { - String newWhitePath; - if (inName) { - newWhitePath = FileUtil.getAbsolutePath(file.getAbsolutePath()); - } else { - newWhitePath = FileUtil.getAbsolutePath(file.getParentFile()); - } - String itemAbsPath = FileUtil.getAbsolutePath(new File(startPath)); - itemAbsPath = FileUtil.normalize(itemAbsPath); - newWhitePath = FileUtil.normalize(newWhitePath); - String path = StrUtil.removePrefix(newWhitePath, itemAbsPath); - //newWhitePath.substring(newWhitePath.indexOf(itemAbsPath) + itemAbsPath.length()); - path = FileUtil.normalize(path); - if (path.startsWith(StrUtil.SLASH)) { - path = path.substring(1); - } - return path; - } - - /** - * 获取jdk 中的tools jar文件路径 - * - * @return file - */ - public static File getToolsJar() { - File file = new File(SystemUtil.getJavaRuntimeInfo().getHomeDir()); - return new File(file.getParentFile(), "lib/tools.jar"); - } - - /** - * 指定时间的下一个刻度 - * - * @return String - */ - public static String getNextScaleTime(String time, Long millis) { - DateTime dateTime = DateUtil.parse(time); - if (millis == null) { - millis = 30 * 1000L; - } - DateTime newTime = dateTime.offsetNew(DateField.SECOND, (int) (millis / 1000)); - return DateUtil.formatTime(newTime); - } - -// /** -// * 删除 yml 文件内容注释 -// * -// * @param content 配置内容 -// * @return 移除后的内容 -// */ -// public static String deleteComment(String content) { -// List split = StrUtil.split(content, StrUtil.LF); -// split = split.stream().filter(s -> { -// if (StrUtil.isEmpty(s)) { -// return false; -// } -// s = StrUtil.trim(s); -// return !StrUtil.startWith(s, "#"); -// }).collect(Collectors.toList()); -// return CollUtil.join(split, StrUtil.LF); -// } - - /** - * json 字符串转 bean,兼容普通json和字符串包裹情况 - * - * @param jsonStr json 字符串 - * @param cls 要转为bean的类 - * @param 泛型 - * @return data - */ - public static T jsonConvert(String jsonStr, Class cls) { - if (StrUtil.isEmpty(jsonStr)) { - return null; - } - try { - return JSON.parseObject(jsonStr, cls); - } catch (Exception e) { - return JSON.parseObject(JSON.parse(jsonStr).toString(), cls); - } - } - - /** - * json 字符串转 bean,兼容普通json和字符串包裹情况 - * - * @param jsonStr json 字符串 - * @param cls 要转为bean的类 - * @param 泛型 - * @return data - */ - public static List jsonConvertArray(String jsonStr, Class cls) { - try { - if (StrUtil.isEmpty(jsonStr)) { - return null; - } - return JSON.parseArray(jsonStr, cls); - } catch (Exception e) { - Object parse = JSON.parse(jsonStr); - return JSON.parseArray(parse.toString(), cls); - } - } -} diff --git a/modules/common/src/main/java/io/jpom/util/VersionUtils.java b/modules/common/src/main/java/io/jpom/util/VersionUtils.java deleted file mode 100644 index e3201d9ded..0000000000 --- a/modules/common/src/main/java/io/jpom/util/VersionUtils.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.io.file.FileNameUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; - -import java.security.CodeSource; - -/** - * VersionUtils. - * Just read MANIFEST.MF file and find Implementation-Version property - * - * @author Hotstrip - */ -public final class VersionUtils { - - private static final String VERSION = getVersion(io.jpom.util.VersionUtils.class, "1.0.0"); - - private VersionUtils() { - } - - /** - * Gets version. - * - * @return the version - */ - public static String getVersion() { - return VERSION; - } - - /** - * Gets version. - * - * @param cls the cls - * @param defaultVersion the default version - * @return the version - */ - public static String getVersion(final Class cls, final String defaultVersion) { - // find version info from MANIFEST.MF first - String version = cls.getPackage().getImplementationVersion(); - if (StrUtil.isEmpty(version)) { - version = cls.getPackage().getSpecificationVersion(); - } - if (!StrUtil.isEmpty(version)) { - return version; - } - // guess version fro jar file name if nothing's found from MANIFEST.MF - CodeSource codeSource = cls.getProtectionDomain().getCodeSource(); - - if (codeSource == null) { - DefaultSystemLog.getLog().warn("No codeSource for class {} when getVersion, use default version {}", cls.getName(), defaultVersion); - return defaultVersion; - } - String file = codeSource.getLocation().getFile(); - if (file != null && file.endsWith(FileNameUtil.EXT_JAR)) { - file = file.substring(0, file.length() - 4); - int i = file.lastIndexOf('/'); - if (i >= 0) { - file = file.substring(i + 1); - } - i = file.indexOf("-"); - if (i >= 0) { - file = file.substring(i + 1); - } - while (file.length() > 0 && !Character.isDigit(file.charAt(0))) { - i = file.indexOf("-"); - if (i < 0) { - break; - } - file = file.substring(i + 1); - } - version = file; - } - // return default version if no version info is found - return StrUtil.isEmpty(version) ? defaultVersion : version; - } -} - - diff --git a/modules/common/src/main/java/io/jpom/util/package-info.java b/modules/common/src/main/java/io/jpom/util/package-info.java deleted file mode 100644 index 06b116093c..0000000000 --- a/modules/common/src/main/java/io/jpom/util/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; \ No newline at end of file diff --git a/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java b/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java new file mode 100644 index 0000000000..e8d1e5fe75 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.thread.GlobalThreadPool; +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 cn.hutool.extra.spring.SpringUtil; +import cn.hutool.system.OsInfo; +import cn.hutool.system.SystemUtil; +import cn.keepbx.jpom.JpomAppType; +import cn.keepbx.jpom.Type; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.system.JpomRuntimeException; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Configuration; + +import java.io.File; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * Jpom + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Slf4j +@Configuration +@Getter +public class JpomApplication implements DisposableBean, InitializingBean { + /** + * 程序端口 + */ + @Value("${server.port}") + private int port; + + @Value("${server.address:}") + private String address; + /** + * 数据目录缓存大小 + */ + private long dataSizeCache; + + + private static volatile JpomApplication jpomApplication; + + private static final Map LINK_EXECUTOR_SERVICE = new SafeConcurrentHashMap<>(); + + /** + * 单利模式 + * + * @return config + */ + public static JpomApplication getInstance() { + if (jpomApplication == null) { + synchronized (JpomApplication.class) { + if (jpomApplication == null) { + jpomApplication = SpringUtil.getBean(JpomApplication.class); + } + } + } + return jpomApplication; + } + + /** + * 获取项目运行数据存储文件夹路径 + * + * @return 文件夹路径 + */ + public String getDataPath() { + String dataPath = FileUtil.normalize(ExtConfigBean.getPath() + StrUtil.SLASH + Const.DATA); + FileUtil.mkdir(dataPath); + return dataPath; + } + +// /** +// * 执行脚本 +// * +// * @param inputStream 脚本内容 +// * @param function 回调分发 +// * @param 值类型 +// * @return 返回值 +// */ +// public T execScript(InputStream inputStream, Function function) { +// String sshExecTemplate = IoUtil.readUtf8(inputStream); +// return this.execScript(sshExecTemplate, function); +// } + + /** + * 执行脚本 + * + * @param context 脚本内容 + * @param function 回调分发 + * @param 值类型 + * @return 返回值 + */ + public T execScript(String context, Function function) { + String dataPath = this.getDataPath(); + File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX)); + FileUtils.writeScript(context, scriptFile, ExtConfigBean.getConsoleLogCharset()); + try { + return function.apply(scriptFile); + } finally { + FileUtil.del(scriptFile); + } + } + + /** + * 获取临时文件存储路径 + * + * @return file + */ + public File getTempPath() { + File file = new File(this.getDataPath()); + file = FileUtil.file(file, "temp"); + FileUtil.mkdir(file); + return file; + } + + /** + * 数据目录大小 + * + * @return byte + */ + public long dataSize() { + String dataPath = getDataPath(); + long size = FileUtil.size(FileUtil.file(dataPath)); + dataSizeCache = size; + return size; + } + + /** + * 获取脚本模板路径 + * + * @return file + */ + public File getScriptPath() { + return FileUtil.file(this.getDataPath(), Const.SCRIPT_DIRECTORY); + } + + /** + * 获取当前程序的类型 + * + * @return Agent 或者 Server + */ + public static Type getAppType() { + Map beansWithAnnotation = SpringUtil.getApplicationContext().getBeansWithAnnotation(JpomAppType.class); + Class jpomAppClass = Optional.of(beansWithAnnotation) + .map(map -> CollUtil.getFirst(map.values())) + .map(Object::getClass) + .orElseThrow(() -> new RuntimeException(I18nMessageUtil.get("i18n.no_jpom_type_config_found.aa57"))); + JpomAppType jpomAppType = jpomAppClass.getAnnotation(JpomAppType.class); + return jpomAppType.value(); + } + + public static Class getAppClass() { + Map beansWithAnnotation = SpringUtil.getApplicationContext().getBeansWithAnnotation(SpringBootApplication.class); + return Optional.of(beansWithAnnotation) + .map(map -> CollUtil.getFirst(map.values())) + .map(Object::getClass) + .orElseThrow(() -> new RuntimeException(I18nMessageUtil.get("i18n.main_class_not_found.8a12"))); + } + + /** + * 重启自身 + * 分发会延迟2秒执行正式升级 重启命令 + * + * @see JpomManifest#releaseJar + */ + public static void restart() { + File runFile = JpomManifest.getRunPath(); + File runPath = runFile.getParentFile(); + if (!runPath.isDirectory()) { + throw new JpomRuntimeException(runPath.getAbsolutePath() + " error"); + } + OsInfo osInfo = SystemUtil.getOsInfo(); + if (osInfo.isWindows()) { + // 需要重新变更 stdout_log 文件来保证进程不被占用 + String format = StrUtil.format("stdout_{}.log", System.currentTimeMillis()); + FileUtil.writeString(format, FileUtil.file(runPath, "run.log"), CharsetUtil.CHARSET_UTF_8); + } + File scriptFile = JpomManifest.getScriptFile(); + ThreadUtil.execute(() -> { + // Waiting for method caller,For example, the interface response + ThreadUtil.sleep(2, TimeUnit.SECONDS); + try { + String command = CommandUtil.generateCommand(scriptFile, "restart upgrade"); + File parentFile = scriptFile.getParentFile(); + if (osInfo.isWindows()) { + //String result = CommandUtil.execSystemCommand(command, scriptFile.getParentFile()); + //log.debug("windows restart {}", result); + CommandUtil.asyncExeLocalCommand("start /b" + command, parentFile); + } else { + String jpomService = SystemUtil.get("JPOM_SERVICE"); + if (StrUtil.isEmpty(jpomService)) { + CommandUtil.asyncExeLocalCommand(command, parentFile); + } else { + // 使用了服务 + CommandUtil.asyncExeLocalCommand("systemctl restart " + jpomService, parentFile, null, true); + } + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.restart_self_exception.85b7"), e); + } + }); + } + + public static ScheduledExecutorService getScheduledExecutorService() { + return (ScheduledExecutorService) LINK_EXECUTOR_SERVICE.computeIfAbsent("jpom-system-task", + s -> Executors.newScheduledThreadPool(4, + r -> new Thread(r, "jpom-system-task"))); + } + + /** + * 注册线程池 + * + * @param name 线程池名 + * @param executorService 线程池 + */ + public static void register(String name, ExecutorService executorService) { + LINK_EXECUTOR_SERVICE.put(name, executorService); + } + + /** + * 关闭全局线程池 + */ + public static void shutdownGlobalThreadPool() { + LINK_EXECUTOR_SERVICE.forEach((s, executorService) -> { + if (!executorService.isShutdown()) { + log.debug(I18nMessageUtil.get("i18n.close_thread_pool.4cd9"), s); + executorService.shutdownNow(); + } + }); + } + + @Override + public void destroy() throws Exception { + Type appType = getAppType(); + log.info("Jpom {} disposable", appType); + shutdownGlobalThreadPool(); + } + + @Override + public void afterPropertiesSet() throws Exception { + register("Global", GlobalThreadPool.getExecutor()); + } +} 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 new file mode 100644 index 0000000000..c4807d987b --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/BaseJpomController.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.*; +import cn.hutool.crypto.SecureUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.util.Assert; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.Arrays; +import java.util.Objects; + +/** + * controller + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Slf4j +public abstract class BaseJpomController { + + /** + * 获取请求的ip 地址 + * + * @return ip + */ + protected String getIp() { + return ServletUtil.getClientIP(getRequest()); + } + + /** + * 上传保存分片信息 + * + * @param file 上传的文件信息 + * @param tempPath 临时保存目录 + * @param sliceId 分片id + * @param totalSlice 累积分片 + * @param nowSlice 当前分片 + * @param fileSumMd5 文件签名信息 + * @throws IOException 异常 + */ + public void uploadSharding(MultipartFile file, + String tempPath, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5, + String... extNames) throws IOException { + Assert.hasText(fileSumMd5, I18nMessageUtil.get("i18n.file_signature_info_not_found.83bf")); + Assert.hasText(sliceId, I18nMessageUtil.get("i18n.no_shard_id_info.30f8")); + + Assert.notNull(totalSlice, I18nMessageUtil.get("i18n.incomplete_upload_info_total_slice.7e85")); + Assert.notNull(nowSlice, I18nMessageUtil.get("i18n.incomplete_upload_info_now_slice.34aa")); + Assert.state(totalSlice > 0 && nowSlice > -1 && totalSlice >= nowSlice, I18nMessageUtil.get("i18n.current_upload_chunk_info_incorrect.900e")); + // 保存路径 + File slicePath = FileUtil.file(tempPath, "slice", sliceId); + File sliceItemPath = FileUtil.file(slicePath, "items"); + Assert.notNull(file, I18nMessageUtil.get("i18n.no_uploaded_file.07ef")); + String originalFilename = file.getOriginalFilename(); + // 截断序号 xxxxx.avi.1 + String realName = StrUtil.subBefore(originalFilename, StrUtil.DOT, true); + if (ArrayUtil.isNotEmpty(extNames)) { + String extName = FileUtil.extName(realName); + Assert.state(StrUtil.containsAnyIgnoreCase(extName, extNames), I18nMessageUtil.get("i18n.file_type_not_supported2.d497") + extName); + } + assert originalFilename != null; + File slice = FileUtil.file(sliceItemPath, originalFilename); + FileUtil.mkParentDirs(slice); + // 保存 + file.transferTo(slice); + } + + /** + * 合并分片 + * + * @param tempPath 临时保存目录 + * @param sliceId 上传id + * @param totalSlice 累积分片 + * @param fileSumMd5 文件签名 + * @return 合并后的文件 + * @throws IOException io + */ + public File shardingTryMerge(String tempPath, + String sliceId, + Integer totalSlice, + String fileSumMd5) throws IOException { + Assert.hasText(fileSumMd5, I18nMessageUtil.get("i18n.file_signature_info_not_found.83bf")); + Assert.hasText(sliceId, I18nMessageUtil.get("i18n.no_shard_id_info.30f8")); + + Assert.notNull(totalSlice, I18nMessageUtil.get("i18n.incomplete_upload_info_total_slice.7e85")); + + // 保存路径 + File slicePath = FileUtil.file(tempPath, "slice", sliceId); + File sliceItemPath = FileUtil.file(slicePath, "items"); + + // 准备合并 + File[] files = sliceItemPath.listFiles(); + int length = ArrayUtil.length(files); + Assert.state(files != null && length == totalSlice, StrUtil.format(I18nMessageUtil.get("i18n.file_upload_failure_due_to_missing_chunks.1865"), length, totalSlice)); + // 文件真实名称 + String name = files[0].getName(); + name = StrUtil.subBefore(name, StrUtil.DOT, true); + File successFile = FileUtil.file(slicePath, name); + try (FileOutputStream fileOutputStream = new FileOutputStream(successFile)) { + try (FileChannel channel = fileOutputStream.getChannel()) { + Arrays.stream(files).sorted((o1, o2) -> { + // 排序 + Integer o1Int = Convert.toInt(FileUtil.extName(o1), 0); + Integer o2Int = Convert.toInt(FileUtil.extName(o2), 0); + return o1Int.compareTo(o2Int); + }).forEach(file12 -> { + try { + FileUtils.appendChannel(file12, channel); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + } + } + // 删除分片信息 + FileUtil.del(sliceItemPath); + // 对比文件信息 + String newMd5 = SecureUtil.md5(successFile); + Assert.state(StrUtil.equals(newMd5, fileSumMd5), () -> { + log.warn(I18nMessageUtil.get("i18n.file_merge_exception_details.e9d0"), FileUtil.getAbsolutePath(successFile), newMd5, fileSumMd5); + return I18nMessageUtil.get("i18n.file_merge_error.f32f"); + }); + return successFile; + } + + protected String getParameter(String name) { + return getParameter(name, null); + } + + protected String[] getParameters(String name) { + return getRequest().getParameterValues(name); + } + + /** + * 获取指定参数名的值 + * + * @param name 参数名 + * @param def 默认值 + * @return str + */ + protected String getParameter(String name, String def) { + String value = getRequest().getParameter(name); + return value == null ? def : value; + } + + protected int getParameterInt(String name, int def) { + return Convert.toInt(getParameter(name), def); + } + + + // ----------------文件上传 + /** + * cache + */ + private static final ThreadLocal THREAD_LOCAL_MULTIPART_HTTP_SERVLET_REQUEST = new ThreadLocal<>(); + + /** + * 释放资源 + */ + public static void clearResources() { + THREAD_LOCAL_MULTIPART_HTTP_SERVLET_REQUEST.remove(); + } + + /** + * 获取文件上传请求对象 + * + * @return multipart + */ + protected MultipartHttpServletRequest getMultiRequest() { + HttpServletRequest request = getRequest(); + if (request instanceof MultipartHttpServletRequest) { + return (MultipartHttpServletRequest) request; + } + if (ServletFileUpload.isMultipartContent(request)) { + MultipartHttpServletRequest multipartHttpServletRequest = THREAD_LOCAL_MULTIPART_HTTP_SERVLET_REQUEST.get(); + if (multipartHttpServletRequest != null) { + return multipartHttpServletRequest; + } + multipartHttpServletRequest = new StandardMultipartHttpServletRequest(request); + THREAD_LOCAL_MULTIPART_HTTP_SERVLET_REQUEST.set(multipartHttpServletRequest); + return multipartHttpServletRequest; + } + throw new IllegalArgumentException("not MultipartHttpServletRequest"); + } + + // ------------------------文件上传结束 + + /** + * 全局获取请求对象 + * + * @return req + */ + public static ServletRequestAttributes getRequestAttributes() { + ServletRequestAttributes servletRequestAttributes = tryGetRequestAttributes(); + Objects.requireNonNull(servletRequestAttributes); + return servletRequestAttributes; + } + + /** + * 尝试获取 + * + * @return ServletRequestAttributes + */ + public static ServletRequestAttributes tryGetRequestAttributes() { + RequestAttributes attributes = null; + try { + attributes = RequestContextHolder.currentRequestAttributes(); + } catch (IllegalStateException ignored) { + } + if (attributes == null) { + return null; + } + if (attributes instanceof ServletRequestAttributes) { + return (ServletRequestAttributes) attributes; + } + return null; + } + + /** + * 获取客户端的ip地址 + * + * @return 如果没有就返回null + */ + public static String getClientIP() { + ServletRequestAttributes servletRequest = tryGetRequestAttributes(); + if (servletRequest == null) { + return null; + } + HttpServletRequest request = servletRequest.getRequest(); + if (request == null) { + return null; + } + return ServletUtil.getClientIP(request); + } + + public HttpServletRequest getRequest() { + HttpServletRequest request = getRequestAttributes().getRequest(); + Objects.requireNonNull(request, "request null"); + return request; + } + + /** + * 获取session 字符串 + * + * @param name name + * @return str + * @author bwcx_jzy + */ + public String getSessionAttribute(String name) { + return Objects.toString(getSessionAttributeObj(name), ""); + } + + /** + * 获取session 中对象 + * + * @param name name + * @return obj + */ + public Object getSessionAttributeObj(String name) { + return getRequestAttributes().getAttribute(name, RequestAttributes.SCOPE_SESSION); + } + + /** + * 移除session 值 + * + * @param name name + * @author bwcx_jzy + */ + public void removeSessionAttribute(String name) { + getRequestAttributes().removeAttribute(name, RequestAttributes.SCOPE_SESSION); + } + + /** + * 设置session 字符串 + * + * @param name name + * @param object 值 + */ + public void setSessionAttribute(String name, Object object) { + getRequestAttributes().setAttribute(name, object, RequestAttributes.SCOPE_SESSION); + } + + 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))); + response.setContentType(contentType); + } + + protected String getWorkspaceId() { + return ServletUtil.getHeader(getRequest(), Const.WORKSPACE_ID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/Const.java b/modules/common/src/main/java/org/dromara/jpom/common/Const.java new file mode 100644 index 0000000000..efdcec2f8c --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/Const.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.util.function.Supplier; + +/** + * @author Hotstrip + * Const class + */ +public class Const { + /** + * 升级提示语 + */ + public static final Supplier UPGRADE_MSG = () -> I18nMessageUtil.get("i18n.upgrade_duration_message.bab4"); + + /** + * 请求 header + */ + public static final String WORKSPACE_ID_REQ_HEADER = "workspaceId"; + /** + * 默认的工作空间 + */ + public static final String WORKSPACE_DEFAULT_ID = "DEFAULT"; + /** + * 工作空间全局 + */ + public static final String WORKSPACE_GLOBAL = "GLOBAL"; + /** + * 默认的分组名 + */ + public static final Supplier DEFAULT_GROUP_NAME = () -> I18nMessageUtil.get("i18n.default_setting.18c6"); +// /** +// * websocket 传输 agent 包 buffer size +// */ +// public static final int DEFAULT_BUFFER_SIZE = 1024 * 1024; + /** + * id 最大长度 + */ + public static final int ID_MAX_LEN = 50; + + /** + * 用户名header + */ + public static final String JPOM_SERVER_USER_NAME = "Jpom-Server-UserName"; + + public static final String JPOM_AGENT_AUTHORIZE = "Jpom-Agent-Authorize"; + + public static final String DATA = "data"; + + public static final int AUTHORIZE_ERROR = 900; + /** + * 脚本模板存放路径 + */ + public static final String SCRIPT_DIRECTORY = "script"; + /** + * 脚本默认运行缓存执行文件路径,考虑 windows 文件被占用情况 + */ + public static final String SCRIPT_RUN_CACHE_DIRECTORY = "script_run_cache"; + /** + * 授权信息 + */ + public static final String AUTHORIZE = "agent_authorize.json"; + + /** + * 程序升级信息文件 + */ + public static final String UPGRADE = "upgrade.json"; + public static final String RUN_JAR = "run.bin"; + /** + * 远程版本信息 + */ + public static final String REMOTE_VERSION = "remote_version.json"; + + public static final String FILE_NAME = "application.yml"; + + /** + * + */ + public static final String SYSTEM_ID = "system"; + + public static final String SOCKET_MSG_TAG = "JPOM_MSG"; + + /** + * 第一次服务端安装信息 + */ + public static final String INSTALL = "INSTALL.json"; +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/ILoadEvent.java b/modules/common/src/main/java/org/dromara/jpom/common/ILoadEvent.java new file mode 100644 index 0000000000..d5715884f2 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/ILoadEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.Ordered; + +/** + * jpom 加载事件 + *

+ * 保证在容器的 bean 加载完成之后 + * + * @author bwcx_jzy + * @since 2022/12/25 + */ +public interface ILoadEvent extends Ordered { + + /** + * 初始化成功后执行 + * + * @param applicationContext 应用上下文 + * @throws Exception 异常 + */ + void afterPropertiesSet(ApplicationContext applicationContext) throws Exception; + + /** + * 排序只 + * + * @return 0 是默认 + */ + @Override + default int getOrder() { + return 0; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/JpomApplicationEvent.java b/modules/common/src/main/java/org/dromara/jpom/common/JpomApplicationEvent.java new file mode 100644 index 0000000000..ee5380c1f9 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/JpomApplicationEvent.java @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.system.SystemUtil; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.cron.ICron; +import cn.keepbx.jpom.event.IAsyncLoad; +import cn.keepbx.jpom.event.ICacheTask; +import cn.keepbx.jpom.event.ISystemTask; +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.JsonFileUtil; +import org.springframework.beans.BeansException; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 启动 、关闭监听 + * + * @author bwcx_jzy + * @since 2019/4/7 + */ +@Slf4j +@Configuration +public class JpomApplicationEvent implements ApplicationListener, ApplicationContextAware { + + private final JpomApplication configBean; + + private static int oldJarsCount = 2; + + public static void setOldJarsCount(int oldJarsCount) { + JpomApplicationEvent.oldJarsCount = oldJarsCount; + } + + public JpomApplicationEvent(JpomApplication configBean) { + this.configBean = configBean; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + // 启动最后的预加载 + if (event instanceof ApplicationReadyEvent) { + + } else if (event instanceof ContextClosedEvent) { + // + } + } + + + private void checkPath() { + String path = ExtConfigBean.getPath(); + String extConfigPath; + try { + extConfigPath = ExtConfigBean.getResource().getURL().toString(); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + File file = FileUtil.file(path); + try { + FileUtil.mkdir(file); + file = FileUtil.createTempFile("jpom", ".temp", file, true); + } catch (Exception e) { + log.error(StrUtil.format("Jpom Failed to create data directory, directory location:{}," + + "Please check whether the current user has permission to this directory or modify the configuration file:{} jpom.path in is the path where the directory can be created", path, extConfigPath), e); + asyncExit(-1); + } + FileUtil.del(file); + log.info("Jpom[{}] Current data path:{} External configuration file path:{}", JpomManifest.getInstance().getVersion(), path, extConfigPath); + } + + private void install() { + String installId; + File file = FileUtil.file(configBean.getDataPath(), Const.INSTALL); + if (file.exists()) { + JSONObject jsonObject; + try { + jsonObject = JsonFileUtil.readJson(file); + } catch (FileNotFoundException e) { + throw Lombok.sneakyThrow(e); + } + installId = jsonObject.getString("installId"); + Assert.hasText(installId, I18nMessageUtil.get("i18n.install_id_does_not_exist.6aee")); + log.info(I18nMessageUtil.get("i18n.machine_installation_id.d0b9"), installId); + } else { + JSONObject jsonObject = new JSONObject(); + installId = IdUtil.fastSimpleUUID(); + jsonObject.put("installId", installId); + jsonObject.put("installTime", DateTime.now().toString()); + String value = I18nMessageUtil.get("i18n.please_do_not_delete_this_file.0a7f"); + jsonObject.put("desc", value); + JsonFileUtil.saveJson(file.getAbsolutePath(), jsonObject); + log.info(I18nMessageUtil.get("i18n.installation_success_with_machine_id.1cd6"), installId); + } + JpomManifest.getInstance().setInstallId(installId); + } + + /** + * 检查更新包文件状态 + */ + private void checkUpdate() { + File runFile = JpomManifest.getRunPath().getParentFile(); + String upgrade = FileUtil.file(runFile, Const.UPGRADE).getAbsolutePath(); + JSONObject jsonObject = null; + try { + jsonObject = JsonFileUtil.readJson(upgrade); + } catch (FileNotFoundException ignored) { + } + if (jsonObject != null) { + String beforeJar = jsonObject.getString("beforeJar"); + String newJar = jsonObject.getString("newJar"); + if (StrUtil.isNotEmpty(beforeJar)) { + File beforeJarFile = FileUtil.file(runFile, beforeJar); + if (beforeJarFile.exists()) { + if (this.canMvOldJar(jsonObject, runFile)) { + File oldJars = JpomManifest.getOldJarsPath(); + FileUtil.mkdir(oldJars); + FileUtil.move(beforeJarFile, oldJars, true); + log.info(I18nMessageUtil.get("i18n.backup_old_package.a7fc"), beforeJar); + } else { + log.debug(I18nMessageUtil.get("i18n.backup_old_package_failure_due_to_new_package_absence.b90c"), beforeJar, newJar); + } + } else { + log.debug(I18nMessageUtil.get("i18n.backup_old_package_failure_due_to_old_package_absence.53aa"), beforeJar); + } + } + } + clearOldJar(); + // windows 备份日志 + // if (SystemUtil.getOsInfo().isWindows()) { + // boolean logBack = jsonObject.getBooleanValue("logBack"); + // String oldLogName = jsonObject.getString("oldLogName"); + // if (logBack && StrUtil.isNotEmpty(oldLogName)) { + // File scriptFile = JpomManifest.getScriptFile(); + // File oldLog = FileUtil.file(scriptFile.getParentFile(), oldLogName); + // if (oldLog.exists()) { + // File logBackDir = FileUtil.file(scriptFile.getParentFile(), "log"); + // FileUtil.move(oldLog, logBackDir, true); + // } + // } + // } + } + + private boolean canMvOldJar(JSONObject jsonObject, File runFile) { + String newJar = jsonObject.getString("newJar"); + if (StrUtil.isEmpty(newJar)) { + return false; + } + File newJarFile = FileUtil.file(runFile, newJar); + return FileUtil.exist(newJarFile); + } + + private void clearOldJar() { + File oldJars = JpomManifest.getOldJarsPath(); + List files = FileUtil.loopFiles(oldJars, 1, file -> StrUtil.endWith(file.getName(), FileUtil.JAR_FILE_EXT, true)); + if (CollUtil.isEmpty(files)) { + return; + } + // 排序 + files.sort((o1, o2) -> FileUtil.lastModifiedTime(o2).compareTo(FileUtil.lastModifiedTime(o1))); + // 截取 + int size = CollUtil.size(files); + files = CollUtil.sub(files, oldJarsCount, size); + // 删除文件 + files.forEach(file -> { + FileUtil.del(file); + log.debug(I18nMessageUtil.get("i18n.delete_old_package.ca95"), file.getAbsolutePath()); + }); + } + + + @SuppressWarnings("rawtypes") + private void statLoad() { + ThreadUtil.execute(() -> { + // 加载定时器 + Map cronMap = SpringUtil.getApplicationContext().getBeansOfType(ICron.class); + cronMap.forEach((name, iCron) -> { + int startCron = iCron.startCron(); + if (startCron > 0) { + log.debug(I18nMessageUtil.get("i18n.auto_start_timed_task_message.9637"), name, startCron); + } + }); + Map asyncLoadMap = SpringUtil.getApplicationContext().getBeansOfType(IAsyncLoad.class); + asyncLoadMap.forEach((name, asyncLoad) -> asyncLoad.startLoad()); + // + }); + } + + /** + * 输出启动成功的 日志 + */ + private void success() { + Type type = JpomManifest.getInstance().getType(); + int port = configBean.getPort(); + String address = configBean.getAddress(); + String localhostStr = Opt.ofBlankAble(address).orElseGet(NetUtil::getLocalhostStr); + String url = StrUtil.format("http://{}:{}", localhostStr, port); + if (type == Type.Server) { + log.info("{} Successfully started,Can use happily => {} 【The current address is for reference only】", type, url); + } else if (type == Type.Agent) { + log.info("{} Successfully started,Please go to the server to configure and use,Current node address => {} 【The current address is for reference only】", type, url); + } + } + + + private void clearTemp() { + log.debug("Automatically clean up temporary directories"); + File file = configBean.getTempPath(); + /** + * @author Hotstrip + * use Hutool's FileUtil.del method just put file as param not file's path + * or else, may be return Accessdenied exception + */ + try { + FileUtil.del(file); + } catch (Exception e) { + // Try again jzy 2021-07-31 + log.warn("Attempt to delete temporary folder failed, try to handle read-only permission:{}", e.getMessage()); + List files = FileUtil.loopFiles(file); + long count = files.stream().map(file12 -> file12.setWritable(true)).filter(aBoolean -> aBoolean).count(); + log.warn("Cumulative number of files in temporary folder: {}, number of successful processing:{}", CollUtil.size(files), count); + try { + FileUtil.del(file.toPath()); + } catch (Exception e1) { + e1.addSuppressed(e); + boolean causedBy = ExceptionUtil.isCausedBy(e1, AccessDeniedException.class); + if (causedBy) { + log.error(I18nMessageUtil.get("i18n.clear_temp_file_failed_manually.0dad") + FileUtil.getAbsolutePath(file), e); + return; + } + log.error(I18nMessageUtil.get("i18n.clear_temp_file_failed_check_directory.7340") + FileUtil.getAbsolutePath(file), e); + } + } + } + + @Bean + public MappingJackson2HttpMessageConverter objectMapper() { + ObjectMapper build = createJackson(); + MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(build); +// messageConverter.setDefaultCharset(CharsetUtil.CHARSET_UTF_8); + return messageConverter; + } + + + /** + * jackson 配置 + * + * @return mapper + */ + private ObjectMapper createJackson() { + Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = Jackson2ObjectMapperBuilder.json(); + jackson2ObjectMapperBuilder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN); + ObjectMapper build = jackson2ObjectMapperBuilder.build(); + // 忽略空 + build.setSerializationInclusion(JsonInclude.Include.NON_NULL); + // 驼峰转下划线 + // build.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy()); + // long to String + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(Long.class, ToStringSerializer.instance); + simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); + build.registerModule(simpleModule); + // + build.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); +// build.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); + + return build; + } + + /** + * 异步退出,避免 springboot 锁 synchronized (this.startupShutdownMonitor) + * + * @param code 退出码 + * @see AbstractApplicationContext#refresh() + * @see AbstractApplicationContext#close() + */ + public static void asyncExit(int code) { + ThreadUtil.execute(() -> System.exit(code)); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // + File file = FileUtil.file(JpomApplication.getInstance().getDataPath(), Const.REMOTE_VERSION); + SystemUtil.set("JPOM_REMOTE_VERSION_CACHE_FILE", file.getAbsolutePath()); + JpomManifest jpomManifest = JpomManifest.getInstance(); + SystemUtil.set("JPOM_IS_DEBUG", String.valueOf(jpomManifest.isDebug())); + SystemUtil.set("JPOM_TYPE", jpomManifest.getType().name()); + SystemUtil.set("JPOM_VERSION", jpomManifest.getVersion()); + SystemUtil.set("JPOM_INSTALL_ID", jpomManifest.getInstallId()); + // 检查目录权限 + this.checkPath(); + this.install(); + // 清空临时目录 + this.clearTemp(); + // 开始加载子模块 + Map loadEventMap = applicationContext.getBeansOfType(ILoadEvent.class); + loadEventMap.values() + .stream() + .sorted((o1, o2) -> CompareUtil.compare(o1.getOrder(), o2.getOrder())) + .forEach(iLoadEvent -> { + try { + iLoadEvent.afterPropertiesSet(applicationContext); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + // 检查更新文件 + this.checkUpdate(); + // 开始异常加载 + this.statLoad(); + // 提示成功消息 + this.success(); + } + + @Configuration + public static class SystemEvent implements ILoadEvent { + + @Override + public int getOrder() { + return LOWEST_PRECEDENCE; + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + CronUtils.upsert("system_monitor", "0 0 0,12 * * ?", this::executeTask); + CronUtils.upsert("system_cache", "0 0/10 * * * ?", this::refresh); + // 启动执行一次 + ThreadUtil.execute(() -> { + try { + this.executeTask(); + this.refresh(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.system_task_execution_exception.d559"), e); + } + }); + } + + private void executeTask() { + Map taskMap = SpringUtil.getBeansOfType(ISystemTask.class); + Optional.ofNullable(taskMap).ifPresent(map -> map.values().forEach(ISystemTask::executeTask)); + } + + private void refresh() { + Map taskMap = SpringUtil.getBeansOfType(ICacheTask.class); + Optional.ofNullable(taskMap).ifPresent(map -> map.values().forEach(ICacheTask::refreshCache)); + } + } +} 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 new file mode 100644 index 0000000000..a91aabf555 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/JpomManifest.java @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.ManifestUtil; +import cn.hutool.core.lang.JarClassLoader; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.*; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.GlobalHeaders; +import cn.hutool.http.Header; +import cn.hutool.http.useragent.UserAgentInfo; +import cn.hutool.system.JavaInfo; +import cn.hutool.system.OsInfo; +import cn.hutool.system.SystemUtil; +import cn.keepbx.jpom.Type; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.JpomRuntimeException; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.JsonFileUtil; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Jpom 的运行信息 + * + * @author bwcx_jzy + * @since 2019/4/7 + */ +@Slf4j +public class JpomManifest { + + private volatile static JpomManifest JPOM_MANIFEST; + /** + * 允许降级 + */ + private volatile static boolean allowedDowngrade; + /** + * 当前版本 + */ + private String version = "dev"; + /** + * 打包时间 + */ + private String timeStamp; + /** + * 进程id + */ + private long pid = SystemUtil.getCurrentPID(); + /** + * 当前运行类型 + */ + private final Type type = JpomApplication.getAppType(); + /** + * 端口号 + */ + private int port; + /** + * 随机ID + */ + private String randomId; + /** + * Jpom 的数据目录 + */ + private String dataPath; + /** + * jar 运行路径 + */ + private String jarFile; + /** + * 系统名称 + */ + private final String osName = SystemUtil.getOsInfo().getName(); + /** + * 安装id + */ + private String installId; + + private static JpomManifest buildJpomManifest() { + JpomManifest jpomManifest = new JpomManifest(); + File jarFile = getRunPath(); + Tuple jarVersion = getJarVersion(jarFile); + if (jarVersion != null) { + jpomManifest.setVersion(jarVersion.get(0)); + jpomManifest.setTimeStamp(jarVersion.get(1)); + } + jpomManifest.setJarFile(FileUtil.getAbsolutePath(jarFile)); + // + jpomManifest.randomId = IdUtil.fastSimpleUUID(); + return jpomManifest; + } + + private static String buildOsInfo() { + // 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(() -> UserAgentInfo.NameUnknown); + return StrUtil.format("{} {}; {}; {}", + inDocker ? "docker" : osName, + Opt.ofBlankAble(osInfo.getVersion()).orElse("0"), + Opt.ofBlankAble(osInfo.getArch()).orElse(UserAgentInfo.NameUnknown), + Opt.ofBlankAble(javaInfo.getVersion()).orElse(UserAgentInfo.NameUnknown) + ); + } + + /** + * 根据 jar 文件解析 jpom 版本信息 + * + * @param jarFile 文件 + * @return 版本, 打包时间, mainClass + */ + private static Tuple getJarVersion(File jarFile) { + Manifest manifest = ManifestUtil.getManifest(jarFile); + if (manifest != null) { + Attributes attributes = manifest.getMainAttributes(); + String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + if (version != null) { + // @see VersionUtils#getVersion() + String timeStamp = attributes.getValue("Jpom-Timestamp"); + timeStamp = parseJpomTime(timeStamp); + String mainClass = attributes.getValue(Attributes.Name.MAIN_CLASS); + String jpomMinVersion = attributes.getValue("Jpom-Min-Version"); + return new Tuple(version, timeStamp, mainClass, jarFile, jpomMinVersion); + } + } + return null; + } + + + private JpomManifest() { + } + + /** + * 单利模式获取Jpom 信息 + * + * @return this + */ + public static JpomManifest getInstance() { + if (JPOM_MANIFEST == null) { + synchronized (JpomManifest.class) { + if (JPOM_MANIFEST == null) { + JPOM_MANIFEST = buildJpomManifest(); + } + String jpomTag = StrUtil.format("Jpom {}/{}", JPOM_MANIFEST.getType(), JPOM_MANIFEST.getVersion()); + String osInfo = buildOsInfo(); + // Mozilla/5.0 (${os-name} ${os-version}; ${os-arch}; ${jdk-version}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.0000.000 Safari/537.36 ${jpom-type}/${jpom-version} + GlobalHeaders.INSTANCE.header(Header.USER_AGENT, StrUtil.format("Mozilla/5.0 ({}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.0000.000 Safari/537.36 {}", osInfo, jpomTag), true); + } + } + return JPOM_MANIFEST; + } + + public Type getType() { + return type; + } + + public long getPid() { + return pid; + } + + public void setPid(int pid) { + this.pid = pid; + } + + public String randomIdSign() { + String tempToken = SystemUtil.get("JPOM_SERVER_TEMP_TOKEN"); + return Opt.ofBlankAble(tempToken).orElseGet(() -> SecureUtil.sha1(JpomManifest.this.getPid() + JpomManifest.this.randomId)); + + } + + /** + * 获取当前运行的版本号 + * + * @return 返回当前版本号 + */ + public String getVersion() { + return version; + } + + /** + * 判断当前是否为调试模式 + * + * @return jar 为非调试模式 + */ + public boolean isDebug() { + return "dev".equals(getVersion()); + } + + public void setVersion(String version) { + if (StrUtil.isNotEmpty(version)) { + this.version = version; + } + } + + public String getJarFile() { + return jarFile; + } + + public void setJarFile(String jarFile) { + this.jarFile = jarFile; + } + + public String getTimeStamp() { + if (timeStamp == null) { + long uptime = SystemUtil.getRuntimeMXBean().getUptime(); + long statTime = System.currentTimeMillis() - uptime; + return new DateTime(statTime).toString(); + } + return timeStamp; + } + + /** + * 装换打包时间 + * + * @param timeStamp utc时间 + */ + public void setTimeStamp(String timeStamp) { + this.timeStamp = timeStamp; + } + + public void setPort(int port) { + this.port = port; + } + + /** + * 程序运行的端口 + * + * @return 端口 + */ + public int getPort() { + if (port == 0) { + port = JpomApplication.getInstance().getPort(); + } + return port; + } + + public String getDataPath() { + if (StrUtil.isEmpty(dataPath)) { + dataPath = JpomApplication.getInstance().getDataPath(); + } + return dataPath; + } + + public void setDataPath(String dataPath) { + this.dataPath = dataPath; + } + + public long getUpTime() { + return SystemUtil.getRuntimeMXBean().getUptime(); + } + + public String getOsName() { + return osName; + } + + public String getInstallId() { + return installId; + } + + public void setInstallId(String installId) { + this.installId = installId; + } + + @Override + public String toString() { + return JSONObject.toJSONString(this); + } + + /** + * 获取当前运行的路径 + * + * @return jar 或者classPath + */ + public static File getRunPath() { + URL location = ClassUtil.getLocation(JpomApplication.getAppClass()); + String file = location.getFile(); + String before = StrUtil.subBefore(file, "!", false); + return FileUtil.file(before); + } + + /** + * 升级之后的旧包 + * + * @return oldJars + */ + public static File getOldJarsPath() { + File runFile = getRunPath().getParentFile(); + return FileUtil.file(runFile, "oldJars"); + } + + /** + * 转化时间 + * + * @param timeStamp time + * @return 默认使用utc + */ + private static String parseJpomTime(String timeStamp) { + if (StrUtil.isNotEmpty(timeStamp)) { + try { + DateTime dateTime = DateUtil.parseUTC(timeStamp); + return dateTime.toStringDefaultTimeZone(); + } catch (Exception e) { + return timeStamp; + } + } else { + return "dev"; + } + } + + /** + * 检查是否为jpom包 + * + * @param path 路径 + * @param type 类型 + * @return 结果消息 + * @see Type#getApplicationClass() + */ + public static JsonMessage checkJpomJar(String path, Type type) { + return checkJpomJar(path, type, true); + } + + public static void setAllowedDowngrade(boolean allowedDowngrade) { + JpomManifest.allowedDowngrade = allowedDowngrade; + } + + /** + * 检查是否为jpom包 + * + * @param path 路径 + * @param type 类型 + * @param checkRepeat 是否检查版本重复 + * @return 结果消息 + * @see Type#getApplicationClass() + */ + public static JsonMessage checkJpomJar(String path, Type type, boolean checkRepeat) { + File jarFile = new File(path); + Tuple jarVersion = getJarVersion(jarFile); + if (jarVersion == null) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.invalid_jar_file.e80a")); + } + try (JarFile jarFile1 = new JarFile(jarFile)) { + //Manifest manifest = jarFile1.getManifest(); + //Attributes attributes = manifest.getMainAttributes(); + String mainClass = jarVersion.get(2); + if (mainClass == null) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.main_class_attribute_not_found.24c9")); + } + try (JarClassLoader jarClassLoader = JarClassLoader.load(jarFile)) { + jarClassLoader.loadClass(mainClass); + } catch (ClassNotFoundException notFound) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.main_class_not_found.b4b7") + mainClass); + } + String applicationClass = type.getApplicationClass(); + ZipEntry entry = jarFile1.getEntry(StrUtil.format("BOOT-INF/classes/{}.class", + StrUtil.replace(applicationClass, ".", StrUtil.SLASH))); + if (entry == null) { + return new JsonMessage<>(405, StrUtil.format(I18nMessageUtil.get("i18n.not_jpom_package.ea3e"), type.name())); + } + String version = jarVersion.get(0); + String timeStamp = jarVersion.get(1); + String minVersion = jarVersion.get(4); + if (StrUtil.hasEmpty(version, timeStamp, minVersion)) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.package_missing_info.e277")); + } + if (checkRepeat) { + // + JpomManifest jpomManifest = JpomManifest.getInstance(); + if (StrUtil.equals(version, jpomManifest.getVersion()) && + StrUtil.equals(timeStamp, jpomManifest.getTimeStamp())) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.new_package_same_as_running_package.e25a")); + } + if (StrUtil.compareVersion(jpomManifest.getVersion(), minVersion) < 0) { + return new JsonMessage<>(405, StrUtil.format(I18nMessageUtil.get("i18n.incompatible_program_versions.5291"), jpomManifest.getVersion(), minVersion)); + } + // 判断降级 + if (!allowedDowngrade && StrUtil.compareVersion(version, jpomManifest.getVersion()) < 0) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.online_upgrade_cannot_downgrade.d419")); + } + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_jar.a26e"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.parse_error.da6d") + e.getMessage()); + } + return new JsonMessage<>(200, "", jarVersion); + } + + /** + * 发布包到对应运行路径 + * + * @param path 文件路径 + * @param version 新版本号 + */ + public static void releaseJar(String path, String version) { + File runFile = getRunPath(); + File runPath = runFile.getParentFile(); + if (!runPath.isDirectory()) { + throw new JpomRuntimeException(runPath.getAbsolutePath() + " error"); + } + String upgrade = FileUtil.file(runPath, Const.UPGRADE).getAbsolutePath(); + JSONObject jsonObject = null; + try { + jsonObject = JsonFileUtil.readJson(upgrade); + } catch (FileNotFoundException ignored) { + } + if (jsonObject == null) { + jsonObject = new JSONObject(); + } + jsonObject.put("beforeJar", runFile.getName()); + // 如果升级的版本号一致 + if (StrUtil.equals(version, JpomManifest.getInstance().getVersion())) { + version = StrUtil.format("{}_{}", version, System.currentTimeMillis()); + } + String newFile; + File to; + while (true) { + newFile = JpomApplication.getAppType().name() + "-" + version + FileUtil.JAR_FILE_EXT; + to = FileUtil.file(runPath, newFile); + if (FileUtil.equals(to, runFile)) { + version = StrUtil.format("{}_{}", version, RandomUtil.randomInt(1, 100)); + continue; + } + break; + } + // + FileUtil.move(new File(path), to, true); + jsonObject.put("newJar", newFile); + jsonObject.put("updateTime", new DateTime().toString()); + // 新增升级次数 @author jzy 2021-08-04 + jsonObject.put("upgradeCount", jsonObject.getIntValue("upgradeCount")); + // + JsonFileUtil.saveJson(upgrade, jsonObject); + FileUtil.writeString(newFile, FileUtil.file(runPath, Const.RUN_JAR), CharsetUtil.CHARSET_UTF_8); + } + + /** + * 获取当前的管理名文件 + * + * @return file + */ + public static File getScriptFile() { + File runPath = getRunPath().getParentFile().getParentFile(); + String type = JpomApplication.getAppType().name(); + File scriptFile = FileUtil.file(runPath, "bin", StrUtil.format("{}.{}", type, CommandUtil.SUFFIX)); + Assert.state(FileUtil.isFile(scriptFile), StrUtil.format(I18nMessageUtil.get("i18n.command_script_not_found_in_service.25ac"), type, CommandUtil.SUFFIX)); + return scriptFile; + } + + /** + * 解析 jpom 安装包 + * + * @param path 文件路径 + * @param type 查找类型 + * @param savePath 保存对文件夹 + * @return 结果文件 + */ + public static File zipFileFind(String path, Type type, String savePath) throws IOException { + String extName = FileUtil.extName(path); + if (StrUtil.endWithIgnoreCase(extName, "zip")) { + try (ZipFile zipFile = ZipUtil.toZipFile(FileUtil.file(path), CharsetUtil.CHARSET_UTF_8)) { + Optional first = zipFile.stream().filter((Predicate) zipEntry -> { + String name = zipEntry.getName().toLowerCase(); + String typeName = type.name().toLowerCase(); + return StrUtil.startWith(name, "lib/" + typeName) && StrUtil.endWith(name, ".jar"); + }).findFirst(); + Assert.state(first.isPresent(), StrUtil.format(I18nMessageUtil.get("i18n.invalid_zip_file.3092"), type)); + // + ZipEntry zipEntry = first.get(); + try (InputStream stream = ZipUtil.getStream(zipFile, zipEntry)) { + String name = FileUtil.getName(zipEntry.getName()); + return FileUtil.writeFromStream(stream, FileUtil.file(savePath, name)); + } + } + } else if (StrUtil.endWithIgnoreCase(extName, "jar")) { + return FileUtil.file(path); + } + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.not_jpom_install_package.2cca")); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/LocaleConfig.java b/modules/common/src/main/java/org/dromara/jpom/common/LocaleConfig.java new file mode 100644 index 0000000000..55fb9f4c9e --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/LocaleConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; + +/** + * @author bwcx_jzy + * @since 2024/6/11 + */ +@Configuration +public class LocaleConfig { + + @Bean + public ResourceBundleMessageSource messageSource() { + ResourceBundleMessageSource source = new ResourceBundleMessageSource(); + //设置国际化文件存储路径 resources目录下 + source.setBasenames("i18n/messages"); + //设置根据key如果没有获取到对应的文本信息,则返回key作为信息 + source.setUseCodeAsDefaultMessage(true); + //设置字符编码 + source.setDefaultEncoding("UTF-8"); + return source; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/RemoteVersion.java b/modules/common/src/main/java/org/dromara/jpom/common/RemoteVersion.java new file mode 100644 index 0000000000..c748220c10 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/RemoteVersion.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.http.HttpUtil; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +/** + * 远程的版本信息 + * + *

+ * {
+ * "tag_name": "v2.6.4",
+ * "agentUrl": "",
+ * "serverUrl": "",
+ * "changelog": ""
+ * }
+ * 
+ * + * @author bwcx_jzy + * @since 2021/9/19 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Slf4j +public class RemoteVersion extends cn.keepbx.jpom.RemoteVersion { + + + @Override + public String toString() { + return JSONObject.toJSONString(cn.keepbx.jpom.RemoteVersion.cacheInfo()); + } + + /** + * 下载 + * + * @param savePath 下载文件保存路径 + * @param type 类型 + * @param checkRepeat 是否验证重复 + * @return 保存的全路径 + * @throws IOException 异常 + */ + public static Tuple download(String savePath, Type type, boolean checkRepeat) throws IOException { + cn.keepbx.jpom.RemoteVersion remoteVersion = loadRemoteInfo(); + Assert.notNull(remoteVersion, I18nMessageUtil.get("i18n.no_available_new_version_upgrade.d8f2")); + // 检查是否存在下载地址 + String remoteUrl = type.getRemoteUrl(remoteVersion); + Assert.hasText(remoteUrl, I18nMessageUtil.get("i18n.new_version_exists_download_unavailable.4ba7")); + // 下载 + File downloadFileFromUrl = HttpUtil.downloadFileFromUrl(remoteUrl, savePath); + // 解析压缩包 + File file = JpomManifest.zipFileFind(FileUtil.getAbsolutePath(downloadFileFromUrl), type, savePath); + // 检查 + JsonMessage error = JpomManifest.checkJpomJar(FileUtil.getAbsolutePath(file), type, checkRepeat); + Assert.state(error.success(), error.getMsg()); + return error.getData(); + } + + /** + * 下载 + * + * @param savePath 下载文件保存路径 + * @param type 类型 + * @return 保存的全路径 + * @throws IOException 异常 + */ + public static Tuple download(String savePath, Type type) throws IOException { + return download(savePath, type, true); + } + + /** + * 升级 + * + * @param savePath 下载文件保存路径 + * @throws IOException 异常 + */ + public static void upgrade(String savePath) throws IOException { + upgrade(savePath, null); + } + + /** + * 升级 + * + * @param savePath 下载文件保存路径 + * @param consumer 执行申请前回调 + * @throws IOException 异常 + */ + public static void upgrade(String savePath, Consumer consumer) throws IOException { + Type type = JpomManifest.getInstance().getType(); + // 下载 + Tuple data = download(savePath, type); + File file = data.get(3); + // 基础检查 + String path = FileUtil.getAbsolutePath(file); + String version = data.get(0); + JpomManifest.releaseJar(path, version); + // + if (consumer != null) { + consumer.accept(data); + } + JpomApplication.restart(); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/ReplaceStreamFilter.java b/modules/common/src/main/java/org/dromara/jpom/common/ReplaceStreamFilter.java new file mode 100644 index 0000000000..4ef6d81839 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/ReplaceStreamFilter.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * https://springboot.io/t/topic/3637 + * + * @author bwcx_jzy + * @since 2022/12/8 + */ +@Configuration +@Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE) +public class ReplaceStreamFilter implements Filter { + + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + long startTime = System.currentTimeMillis(); + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper((HttpServletRequest) request); + chain.doFilter(wrapper, response); + long endTime = System.currentTimeMillis(); + long l = endTime - startTime; + if (l > 1000 * 5) { + byte[] contentAsByteArray = wrapper.getContentAsByteArray(); + String str = StrUtil.str(contentAsByteArray, CharsetUtil.CHARSET_UTF_8); + String reqData = Opt.ofBlankAble(str) + .map(s -> wrapper.getParameterMap()) + .map(JSONObject::toJSONString) + .orElse(StrUtil.EMPTY); + log.warn("[timeout] {} {} {}", wrapper.getRequestURI(), reqData, DateUtil.formatBetween(l)); + } + } +} 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 new file mode 100644 index 0000000000..3c2cfc3dd6 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/ServerOpenApi.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +/** + * Server 开发接口api 列表 + * + * @author bwcx_jzy + * @since 2019/8/5 + */ +public class ServerOpenApi { + /** + * 用户的token + */ + public static final String USER_TOKEN_HEAD = "JPOM-USER-TOKEN"; + + /** + * 存放token的http head + */ + public static final String HTTP_HEAD_AUTHORIZATION = "Authorization"; + + public static final String API = "/api/"; + + /** + * 接收推送 + */ + public static final String RECEIVE_PUSH = API + "node/receive_push"; + + public static final String PUSH_NODE_KEY = "--auto-push-to-server"; + /** + * 触发构建(新), 第一级构建id,第二级token + */ + public static final String BUILD_TRIGGER_BUILD2 = API + "build2/{id}/{token}"; + + /** + * 触发构建 批量触发 + */ + public static final String BUILD_TRIGGER_BUILD_BATCH = API + "build_batch"; + + /** + * 文件下载 + */ + 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}"; + /** + * 获取当前构建状态 + */ + public static final String BUILD_TRIGGER_STATUS = API + "build_status"; + + /** + * 获取当前构建日志 + */ + public static final String BUILD_TRIGGER_LOG = API + "build_log"; + + /** + * SSH 脚本执行, 第一级脚本id,第二级token + */ + public static final String SSH_COMMAND_TRIGGER_URL = API + "ssh_command/{id}/{token}"; + + /** + * SSH 脚本执行 批量触发 + */ + public static final String SSH_COMMAND_TRIGGER_BATCH = API + "ssh_command_batch"; + + /** + * 服务端脚本执行, 第一级脚本id,第二级token + */ + public static final String SERVER_SCRIPT_TRIGGER_URL = API + "server_script/{id}/{token}"; + + /** + * 服务端脚本执行 批量触发 + */ + public static final String SERVER_SCRIPT_TRIGGER_BATCH = API + "server_script_batch"; + + /** + * 插件端脚本执行, 第一级脚本id,第二级token + */ + public static final String NODE_SCRIPT_TRIGGER_URL = API + "node_script/{id}/{token}"; + + /** + * 插件端脚本执行 批量触发 + */ + public static final String NODE_SCRIPT_TRIGGER_BATCH = API + "node_script_batch"; + + /** + * 项目触发器, 第一级项目id(服务端存储),第二级token + */ + public static final String SERVER_PROJECT_TRIGGER_URL = API + "project/{id}/{token}"; + + /** + * 项目触发器,批量触发 + */ + public static final String SERVER_PROJECT_TRIGGER_BATCH = API + "project_batch"; + + /** + * 环境变量, 第一级脚本id,第二级token + */ + public static final String SERVER_ENV_VAR_TRIGGER_URL = API + "env-var/{id}/{token}"; +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/i18n/I18nMessageUtil.java b/modules/common/src/main/java/org/dromara/jpom/common/i18n/I18nMessageUtil.java new file mode 100644 index 0000000000..c59f8fe139 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/i18n/I18nMessageUtil.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.i18n; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.http.Header; +import cn.hutool.system.SystemUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSource; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.function.Supplier; + +/** + * 国际化转换工具类 + * + * @author bwcx_jzy + **/ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j +public class I18nMessageUtil { + /** + * 线程中的语言 + */ + private static final ThreadLocal LANGUAGE = new ThreadLocal<>(); + /** + * 语言获取方式 + */ + private static final List> LANGUAGE_OBTAIN = new ArrayList<>(); + + static { + // 线程变量获取 + LANGUAGE_OBTAIN.add(LANGUAGE::get); + // http 请求获取 + LANGUAGE_OBTAIN.add(I18nMessageUtil::getLanguageByRequest); + // Jpom 配置获取 + LANGUAGE_OBTAIN.add(() -> SystemUtil.get("JPOM_LANG")); + // 系统语言 + LANGUAGE_OBTAIN.add(() -> { + Locale locale = Locale.getDefault(); + String country = locale.getCountry(); + if (StrUtil.equals("zh", country)) { + // 中国 + return "zh-CN"; + } + return "en-US"; + }); + } + + /** + * 尝试获取语言(系统语言) + * + * @return 语言 + */ + public static String tryGetSystemLanguage() { + String language = null; + for (int i = 2; i < LANGUAGE_OBTAIN.size(); i++) { + language = LANGUAGE_OBTAIN.get(i).get(); + if (language != null) { + break; + } + } + return language; + } + + /** + * 设置语言 + * + * @param language 语言 + */ + public static void setLanguage(String language) { + LANGUAGE.set(language); + } + + /** + * 清除语言 + */ + public static void clearLanguage() { + LANGUAGE.remove(); + } + + /** + * 获取语言 通过 http 请求 + * + * @return 语言 + */ + public static String getLanguageByRequest() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (servletRequestAttributes != null) { + HttpServletRequest request = servletRequestAttributes.getRequest(); + return ServletUtil.getHeader(request, Header.ACCEPT_LANGUAGE.getValue(), CharsetUtil.CHARSET_UTF_8); + } + return null; + } + + /** + * 语言格式化 + * + * @param language 语言 + * @return 语言 + */ + private static String normalLanguage(String language) { + language = language != null ? language.toLowerCase() : StrUtil.EMPTY; + language = StrUtil.replace(language, "_", "-"); + switch (language) { + case "en-us": + return "en-US"; + case "zh-tw": + return "zh-TW"; + case "zh-hk": + return "zh-HK"; + case "zh-cn": + default: + return "zh-CN"; + } + } + + /** + * 尝试获取语言 + * + * @return 语言 + */ + public static String tryGetNormalLanguage() { + return normalLanguage(tryGetLanguage()); + } + + /** + * 尝试获取语言 + * + * @return 语言 + */ + public static String tryGetLanguage() { + String language = null; + for (Supplier supplier : LANGUAGE_OBTAIN) { + language = supplier.get(); + if (language != null) { + break; + } + } + return language; + } + + /** + * 根据key信息获取对应语言的内容 + * + * @param key 消息key值 + * @return msg + */ + public static String get(String key) { + if (StrUtil.isEmpty(key)) { + return StrUtil.EMPTY; + } + String language = tryGetLanguage(); + language = normalLanguage(language); + Locale locale; + switch (language) { + case "zh-CN": + locale = Locale.CHINA; + break; + case "zh-TW": + locale = Locale.TAIWAN; + break; + case "zh-HK": + locale = getZhHkInstance(); + break; + case "en-US": + locale = Locale.US; + break; + default: + locale = Locale.CHINA; + log.warn("Unknown language:{}", language); + break; + } + return get(key, locale); + } + + private static String get(String key, Locale language) { + return get(key, new String[0], language); + } + + private static String get(String key, Object[] params, Locale language) { + return getInstance().getMessage(key, params, language); + } + + private static MessageSource getInstance() { + return Lazy.MESSAGE_SOURCE; + } + + private static Locale getZhHkInstance() { + return LazyZhHk.LOCALE; + } + + /** + * 使用懒加载方式实例化messageSource国际化工具 + */ + private static class Lazy { + private static final MessageSource MESSAGE_SOURCE = SpringUtil.getBean(MessageSource.class); + } + + private static class LazyZhHk { + private static final Locale LOCALE = new Locale("zh", "HK"); + } + +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/i18n/I18nThreadUtil.java b/modules/common/src/main/java/org/dromara/jpom/common/i18n/I18nThreadUtil.java new file mode 100644 index 0000000000..24a01e8f82 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/i18n/I18nThreadUtil.java @@ -0,0 +1,71 @@ +package org.dromara.jpom.common.i18n; + +import cn.hutool.core.thread.ThreadUtil; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +/** + * @author bwcx_jzy1 + * @since 2024/6/15 + */ +public class I18nThreadUtil { + + /** + * 线程执行(获取父级线程语言) + * + * @param runnable runnable + */ + public static void execute(Runnable runnable) { + String language = I18nMessageUtil.tryGetLanguage(); + ThreadUtil.execute(() -> { + try { + I18nMessageUtil.setLanguage(language); + runnable.run(); + } finally { + I18nMessageUtil.clearLanguage(); + } + }); + } + + /** + * 执行有返回值的异步方法
+ * Future代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞 + * + * @param 回调对象类型 + * @param task {@link Callable} + * @return Future + */ + public static Future execAsync(Callable task) { + String language = I18nMessageUtil.tryGetLanguage(); + return ThreadUtil.execAsync(() -> { + try { + I18nMessageUtil.setLanguage(language); + return task.call(); + } finally { + I18nMessageUtil.clearLanguage(); + } + }); + } + + /** + * 执行有返回值的异步方法
+ * Future代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞 + * + * @param runnable 可运行对象 + * @return {@link Future} + * @since 3.0.5 + */ + public static Future execAsync(Runnable runnable) { + String language = I18nMessageUtil.tryGetLanguage(); + return ThreadUtil.execAsync(() -> { + try { + I18nMessageUtil.setLanguage(language); + runnable.run(); + } finally { + I18nMessageUtil.clearLanguage(); + } + }); + } + +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/interceptor/HandlerMethodInterceptor.java b/modules/common/src/main/java/org/dromara/jpom/common/interceptor/HandlerMethodInterceptor.java new file mode 100644 index 0000000000..7f81e81c94 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/interceptor/HandlerMethodInterceptor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author bwcx_jzy + * @since 2022/12/8 + */ +public interface HandlerMethodInterceptor extends AsyncHandlerInterceptor { + + /** + * 默认执行 + * + * @param request current HTTP request + * @param response current HTTP response + * @param handler chosen handler to execute, for type and/or instance evaluation + * @return 是否放行 + * @throws Exception 发生异常 + */ + @Override + default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (handler instanceof HandlerMethod) { + return this.preHandle(request, response, (HandlerMethod) handler); + } + return true; + } + + /** + * 默认执行 + * + * @param request current HTTP request + * @param response current HTTP response + * @param handler chosen handler to execute, for type and/or instance evaluation + * @return 是否放行 + * @throws Exception 发生异常 + */ + boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler) throws Exception; +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/transport/BodyRewritingRequestWrapper.java b/modules/common/src/main/java/org/dromara/jpom/common/transport/BodyRewritingRequestWrapper.java new file mode 100644 index 0000000000..040934cf59 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/transport/BodyRewritingRequestWrapper.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.transport; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * @author loyal.f + * @since 2023/3/13 + */ +public class BodyRewritingRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public BodyRewritingRequestWrapper(HttpServletRequest request, byte[] body) { + super(request); + this.body = body; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return byteArrayInputStream.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + throw new UnsupportedOperationException(); + } + + @Override + public int read() throws IOException { + return byteArrayInputStream.read(); + } + }; + } + + @Override + public BufferedReader getReader() throws IOException { + InputStreamReader inputStreamReader = new InputStreamReader(getInputStream(), StandardCharsets.UTF_8); + return new BufferedReader(inputStreamReader); + } +} + diff --git a/modules/common/src/main/java/org/dromara/jpom/common/transport/MultipartRequestWrapper.java b/modules/common/src/main/java/org/dromara/jpom/common/transport/MultipartRequestWrapper.java new file mode 100644 index 0000000000..7456ae2116 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/transport/MultipartRequestWrapper.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.transport; + +import cn.hutool.core.util.ArrayUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.encrypt.Encryptor; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * @author loyal.f + * @since 2023/3/13 + */ +@Slf4j +public class MultipartRequestWrapper extends StandardMultipartHttpServletRequest { + + private final Map parameterMap; + + public MultipartRequestWrapper(HttpServletRequest request, Encryptor encryptor) { + super(request); + Map parameterMap = super.getParameterMap(); + Map decryptMap = new HashMap<>(); + try { + for (Map.Entry entry : parameterMap.entrySet()) { + String key = entry.getKey(); + String[] value = entry.getValue(); + for (int i = 0; i < value.length; i++) { + value[i] = encryptor.decrypt(value[i]); + } + decryptMap.put(encryptor.decrypt(key), value); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.decrypt_failure.ad83"), e); + } + this.parameterMap = decryptMap; + // 处理文件名 + MultiValueMap multipartFiles = super.getMultipartFiles(); + try { + MultiValueMap files = new LinkedMultiValueMap<>(multipartFiles.size()); + for (String key : multipartFiles.keySet()) { + files.put(encryptor.decrypt(key), multipartFiles.remove(key)); + } + setMultipartFiles(files); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.decrypt_failure.ad83"), e); + } + } + + + @Override + public Map getParameterMap() { + return parameterMap; + } + + @Override + public String getParameter(String name) { + String[] values = parameterMap.get(name); + return ArrayUtil.get(values, 0); + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(parameterMap.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + return parameterMap.get(name); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/transport/ParameterRequestWrapper.java b/modules/common/src/main/java/org/dromara/jpom/common/transport/ParameterRequestWrapper.java new file mode 100644 index 0000000000..22cd99e5a9 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/transport/ParameterRequestWrapper.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.transport; + +import cn.hutool.core.util.ArrayUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.encrypt.Encryptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * @author loyal.f + * @since 2023/3/13 + */ +@Slf4j +public class ParameterRequestWrapper extends HttpServletRequestWrapper { + + private final Map parameterMap; + + public ParameterRequestWrapper(HttpServletRequest request, Encryptor encryptor) { + super(request); + Map parameterMap = request.getParameterMap(); + Map decryptMap = new HashMap<>(); + try { + for (Map.Entry entry : parameterMap.entrySet()) { + String key = entry.getKey(); + String[] value = entry.getValue(); + for (int i = 0; i < value.length; i++) { + value[i] = encryptor.decrypt(value[i]); + } + decryptMap.put(encryptor.decrypt(key), value); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.decrypt_failure.ad83"), e); + } + this.parameterMap = decryptMap; + } + + @Override + public Map getParameterMap() { + return parameterMap; + } + + @Override + public String getParameter(String name) { + String[] values = parameterMap.get(name); + return ArrayUtil.get(values, 0); + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(parameterMap.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + return parameterMap.get(name); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/validator/ErrorCondition.java b/modules/common/src/main/java/org/dromara/jpom/common/validator/ErrorCondition.java new file mode 100644 index 0000000000..e91037af0c --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/validator/ErrorCondition.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.validator; + +/** + * 错误条件 + * + * @author bwcx_jzy + * @since 2018/12/25 + */ +public enum ErrorCondition { + /** + * 或者 + */ + OR, + /** + * 并且 + */ + AND +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/validator/ParameterInterceptor.java b/modules/common/src/main/java/org/dromara/jpom/common/validator/ParameterInterceptor.java new file mode 100644 index 0000000000..564a92c646 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/validator/ParameterInterceptor.java @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.validator; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.Header; +import cn.hutool.http.HtmlUtil; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.HandlerMethodInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.ValueConstants; +import org.springframework.web.method.HandlerMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * 参数拦截器 验证参数是否正确 排序号是:-100 + *

+ * 配置方法 + * + * @author bwcx_jzy + * @since 2018/8/21. + */ +@Slf4j +@Configuration +public class ParameterInterceptor implements HandlerMethodInterceptor { + /** + * int 类型的数字输入最大长度 防止数据库字段溢出 + */ + public static int INT_MAX_LENGTH = 7; + private final Interceptor interceptor = new DefaultInterceptor(); + + /** + * 获取值 + * + * @param validatorConfig 验证规则 + * @param request req + * @param name name + * @param item item + * @return val + */ + private String getValue(ValidatorConfig validatorConfig, HttpServletRequest request, String name, MethodParameter item) { + // 获取值 + String value; + // 指定name + String configName = null; + if (validatorConfig != null) { + configName = validatorConfig.name(); + } + if (StrUtil.isNotEmpty(configName)) { + value = request.getParameter(configName); + } else { + value = request.getParameter(name); + } + // 默认值 + if (validatorConfig != null && !ValueConstants.DEFAULT_NONE.equals(validatorConfig.defaultVal())) { + if (value == null && !validatorConfig.strEmpty()) { + value = validatorConfig.defaultVal(); + } + if (StrUtil.isEmpty(value) && validatorConfig.strEmpty()) { + value = validatorConfig.defaultVal(); + } + } + return value; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { + String language = ServletUtil.getHeader(request, Header.ACCEPT_LANGUAGE.getValue(), CharsetUtil.CHARSET_UTF_8); + I18nMessageUtil.setLanguage(language); + MethodParameter[] methodParameters = handlerMethod.getMethodParameters(); + for (MethodParameter item : methodParameters) { + ValidatorItem[] validatorItems; + ValidatorConfig validatorConfig = item.getParameterAnnotation(ValidatorConfig.class); + if (validatorConfig == null) { + ValidatorItem validatorItem = item.getParameterAnnotation(ValidatorItem.class); + if (validatorItem == null) { + continue; + } else { + validatorItems = new ValidatorItem[]{validatorItem}; + } + } else { + validatorItems = validatorConfig.value(); + } + String name = item.getParameterName(); + if (name == null) { + continue; + } + String value = getValue(validatorConfig, request, name, item); + // 验证每一项 + int errorCount = 0; + for (int i = 0, len = validatorItems.length; i < len; i++) { + ValidatorItem validatorItem = validatorItems[i]; + if (validatorItem.unescape()) { + value = HtmlUtil.unescape(value); + } + if (validatorConfig != null && validatorItem.value() == ValidatorRule.CUSTOMIZE) { + if (!customize(handlerMethod, item, validatorConfig, validatorItem, name, value, request, response)) { + return false; + } + // 自定义条件只识别一次 + break; + } + boolean error = validator(validatorItem, value); + if (validatorConfig == null) { + if (!error) { + //错误 + interceptor.error(request, response, name, value, validatorItem); + return false; + } + } else { + if (validatorConfig.errorCondition() == ErrorCondition.AND) { + if (!error) { + //错误 + interceptor.error(request, response, name, value, validatorItem); + return false; + } + } + if (validatorConfig.errorCondition() == ErrorCondition.OR) { + if (error) { + break; + } else { + errorCount++; + if (i < len - 1) { + continue; + } + // 最后一项 + if (i == len - 1 && errorCount == len) { + //错误 + interceptor.error(request, response, name, value, validatorItem); + return false; + } + } + } + } + } + } + return true; + } + + /** + * 自定义参数效验 + * + * @param handlerMethod method + * @param validatorConfig config + * @param validatorItem 效验规则 + * @param methodParameter 参数对象 + * @param name 参数名 + * @param value 值 + * @return true 通过效验 + * @throws InvocationTargetException 反射异常 + * @throws IllegalAccessException 反射异常 + */ + private boolean customize(HandlerMethod handlerMethod, MethodParameter methodParameter, ValidatorConfig validatorConfig, ValidatorItem validatorItem, String name, String value, + HttpServletRequest request, HttpServletResponse response + ) throws InvocationTargetException, IllegalAccessException { + // 自定义验证 + Method method; + try { + method = ReflectUtil.getMethod(handlerMethod.getBeanType(), validatorConfig.customizeMethod(), MethodParameter.class, String.class); + } catch (SecurityException s) { + // 没有权限访问 直接拦截 + log.error(s.getMessage(), s); + interceptor.error(request, response, name, value, validatorItem); + return false; + } + if (method == null) { + // 没有配置对应方法 + log.error(I18nMessageUtil.get("i18n.verification_method_not_configured.7358"), handlerMethod.getBeanType(), validatorConfig.customizeMethod()); + interceptor.error(request, response, name, value, validatorItem); + return false; + } + Object obj = method.invoke(handlerMethod.getBean(), methodParameter, value); + if (!Convert.toBool(obj, false)) { + interceptor.error(request, response, name, value, validatorItem); + return false; + } + return true; + } + + /** + * 获取长度范围 + * + * @param range 范围 + * @return int数组 + */ + private int[] spiltRange(String range) { + if (StrUtil.isEmpty(range)) { + return null; + } + if (range.contains(StrUtil.COLON)) { + // 范围 + String[] ranges = StrUtil.splitToArray(range, StrUtil.COLON); + if (ranges != null && ranges.length == 2) { + int start = Convert.toInt(ranges[0]); + int end = Convert.toInt(ranges[1]); + return new int[]{start, end}; + } + } else { + + // 具体某个值 + int len = Convert.toInt(range); + return new int[]{len}; + } + return null; + } + + /** + * 拆分验证范围 + * + * @param range 范围字符串 + * @return 数组 + */ + private Double[] spiltRangeDouble(String range) { + if (StrUtil.isEmpty(range)) { + return null; + } + Double[] doubles = new Double[3]; + if (range.contains(StrUtil.BRACKET_START) && range.endsWith(StrUtil.BRACKET_END)) { + int start = range.indexOf(StrUtil.BRACKET_START); + int end = range.indexOf(StrUtil.BRACKET_END); + int len = Convert.toInt(range.substring(start + 1, end)); + doubles[2] = (double) len; + range = range.substring(0, start); + } + if (range.contains(StrUtil.COLON)) { + String[] ranges = StrUtil.splitToArray(range, StrUtil.COLON); + if (ranges != null && ranges.length == 2) { + doubles[0] = Convert.toDouble(ranges[0]); + doubles[1] = Convert.toDouble(ranges[1]); + } + } else { + doubles[0] = Convert.toDouble(range); + } + return doubles; + } + + private boolean validator(final ValidatorItem validatorItem, String value) { + ValidatorRule validatorRule = validatorItem.value(); + switch (validatorRule) { + case EMPTY: + if (Validator.isNotEmpty(value)) { + return false; + } + break; + case NOT_EMPTY: + case NOT_BLANK: { + if (validatorRule == ValidatorRule.NOT_EMPTY) { + if (Validator.isEmpty(value)) { + return false; + } + } else { + if (StrUtil.isBlank(value)) { + return false; + } + } + if (value == null) { + return false; + } + int valLen = value.length(); + int[] ranges = spiltRange(validatorItem.range()); + if (ranges != null) { + if (ranges.length == 1) { + if (ranges[0] != valLen) { + return false; + } + } else { + if (valLen < ranges[0] || valLen > ranges[1]) { + return false; + } + } + } + } + break; + case GENERAL: { + int[] ranges = spiltRange(validatorItem.range()); + if (ranges == null) { + if (!Validator.isGeneral(value)) { + return false; + } + } else if (ranges.length == 1) { + if (!Validator.isGeneral(value, ranges[0])) { + return false; + } + } else { + if (!Validator.isGeneral(value, ranges[0], ranges[1])) { + return false; + } + } + } + break; + case DECIMAL: + case NUMBERS: + if (!validatorNumber(validatorItem, value)) { + return false; + } + break; + case POSITIVE_INTEGER: + case NON_ZERO_INTEGERS: + String reg = validatorRule == ValidatorRule.POSITIVE_INTEGER ? "^\\+?[0-9]*$" : "^\\+?[1-9][0-9]*$"; + if (!Validator.isMatchRegex(reg, value)) { + return false; + } + // 强制现在整数不能超过7位 + if (value.length() > INT_MAX_LENGTH) { + return false; + } + if (!validatorNumber(validatorItem, value)) { + return false; + } + break; + default: + break; + } + return validator2(validatorItem, value); + } + + /** + * 数字类型的 + * + * @param validatorItem 规则 + * @param value 值 + * @return true 正确的 + */ + private boolean validatorNumber(final ValidatorItem validatorItem, String value) { + Double[] douRange = spiltRangeDouble(validatorItem.range()); + if (douRange != null && douRange[2] != null) { + int len = douRange[2].intValue(); + // 小数 + if (!Validator.isMatchRegex("\\d+\\.\\d{" + len + "}$", value)) { + return false; + } + } else if (!Validator.isNumber(value)) { + return false; + } + if (douRange != null) { + if (douRange[1] == null && douRange[0] != null) { + // 具体某个值 + Double doubleVal = Convert.toDouble(value); + return douRange[0].equals(doubleVal); + } else if (douRange[1] != null && douRange[0] != null) { + // 范围 + if (douRange[0] <= douRange[1]) { + Double doubleVal = Convert.toDouble(value); + return doubleVal <= douRange[1] && doubleVal >= douRange[0]; + } + } + } + return true; + } + + /** + * 普通的验证规则 + * + * @param validatorItem 规则item + * @param value 值 + * @return true通过 + */ + private boolean validator2(final ValidatorItem validatorItem, String value) { + ValidatorRule validatorRule = validatorItem.value(); + switch (validatorRule) { + case EMAIL: + if (!Validator.isEmail(value)) { + return false; + } + break; + case MOBILE: + if (!Validator.isMobile(value)) { + return false; + } + break; + case URL: + if (!Validator.isUrl(value)) { + return false; + } + break; + case WORD: + if (!Validator.isWord(value)) { + return false; + } + break; + case CHINESE: + if (!Validator.isChinese(value)) { + return false; + } + break; + default: + break; + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + BaseJpomController.clearResources(); + I18nMessageUtil.clearLanguage(); + } + + /** + * 验证拦截器 + */ + public interface Interceptor { + /** + * 拦截到 + * + * @param request ree + * @param response res + * @param parameterName 参数名 + * @param value 值 + * @param validatorItem 验证规则 + */ + void error(final HttpServletRequest request, final HttpServletResponse response, final String parameterName, final String value, final ValidatorItem validatorItem); + + /** + * 获取参数 + * + * @param request req + * @param parameterName 参数名 + * @return 值 + */ + String getParameter(final HttpServletRequest request, final String parameterName); + } + + /** + * 默认的参数拦截 + */ + public static class DefaultInterceptor implements Interceptor { + @Override + public void error(HttpServletRequest request, HttpServletResponse response, String parameterName, String value, ValidatorItem validatorItem) { + String msg = validatorItem.msg(); + if (StrUtil.isEmpty(msg)) { + msg = I18nMessageUtil.get("i18n.parameter_validation_failed.f0a1"); + } + JsonMessage jsonMessage = new JsonMessage<>(validatorItem.code(), msg); + log.warn("{} {} {} {} {}", request.getRequestURI(), parameterName, value, validatorItem.value(), jsonMessage); + ServletUtil.write(response, jsonMessage.toString(), MediaType.APPLICATION_JSON_VALUE); + } + + @Override + public String getParameter(HttpServletRequest request, String parameterName) { + return null; + } + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorConfig.java b/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorConfig.java new file mode 100644 index 0000000000..cb7ddd7da1 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.validator; + +import org.springframework.web.bind.annotation.ValueConstants; + +import java.lang.annotation.*; + +/** + * 字段验证配置 + * + * @author bwcx_jzy + * @since 2018/8/21. + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ValidatorConfig { + /** + * 需要验证的规则 + * + * @return ValidatorItem + */ + ValidatorItem[] value() default + { + @ValidatorItem(value = ValidatorRule.NOT_EMPTY) + }; + + /** + * 自动参数值 + * + * @return url 参数 + */ + String name() default ""; + + /** + * 默认值 + * + * @return 默认 + */ + String defaultVal() default ValueConstants.DEFAULT_NONE; + + /** + * 自定义验证 Controller 中方法名 + *

+ * public boolean customizeValidator(MethodParameter methodParameter, String value) + * + * @return 默认 customizeValidator + */ + String customizeMethod() default "customizeValidator"; + + /** + * 判断参数为空 是字符串空 + * 如果为false + * + * @return 默认true + */ + boolean strEmpty() default true; + + /** + * 错误条件 + *

+ * or 一项正确返回正确,所有错误抛出错误 + *

+ * and 一项错误 抛出错误并结束整个判断 + * + * @return 默认or + */ + ErrorCondition errorCondition() default ErrorCondition.AND; +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorItem.java b/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorItem.java new file mode 100644 index 0000000000..f0e7b0bca3 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorItem.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.validator; + +import java.lang.annotation.*; + +/** + * 验证规则 + * + * @author bwcx_jzy + * @since 2018/8/21 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ValidatorItem { + /** + * 规则 + * + * @return ValidatorRule + */ + ValidatorRule value() default ValidatorRule.NOT_EMPTY; + + /** + * 还原html 转义字符 + * 一般用户字符串长度验证统一性 + * + * @return 默认不还原 + */ + boolean unescape() default false; + + /** + * 响应码 + * + * @return 默认400 + */ + int code() default 400; + + /** + * 错误信息 + * + * @return msg + */ + String msg() default ""; + + /** + * 数字类型的范围 + * 配置错误将不判断 + *

+ * 范围写反也将不判断 + *

+ * 逻辑判断符 是 > 或者 < + *

+ * 当 ValidatorRule 为 CUSTOMIZE 时此参数无效 + *

+ * 1 则为长度必须为1 + *

+ * 1.2:2 double类型的范围,值为1.2~2 + * + *

+ * 1.2:2.5[1] double类型的范围,值为1.2~2.5 且小数点只能有一位 + * + * @return 具体的规则 + * @see ValidatorRule#DECIMAL + * @see ValidatorRule#NUMBERS + * @see ValidatorRule#POSITIVE_INTEGER + * @see ValidatorRule#NON_ZERO_INTEGERS + * @see ValidatorRule#NOT_EMPTY + * @see ValidatorRule#NOT_BLANK + * @see ValidatorRule#GENERAL + */ + String range() default ""; +} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorRule.java b/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorRule.java new file mode 100644 index 0000000000..13731980cc --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/common/validator/ValidatorRule.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.validator; + +/** + * 验证规则 + * + * @author bwcx_jzy + * @since 2018/8/21. + */ +public enum ValidatorRule { + /** + * 不为空 + */ + NOT_EMPTY, + /** + * 空 + */ + EMPTY, + /** + * 不为空白 + */ + NOT_BLANK, + /** + * 手机号码 + */ + MOBILE, + /** + * 邮箱 + */ + EMAIL, + /** + * 英文字母 、数字和下划线 + */ + GENERAL, + /** + * url + */ + URL, + /** + * 汉字 + */ + CHINESE, + /** + * 是否是字母(包括大写和小写字母) + */ + WORD, + /** + * 小数 + * + * @see ParameterInterceptor#validatorNumber(cn.bwcx_jzy.common.validator.ValidatorItem, String) + */ + DECIMAL, + /** + * 数字 + * + * @see ParameterInterceptor#validatorNumber(cn.bwcx_jzy.common.validator.ValidatorItem, String) + */ + NUMBERS, + /** + * 非零 正整数 最大长度7位 + */ + NON_ZERO_INTEGERS, + /** + * 正整数 包括0 最大长度7位 + */ + POSITIVE_INTEGER, + /** + * 自定义验证 + */ + CUSTOMIZE +} diff --git a/modules/common/src/main/java/org/dromara/jpom/controller/BaseMyErrorController.java b/modules/common/src/main/java/org/dromara/jpom/controller/BaseMyErrorController.java new file mode 100644 index 0000000000..7b28b3d37a --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/controller/BaseMyErrorController.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * @author bwcx_jzy + * @see org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController + * @since 2021/3/17 + */ +@Slf4j +public abstract class BaseMyErrorController extends AbstractErrorController { + + public static final Supplier FILE_MAX_SIZE_MSG = () -> I18nMessageUtil.get("i18n.file_too_large.9994"); + + public BaseMyErrorController(ErrorAttributes errorAttributes) { + super(errorAttributes); + } + + @RequestMapping + public ResponseEntity> error(HttpServletRequest request) { + HttpStatus status = getStatus(request); + if (status == HttpStatus.NO_CONTENT) { + return new ResponseEntity<>(status); + } + Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + String requestUri = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); + // 判断异常信息 + Object attribute = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); + Map body = new HashMap<>(5); + body.put(JsonMessage.CODE, HttpStatus.INTERNAL_SERVER_ERROR.value()); + String msg = I18nMessageUtil.get("i18n.general_error_message.728a"); + if (attribute instanceof MaxUploadSizeExceededException) { + // 上传文件大小异常 + msg = FILE_MAX_SIZE_MSG.get(); + log.error(I18nMessageUtil.get("i18n.file_upload_exception.a5f6"), statusCode, requestUri); + } else if (status == HttpStatus.NOT_FOUND) { + msg = I18nMessageUtil.get("i18n.no_resource_found.dc22"); + body.put(JsonMessage.DATA, requestUri); + } else { + log.error(I18nMessageUtil.get("i18n.unexpected_exception_with_details.247d"), statusCode, requestUri); + } + body.put(JsonMessage.MSG, msg); + + return new ResponseEntity<>(body, HttpStatus.OK); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/controller/RobotsController.java b/modules/common/src/main/java/org/dromara/jpom/controller/RobotsController.java new file mode 100644 index 0000000000..98e4a656eb --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/controller/RobotsController.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.extra.servlet.ServletUtil; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.net.URL; + +/** + * robots 接口 + * + * @author bwcx_jzy + * @since 2022/3/5 + */ +@RestController +public class RobotsController { + + @GetMapping(value = "robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) + public void robots(HttpServletResponse response) { + URL resource = ResourceUtil.getResource("robots.txt"); + String readString = FileUtil.readString(resource, CharsetUtil.CHARSET_UTF_8); + ServletUtil.write(response, readString, MediaType.TEXT_PLAIN_VALUE); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/cron/CronUtils.java b/modules/common/src/main/java/org/dromara/jpom/cron/CronUtils.java new file mode 100644 index 0000000000..d74e8416b0 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/cron/CronUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.cron; + +import cn.hutool.core.date.SystemClock; +import cn.hutool.cron.CronUtil; +import cn.hutool.cron.Scheduler; +import cn.hutool.cron.TaskExecutor; +import cn.hutool.cron.listener.TaskListener; +import cn.hutool.cron.pattern.CronPattern; +import cn.hutool.cron.task.CronTask; +import cn.hutool.cron.task.Task; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2019/7/12 + **/ +@Slf4j +public class CronUtils { + + private static final Map TASK_STAT = new ConcurrentHashMap<>(50); + + /** + * 任务统计 + */ + public static class TaskStat { + /** + * 执行次数 + */ + private final AtomicInteger executeCount = new AtomicInteger(0); + /** + * 失败次数 + */ + private final AtomicInteger failedCount = new AtomicInteger(0); + /** + * 成功次数 + */ + private final AtomicInteger succeedCount = new AtomicInteger(0); + /** + * 最后执行时间 + */ + private Long lastExecuteTime; + /** + * 描述 + */ + private final String desc; + + public TaskStat(String desc) { + this.desc = desc; + } + + public void onStart() { + this.lastExecuteTime = SystemClock.now(); + this.executeCount.incrementAndGet(); + } + + public void onSucceeded() { + this.succeedCount.incrementAndGet(); + } + + public void onFailed(String tag, Throwable exception) { + this.failedCount.incrementAndGet(); + log.error(I18nMessageUtil.get("i18n.scheduled_task_exception.f077"), tag, exception); + } + } + + /** + * 开始 + */ + public static void start() { + // + Scheduler scheduler = CronUtil.getScheduler(); + // + boolean started = scheduler.isStarted(); + if (started) { + return; + } + synchronized (CronUtils.class) { + started = scheduler.isStarted(); + if (started) { + return; + } + CronUtil.start(); + scheduler.addListener(new TaskListener() { + @Override + public void onStart(TaskExecutor executor) { + CronTask cronTask = executor.getCronTask(); + TaskStat taskStat = CronUtils.getTaskStat(cronTask.getId(), null); + taskStat.onStart(); + } + + @Override + public void onSucceeded(TaskExecutor executor) { + CronTask cronTask = executor.getCronTask(); + TaskStat taskStat = CronUtils.getTaskStat(cronTask.getId(), null); + taskStat.onSucceeded(); + } + + @Override + public void onFailed(TaskExecutor executor, Throwable exception) { + CronTask cronTask = executor.getCronTask(); + TaskStat taskStat = CronUtils.getTaskStat(cronTask.getId(), null); + taskStat.onFailed(cronTask.getId(), exception); + } + }); + } + } + + /** + * 获取任务统计 + * + * @param id 任务id + * @return 统计对象 + */ + public static TaskStat getTaskStat(String id, String desc) { + return TASK_STAT.computeIfAbsent(id, s -> new TaskStat(desc)); + } + + /** + * 获取任务列表 + * + * @return list + */ + public static List list() { + Scheduler scheduler = CronUtil.getScheduler(); + Set> entries = TASK_STAT.entrySet(); + return entries.stream() + .map(entry -> { + TaskStat taskStat = entry.getValue(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("taskId", entry.getKey()); + CronPattern pattern = scheduler.getPattern(entry.getKey()); + Optional.ofNullable(pattern).ifPresent(cronPattern -> jsonObject.put("cron", cronPattern.toString())); + if (taskStat != null) { + jsonObject.put("executeCount", taskStat.executeCount.get()); + jsonObject.put("failedCount", taskStat.failedCount.get()); + jsonObject.put("succeedCount", taskStat.succeedCount.get()); + jsonObject.put("lastExecuteTime", taskStat.lastExecuteTime); + jsonObject.put("desc", taskStat.desc); + } + return jsonObject; + }) + .collect(Collectors.toList()); + } + + /** + * 添加任务 已经存在则不添加 + * + * @param id 任务ID + * @param cron 表达式 + * @param supplier 创建任务回调 + */ + public static void add(String id, String cron, Supplier supplier) { + Scheduler scheduler = CronUtil.getScheduler(); + Task task = scheduler.getTask(id); + if (task != null) { + return; + } + scheduler.schedule(id, cron, supplier.get()); + // + CronUtils.start(); + } + + /** + * 添加任务、自动去重 + * + * @param id 任务ID + * @param cron 表达式 + * @param task 任务作业 + */ + public static void upsert(String id, String cron, Task task) { + Scheduler scheduler = CronUtil.getScheduler(); + Task schedulerTask = scheduler.getTask(id); + if (schedulerTask != null) { + CronUtil.remove(id); + } + // 创建任务 + CronUtil.schedule(id, cron, task); + // + CronUtils.start(); + } + + /** + * 停止定时任务 + * + * @param id ID + */ + public static void remove(String id) { + CronUtil.remove(id); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/exception/BaseExceptionHandler.java b/modules/common/src/main/java/org/dromara/jpom/exception/BaseExceptionHandler.java new file mode 100644 index 0000000000..f6b1c48a60 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/exception/BaseExceptionHandler.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.exception; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.exceptions.ValidateException; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.SystemUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.BaseMyErrorController; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.yaml.snakeyaml.constructor.ConstructorException; +import org.yaml.snakeyaml.scanner.ScannerException; + +import javax.servlet.http.HttpServletRequest; +import java.nio.file.AccessDeniedException; + +/** + * @author bwcx_jzy + * @since 2022/4/16 + */ +@Slf4j +public abstract class BaseExceptionHandler { + + /** + * 声明要捕获的异常 + * + * @param request 请求 + * @param e 异常 + */ + @ExceptionHandler({JpomRuntimeException.class, RuntimeException.class, Exception.class}) + @ResponseBody + public IJsonMessage defExceptionHandler(HttpServletRequest request, Exception e) { + if (e instanceof JpomRuntimeException) { + log.error("global handle exception: {} {}", request.getRequestURI(), e.getMessage(), e.getCause()); + return new JsonMessage<>(500, e.getMessage()); + } else { + log.error("global handle exception: {}", request.getRequestURI(), e); + boolean causedBy = ExceptionUtil.isCausedBy(e, AccessDeniedException.class); + if (causedBy) { + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.operation_file_permission_exception.5a41") + e.getMessage()); + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.service_exception.3821") + e.getMessage()); + } + } + + @ExceptionHandler({NullPointerException.class}) + @ResponseBody + public IJsonMessage defNullPointerExceptionHandler(HttpServletRequest request, Exception e) { + log.error("global NullPointerException: {}", request.getRequestURI(), e); + String jpomType = SystemUtil.get("JPOM_TYPE", StrUtil.EMPTY); + return new JsonMessage<>(500, jpomType + I18nMessageUtil.get("i18n.program_error_null_pointer.12e1")); + } + + /** + * 声明要捕获的异常 (参数或者状态异常) + * + * @param request 请求 + * @param e 异常 + */ + @ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class, ValidateException.class}) + @ResponseBody + public IJsonMessage paramExceptionHandler(HttpServletRequest request, Exception e) { + if (log.isDebugEnabled()) { + log.debug("controller {}", request.getRequestURI(), e); + } else { + log.warn("controller {} {}", request.getRequestURI(), e.getMessage()); + } + return new JsonMessage<>(405, e.getMessage()); + } + + + @ExceptionHandler({HttpMessageNotReadableException.class, HttpMessageConversionException.class}) + @ResponseBody + public IJsonMessage handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.warn(I18nMessageUtil.get("i18n.parameter_parsing_exception.0056"), e.getMessage()); + return new JsonMessage<>(HttpStatus.EXPECTATION_FAILED.value(), I18nMessageUtil.get("i18n.incorrect_parameter_format.9efb")); + } + + @ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class}) + @ResponseBody + public IJsonMessage handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { + return new JsonMessage<>(HttpStatus.METHOD_NOT_ALLOWED.value(), I18nMessageUtil.get("i18n.unsupported_request_method.45d7"), e.getMessage()); + } + + @ExceptionHandler({NoHandlerFoundException.class}) + @ResponseBody + public IJsonMessage handleNoHandlerFoundException(NoHandlerFoundException e) { + return new JsonMessage<>(HttpStatus.NOT_FOUND.value(), I18nMessageUtil.get("i18n.no_resource_found.dc22"), e.getMessage()); + } + + /** + * 上传文件大小超出限制 + * + * @param e 异常 + */ + @ExceptionHandler({MaxUploadSizeExceededException.class}) + @ResponseBody + public IJsonMessage handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) { + log.error(I18nMessageUtil.get("i18n.file_size_exceeds_limit.8272"), e); + return new JsonMessage<>(HttpStatus.NOT_ACCEPTABLE.value(), BaseMyErrorController.FILE_MAX_SIZE_MSG.get(), e.getMessage()); + } + + @ExceptionHandler({ConstructorException.class}) + @ResponseBody + public IJsonMessage handleConstructorException(ConstructorException e) { + log.warn(I18nMessageUtil.get("i18n.yml_configuration_content_error.08f8"), e); + return new JsonMessage<>(HttpStatus.EXPECTATION_FAILED.value(), I18nMessageUtil.get("i18n.yml_config_format_error_illegal_field.16ea") + e.getMessage()); + } + + @ExceptionHandler({ScannerException.class}) + @ResponseBody + public IJsonMessage handleScannerException(ScannerException e) { + log.warn("ScannerException", e); + return new JsonMessage<>(HttpStatus.EXPECTATION_FAILED.value(), I18nMessageUtil.get("i18n.yml_config_format_error_tab.f629") + e.getMessage()); + } + +} diff --git a/modules/common/src/main/java/org/dromara/jpom/exception/IllegalArgument2Exception.java b/modules/common/src/main/java/org/dromara/jpom/exception/IllegalArgument2Exception.java new file mode 100644 index 0000000000..1da9036f12 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/exception/IllegalArgument2Exception.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.exception; + +/** + * @author bwcx_jzy + * @since 2023/2/10 + */ +public class IllegalArgument2Exception extends IllegalArgumentException { + + public IllegalArgument2Exception(String s) { + super(s); + } +} 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 0000000000..8c4b7be45c --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/exception/LogRecorderCloseException.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.exception; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +/** + * @author bwcx_jzy + * @since 24/1/4 004 + */ +public class LogRecorderCloseException extends IllegalStateException { + public LogRecorderCloseException() { + super(I18nMessageUtil.get("i18n.log_recorder_error_message.ee3e")); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/AfterOpt.java b/modules/common/src/main/java/org/dromara/jpom/model/AfterOpt.java new file mode 100644 index 0000000000..da3caef582 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/AfterOpt.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +/** + * @author bwcx_jzy + * @since 2020/3/21 + */ +public enum AfterOpt implements BaseEnum { + /** + * 操作 + */ + No(0, "不做任何操作"), + /** + * 并发执行项目分发 + */ + Restart(1, "并发重启"), + /** + * 顺序执行项目分发 + */ + Order_Must_Restart(2, "完整顺序重启(有重启失败将结束本次)"), + /** + * 顺序执行项目分发 + */ + Order_Restart(3, "顺序重启(有重启失败将继续)"), + ; + private final int code; + private final String desc; + + AfterOpt(int code, String desc) { + this.code = code; + this.desc = desc; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/AgentFileModel.java b/modules/common/src/main/java/org/dromara/jpom/model/AgentFileModel.java new file mode 100644 index 0000000000..7501cda57c --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/AgentFileModel.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author lf + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AgentFileModel extends BaseModel { + + /** + * 保存Agent文件 + */ + public static final String ID = "AGENT_FILE"; + /** + * 最新插件端包的文件名 + */ + public static final String ZIP_NAME = "agent.zip"; + /** + * 默认空版本信息 + */ + public static final AgentFileModel EMPTY = new AgentFileModel(); + /** + * 文件大小 + */ + private Long size; + /** + * 保存路径 + */ + private String savePath; + /** + * 版本号 + */ + private String version; + /** + * jar 打包时间 + */ + private String timeStamp; + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/BaseEnum.java b/modules/common/src/main/java/org/dromara/jpom/model/BaseEnum.java new file mode 100644 index 0000000000..2b63a67548 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/BaseEnum.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.log.StaticLog; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 基础枚举接口 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +public interface BaseEnum { + /** + * 缓存 + */ + class Cache { + private static final Map, Map> CLASS_MAP_MAP = new HashMap<>(); + @SuppressWarnings("rawtypes") + private static final Map, JSONArray> JSON_ARRAY_MAP = new HashMap<>(); + } + + /** + * 枚举的code + * + * @return int + */ + int getCode(); + + /** + * 枚举的描述 + * + * @return 描述 + */ + String getDesc(); + + /** + * 将枚举转换为map + * + * @param t class + * @return mao + */ + static Map getMap(Class t) { + return Cache.CLASS_MAP_MAP.computeIfAbsent(t, aClass -> { + Map map1 = new HashMap<>(20); + try { + Method method = t.getMethod("values"); + BaseEnum[] baseEnums = (BaseEnum[]) method.invoke(null); + for (BaseEnum item : baseEnums) { + map1.put(item.getCode(), item); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + StaticLog.error("enum error", e); + return null; + } + return map1; + }); + } + + /** + * 根据枚举获取枚举对象 + * + * @param t 枚举类型 + * @param code code + * @param 泛型 + * @return 对应的枚举 + */ + static T getEnum(Class t, Integer code) { + return getEnum(t, code, null); + } + + /** + * 根据枚举获取枚举对象 + * + * @param t 枚举类型 + * @param code code + * @param def 默认值 + * @param 泛型 + * @return 对应的枚举 + */ + @SuppressWarnings("unchecked") + static T getEnum(Class t, Integer code, T def) { + if (code == null) { + return def; + } + Map map = getMap(t); + if (map == null) { + return def; + } + return (T) map.get(code); + } + + /** + * 根据 code 获取描述 + * + * @param t class + * @param code code + * @return desc + */ + static String getDescByCode(Class t, Integer code) { + BaseEnum baseEnums = getEnum(t, code); + if (baseEnums == null) { + return null; + } + return baseEnums.getDesc(); + } + + /** + * 获取 json + * + * @param baseEnum 枚举对象 + * @return json + * @throws InvocationTargetException e + * @throws IllegalAccessException e + */ + @SuppressWarnings("rawtypes") + static JSONObject toJSONObject(Enum baseEnum) throws InvocationTargetException, IllegalAccessException { + Class itemCls = baseEnum.getClass(); + Method[] methods = itemCls.getMethods(); + JSONObject jsonObject = new JSONObject(); + for (Method method : methods) { + String name = method.getName(); + if (!name.startsWith("get")) { + continue; + } + name = name.substring(3); + name = name.substring(0, 1).toLowerCase() + name.substring(1); + try { + itemCls.getDeclaredField(name); + } catch (NoSuchFieldException e) { + continue; + } + jsonObject.put(name, method.invoke(baseEnum)); + } + return jsonObject; + } + + /** + * 将枚举转化为数组 + * 包括里面所有属性 + * + * @param cls cls + * @return array + */ + @SuppressWarnings("rawtypes") + static JSONArray toJSONArray(Class> cls) { + if (!cls.isEnum()) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.not_an_enumeration.8244")); + } + JSONArray getJsonArray = Cache.JSON_ARRAY_MAP.computeIfAbsent(cls, aClass -> { + JSONArray jsonArray = new JSONArray(); + try { + Method values = aClass.getMethod("values"); + Object[] objects = (Object[]) values.invoke(null); + for (Object item : objects) { + JSONObject jsonObject = toJSONObject((Enum) item); + jsonArray.add(jsonObject); + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + StaticLog.error("enum error", e); + return null; + } + return jsonArray; + }); + Objects.requireNonNull(getJsonArray); + return (JSONArray) getJsonArray.clone(); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/BaseModel.java b/modules/common/src/main/java/org/dromara/jpom/model/BaseModel.java new file mode 100644 index 0000000000..67d5554547 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/BaseModel.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.keepbx.jpom.model.BaseIdModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 基础实体(带id) + * + * @author bwcx_jzy + * @since 2019/3/14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseModel extends BaseIdModel { + + private String name; + + @Override + public String toString() { + return super.toString(); + } +} 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 new file mode 100644 index 0000000000..386f226f23 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/EnvironmentMapBuilder.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * 环境变量管理 + * + * @author bwcx_jzy + * @since 2023/2/11 + */ +public class EnvironmentMapBuilder { + + private final Map map; + + public EnvironmentMapBuilder(int initialCapacity) { + map = new LinkedHashMap<>(initialCapacity); + } + + public static EnvironmentMapBuilder builder(Map map) { + EnvironmentMapBuilder environmentMapBuilder = new EnvironmentMapBuilder(map.size() + 10); + environmentMapBuilder.put(map); + return environmentMapBuilder; + } + + public EnvironmentMapBuilder put(String name, String value) { + map.put(name, new Item(value, false)); + return this; + } + + public EnvironmentMapBuilder put(Map map) { + if (map != null) { + this.map.putAll(map); + } + return this; + } + + public EnvironmentMapBuilder putStr(Map map) { + Optional.ofNullable(map).ifPresent(stringMap -> { + for (Map.Entry entry : stringMap.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + }); + return this; + } + + public EnvironmentMapBuilder putObjectArray(Object... parametersEnv) { + for (int i = 0; i < parametersEnv.length; i += 2) { + this.put(StrUtil.toString(parametersEnv[i]), StrUtil.toString(parametersEnv[i + 1])); + } + return this; + } + + public Map environment() { + return this.environment(null); + } + + public Map environment(Map appendMap) { + Map map = new LinkedHashMap<>(this.map.size()); + for (Map.Entry entry : this.map.entrySet()) { + Item entryValue = entry.getValue(); + if (entryValue.value == null || entry.getKey() == null) { + // 值不能为 null + continue; + } + map.put(entry.getKey(), entryValue.value); + } + Optional.ofNullable(appendMap).ifPresent(objectMap -> { + for (Map.Entry entry : objectMap.entrySet()) { + Object value = entry.getValue(); + if (value == null || entry.getKey() == null) { + continue; + } + map.put(entry.getKey(), StrUtil.toStringOrNull(value)); + } + }); + return map; + } + + public void eachStr(Consumer consumer) { + this.eachStr(consumer, null); + } + + /** + * 输出环境变量信息 + * + * @param consumer 回调 + * @param appendMap 附加的环境变量 + */ + public void eachStr(Consumer consumer, Map appendMap) { + int allSize = CollUtil.size(this.map) + CollUtil.size(appendMap); + if (allSize <= 0) { + return; + } + consumer.accept("##################################################################################"); + for (Map.Entry entry : map.entrySet()) { + Item entryValue = entry.getValue(); + String value = entryValue.privacy ? "******" : entryValue.value; + consumer.accept(entry.getKey() + "=" + value); + } + Optional.ofNullable(appendMap).ifPresent(objectMap -> { + for (Map.Entry entry : objectMap.entrySet()) { + consumer.accept(entry.getKey() + "=" + StrUtil.toString(entry.getValue())); + } + }); + consumer.accept("##################################################################################"); + } + + /** + * 获取环境变量的执行 + * + * @param key 变量名 + * @return 值 + */ + public String get(String key) { + Item item = map.get(key); + if (item != null) { + return item.value; + } + return null; + } + + /** + * 获取环境变量的执行 + * + * @param key 变量名 + * @return 值 + */ + public boolean getBool(String key, boolean defaultValue) { + String value = this.get(key); + if (value == null) { + return defaultValue; + } + return BooleanUtil.toBoolean(value); + } + + public String toDataJsonStr() { + return JSONObject.toJSONString(map); + } + + public JSONObject toDataJson() { + return JSONObject.from(map); + } + + @AllArgsConstructor + @Data + public static class Item { + /** + * 值 + */ + private String value; + /** + * 隐私 + */ + private boolean privacy; + } +} 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 new file mode 100644 index 0000000000..fcc7da016b --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/RunMode.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +/** + * 项目的运行方式 + * + * @author bwcx_jzy + * @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, + /** + * 软链 + */ + Link, +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/UploadFileModel.java b/modules/common/src/main/java/org/dromara/jpom/model/UploadFileModel.java new file mode 100644 index 0000000000..5d27ef44dd --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/UploadFileModel.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * @author lf + */ +@EqualsAndHashCode(callSuper = true) +@Slf4j +@Data +public class UploadFileModel extends BaseModel { + private long size = 0; + private long completeSize = 0; + private String savePath; + private String version; + + public void save(byte[] data) { + this.completeSize += data.length; + File file = new File(this.getFilePath()); + FileUtil.mkParentDirs(file); + try (FileOutputStream fileOutputStream = new FileOutputStream(file, true)) { + fileOutputStream.write(data); + fileOutputStream.flush(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + public String getFilePath() { + return savePath + StrUtil.SLASH + getName(); + } + + public void remove() { + FileUtil.del(this.getFilePath()); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/WebSocketMessageModel.java b/modules/common/src/main/java/org/dromara/jpom/model/WebSocketMessageModel.java new file mode 100644 index 0000000000..b61514519e --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/WebSocketMessageModel.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.keepbx.jpom.model.BaseJsonModel; +import com.alibaba.fastjson2.JSONObject; + +/** + * websocket发送和接收消息Model + * + * @author lf + */ +public class WebSocketMessageModel extends BaseJsonModel { + + private String command; + private String nodeId; + private Object params; + private Object data; + + public WebSocketMessageModel(String command, String nodeId) { + this.command = command; + this.nodeId = nodeId; + this.data = ""; + } + + public static WebSocketMessageModel getInstance(String message) { + JSONObject commandObj = JSONObject.parseObject(message); + String command = commandObj.getString("command"); + String nodeId = commandObj.getString("nodeId"); + WebSocketMessageModel model = new WebSocketMessageModel(command, nodeId); + model.setParams(commandObj.get("params")); + model.setData(commandObj.get("data")); + + return model; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public Object getData() { + return data; + } + + public WebSocketMessageModel setData(Object data) { + this.data = data; + return this; + } + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public Object getParams() { + return params; + } + + public void setParams(Object params) { + this.params = params; + } + + @Override + public String toString() { + return super.toString(); + } +} 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 new file mode 100644 index 0000000000..edd00d7ea2 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/data/AgentWhitelist.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +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 lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.BaseModel; +import org.dromara.jpom.system.ExtConfigBean; +import org.springframework.util.Assert; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 授权 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Slf4j +@Data +@EqualsAndHashCode(callSuper = true) +public class AgentWhitelist extends BaseModel { + /** + * 项目目录授权、日志文件授权 + */ + private List project; + /** + * 运行编辑的后缀文件 + */ + private List allowEditSuffix; + + /** + * 格式化,判断是否与jpom 数据路径冲突 + * + * @param list list + * @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 = FileUtil.normalize(s); + Assert.state(FileUtil.isAbsolutePath(val), I18nMessageUtil.get("i18n.need_configure_absolute_path.f2e6") + val); + File file = FileUtil.file(val); + File parentFile = file.getParentFile(); + Assert.notNull(parentFile, I18nMessageUtil.get("i18n.cannot_configure_root_path.d86e") + val); + // 判断是否保护jpom 路径 + Assert.state(!StrUtil.startWith(ExtConfigBean.getPath(), val), errorMsg); + // + if (maxLen > 0) { + Assert.state(StrUtil.length(val) <= maxLen, StrUtil.format(I18nMessageUtil.get("i18n.config_path_exceeds_length_limit.f684"), maxLen, val)); + } + return val; + }) + .distinct() + .collect(Collectors.toList()); + } + + /** + * 转换为字符串 + * + * @param jsonArray jsonArray + * @return str + */ + public static String convertToLine(Collection jsonArray) { + return CollUtil.join(jsonArray, StrUtil.CRLF); + } + + /** + * 判断是否在授权列表中 + * + * @param list list + * @param path 对应项 + * @return false 不在列表中 + */ + public static boolean checkPath(List list, String path) { + if (list == null) { + return false; + } + if (StrUtil.isEmpty(path)) { + return false; + } + File file1, file2 = FileUtil.file(path); + for (String item : list) { + file1 = FileUtil.file(item); + if (FileUtil.pathEquals(file1, file2)) { + return true; + } + } + return false; + } + + /** + * 将字符串转为 list + * + * @param value 字符串 + * @param errorMsg 错误消息 + * @return list + */ + public static List parseToList(String value, String errorMsg) { + return parseToList(value, false, errorMsg); + } + + /** + * 将字符串转为 list + * + * @param value 字符串 + * @param required 是否为必填 + * @param errorMsg 错误消息 + * @return list + */ + public static List parseToList(String value, boolean required, String errorMsg) { + if (required) { + Assert.hasLength(value, errorMsg); + } else { + if (StrUtil.isEmpty(value)) { + return null; + } + } + List list = StrSplitter.splitTrim(value, StrUtil.LF, true); + Assert.notEmpty(list, errorMsg); + return list; + } + + /** + * 获取文件可以编辑的 文件编码格式 + * + * @param filename 文件名 + * @return charset 不能编辑情况会抛出异常 + */ + public static Charset checkFileSuffix(List allowEditSuffix, String filename) { + Assert.notEmpty(allowEditSuffix, I18nMessageUtil.get("i18n.editable_suffixes_not_configured.5b41")); + Charset charset = AgentWhitelist.parserFileSuffixMap(allowEditSuffix, filename); + Assert.notNull(charset, I18nMessageUtil.get("i18n.disallowed_file_extension.eb05")); + return charset; + } + + /** + * 静默判断是否可以编辑对应的文件 + * + * @param filename 文件名 + * @return true 可以编辑 + */ + public static boolean checkSilentFileSuffix(List allowEditSuffix, String filename) { + if (CollUtil.isEmpty(allowEditSuffix)) { + return false; + } + Charset charset = AgentWhitelist.parserFileSuffixMap(allowEditSuffix, filename); + return charset != null; + } + + /** + * 根据文件名 和 可以配置列表 获取编码格式 + * + * @param allowEditSuffix 允许编辑的配置 + * @param filename 文件名 + * @return 没有匹配到 返回 null,没有配置编码格式即使用系统默认编码格式 + */ + private static Charset parserFileSuffixMap(List allowEditSuffix, String filename) { + Map map = CollStreamUtil.toMap(allowEditSuffix, s -> { + List split = StrUtil.split(s, StrUtil.AT); + return CollUtil.getFirst(split); + }, s -> { + List split = StrUtil.split(s, StrUtil.AT); + if (split.size() > 1) { + String last = CollUtil.getLast(split); + return CharsetUtil.charset(last); + } else { + return CharsetUtil.defaultCharset(); + } + }); + // 可能配置 所有 + Charset charset = map.get("*"); + if (charset != null) { + return charset; + } + Set> entries = map.entrySet(); + for (Map.Entry entry : entries) { + if (StrUtil.endWithAnyIgnoreCase(filename, entry.getKey(), StrUtil.DOT + entry.getKey())) { + return entry.getValue(); + } + if (ReUtil.isMatch(entry.getKey(), filename)) { + // 满足正则条件 + return entry.getValue(); + } + } + 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/model/system/AgentAutoUser.java b/modules/common/src/main/java/org/dromara/jpom/model/system/AgentAutoUser.java new file mode 100644 index 0000000000..6d016abcfb --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/model/system/AgentAutoUser.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.system; + +import cn.keepbx.jpom.model.BaseJsonModel; + +/** + * agent 端自动生成的密码实体 + * + * @author bwcx_jzy + * @since 2019/4/18 + */ +public class AgentAutoUser extends BaseJsonModel { + + private String agentName; + private String agentPwd; + + public String getAgentName() { + return agentName; + } + + public void setAgentName(String agentName) { + this.agentName = agentName; + } + + public String getAgentPwd() { + return agentPwd; + } + + public void setAgentPwd(String agentPwd) { + this.agentPwd = agentPwd; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/plugin/IDefaultPlugin.java b/modules/common/src/main/java/org/dromara/jpom/plugin/IDefaultPlugin.java new file mode 100644 index 0000000000..b3b774e23f --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/plugin/IDefaultPlugin.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.keepbx.jpom.plugins.IPlugin; +import org.dromara.jpom.system.ExtConfigBean; + +import java.io.InputStream; + +/** + * 插件模块接口 + * + * @author bwcx_jzy + * @since 2021/12/22 + */ +public interface IDefaultPlugin extends IPlugin, AutoCloseable { + + /** + * 获取配置文件流 + * + * @param name 配置文件名称 + * @return InputStream + */ + default InputStream getConfigResourceInputStream(String name) { + return ExtConfigBean.tryGetConfigResourceInputStream(name); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/plugin/IWorkspaceEnvPlugin.java b/modules/common/src/main/java/org/dromara/jpom/plugin/IWorkspaceEnvPlugin.java new file mode 100644 index 0000000000..5b5071db0a --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/plugin/IWorkspaceEnvPlugin.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import org.dromara.jpom.common.Const; + +import java.util.Map; + +/** + * 工作空间环境变量 插件 + * + * @author bwcx_jzy + * @since 2022/8/30 + */ +public interface IWorkspaceEnvPlugin extends IDefaultPlugin { + + String PLUGIN_NAME = "workspace-Env"; + + /** + * 转化 工作空间环境变量 + * + * @param parameter 插件的参数 + * @param key 参数中的key + * @return 转化后的 + * @throws Exception 异常 + */ + default String convertRefEnvValue(Map parameter, String key) throws Exception { + String workspaceId = (String) parameter.get(Const.WORKSPACE_ID_REQ_HEADER); + return this.convertRefEnvValue(workspaceId, (String) parameter.get(key)); + } + + /** + * 转化 工作空间环境变量 + * + * @param workspaceId 工作空间 + * @param value 值 + * @return 如果存在值,则返回环境变量值。不存在则返回原始值 + * @throws Exception 异常 + */ + default String convertRefEnvValue(String workspaceId, String value) throws Exception { + return (String) PluginFactory.getPlugin(PLUGIN_NAME).execute("convert", "workspaceId", workspaceId, "value", value); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/plugin/PluginFactory.java b/modules/common/src/main/java/org/dromara/jpom/plugin/PluginFactory.java new file mode 100644 index 0000000000..85c76397c3 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/plugin/PluginFactory.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.JarClassLoader; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.plugins.IPlugin; +import cn.keepbx.jpom.plugins.PluginConfig; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.ExtConfigBean; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.annotation.Order; +import org.springframework.util.Assert; + +import java.io.File; +import java.net.URLClassLoader; +import java.util.*; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; + +/** + * 插件工厂 + * + * @author bwcx_jzy + * @since 2019/8/13 + */ +@Slf4j +public class PluginFactory implements ApplicationContextInitializer, ApplicationListener { + + // private static final List FEATURE_CALLBACKS = new ArrayList<>(); + private static final Map> PLUGIN_MAP = new SafeConcurrentHashMap<>(); + +// /** +// * 添加回调事件 +// * +// * @param featureCallback 回调 +// */ +// public static void addFeatureCallback(FeatureCallback featureCallback) { +// FEATURE_CALLBACKS.add(featureCallback); +// } +// +// public static List getFeatureCallbacks() { +// return FEATURE_CALLBACKS; +// } + + /** + * 获取插件端 + * + * @param name 插件名 + * @return 插件对象 + */ + public static IPlugin getPlugin(String name) { + List pluginItemWraps = PLUGIN_MAP.get(name); + PluginItemWrap first = CollUtil.getFirst(pluginItemWraps); + Assert.notNull(first, I18nMessageUtil.get("i18n.plugin_not_found.a6e5") + name); + return first.getPlugin(); + } + + /** + * 判断是否包含某个插件 + * + * @param name 插件名 + * @return true 包含 + */ + public static boolean contains(String name) { + return PLUGIN_MAP.containsKey(name); + } + + /** + * 插件数量 + * + * @return 当前加载的插件数量 + */ + public static int size() { + return PLUGIN_MAP.size(); + } + + /** + * 正式环境添加依赖 + */ + private static void init() { + File runPath = JpomManifest.getRunPath().getParentFile(); + File plugin = FileUtil.file(runPath, "plugin"); + if (!plugin.exists() || plugin.isFile()) { + return; + } + // 加载二级插件包 + File[] dirFiles = plugin.listFiles(File::isDirectory); + if (dirFiles != null) { + for (File file : dirFiles) { + File lib = FileUtil.file(file, "lib"); + if (!lib.exists() || lib.isFile()) { + continue; + } + File[] listFiles = lib.listFiles((dir, name) -> StrUtil.endWith(name, FileUtil.JAR_FILE_EXT, true)); + if (listFiles == null || listFiles.length == 0) { + continue; + } + addPlugin(file.getName(), lib); + } + } + // 加载一级独立插件端包 + File[] files = plugin.listFiles(pathname -> FileUtil.isFile(pathname) && FileUtil.JAR_FILE_EXT.equalsIgnoreCase(FileUtil.extName(pathname))); + if (files != null) { + for (File file : files) { + addPlugin(file.getName(), file); + } + } + } + + private static void addPlugin(String pluginName, File file) { + log.info(I18nMessageUtil.get("i18n.load_plugin.1f64"), pluginName); + ClassLoader contextClassLoader = ClassLoaderUtil.getClassLoader(); + JarClassLoader.loadJar((URLClassLoader) contextClassLoader, file); + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + //init(); + // 扫描插件 实现 + Set> classes = ClassUtil.scanPackage("org.dromara.jpom", IPlugin.class::isAssignableFrom); + List pluginItemWraps = classes + .stream() + .filter(aClass -> ClassUtil.isNormalClass(aClass) && aClass.isAnnotationPresent(PluginConfig.class)) + .map(aClass -> new PluginItemWrap((Class) aClass)) + .filter(pluginItemWrap -> { + if (StrUtil.isEmpty(pluginItemWrap.getName())) { + log.warn("plugin config name error:{}", pluginItemWrap.getClassName()); + return false; + } + return true; + }) + .collect(Collectors.toList()); + // + Map> pluginMap = CollStreamUtil.groupByKey(pluginItemWraps, PluginItemWrap::getName); + pluginMap.forEach((key, value) -> { + // 排序 + value.sort((o1, o2) -> Comparator.comparingInt((ToIntFunction) value1 -> { + Order order = value1.getClassName().getAnnotation(Order.class); + if (order == null) { + return 0; + } + return order.value(); + }).compare(o1, o2)); + PLUGIN_MAP.put(key, value); + }); + log.debug("load plugin count:{}", pluginMap.keySet().size()); + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { +// , ApplicationListener + if (event instanceof ContextClosedEvent) { + Collection> values = PLUGIN_MAP.values(); + for (List value : values) { + for (PluginItemWrap pluginItemWrap : value) { + IPlugin plugin = pluginItemWrap.getPlugin(); + IoUtil.close(plugin); + } + } + } else if (event instanceof ApplicationReadyEvent) { + System.setProperty(IPlugin.DATE_PATH_KEY, ExtConfigBean.getPath()); + System.setProperty(IPlugin.JPOM_VERSION_KEY, JpomManifest.getInstance().getVersion()); + } + + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/plugin/PluginItemWrap.java b/modules/common/src/main/java/org/dromara/jpom/plugin/PluginItemWrap.java new file mode 100644 index 0000000000..23b8980e25 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/plugin/PluginItemWrap.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.plugins.IPlugin; +import cn.keepbx.jpom.plugins.PluginConfig; +import lombok.Getter; + +/** + * 插件端对象 + * + * @author bwcx_jzy + * @since 2021/12/24 + */ +@Getter +public class PluginItemWrap { + + /** + * 配置相关 + */ + private final PluginConfig pluginConfig; + + /** + * 插件名 + */ + private final String name; + + /** + * 插件类名 + */ + private final Class className; + + /** + * 插件对象 + */ + private volatile IPlugin plugin; + + public PluginItemWrap(Class className) { + this.className = className; + this.pluginConfig = className.getAnnotation(PluginConfig.class); + this.name = this.pluginConfig.name(); + } + + public IPlugin getPlugin() { + if (plugin == null) { + synchronized (className) { + if (plugin == null) { + // + boolean nativeObject = this.pluginConfig.nativeObject(); + if (nativeObject) { + plugin = ReflectUtil.newInstance(className); + } else { + plugin = SpringUtil.getBean(className); + } + } + } + } + return plugin; + } +} 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 new file mode 100644 index 0000000000..f11882206d --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/script/BaseRunScript.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.script; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +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; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Date; +import java.util.Optional; + +/** + * 脚本模版执行父类 + * + * @author bwcx_jzy + * @since 2022/1/15 + */ +public abstract class BaseRunScript implements AutoCloseable, ILogRecorder { + + /** + * 日志文件 + */ + protected final LogRecorder logRecorder; + protected final File logFile; + protected Process process; + protected InputStream inputStream; + + protected BaseRunScript(File logFile, Charset charset) { + if (logFile == null) { + this.logFile = null; + this.logRecorder = null; + } else { + this.logFile = logFile; + this.logRecorder = LogRecorder.builder().file(logFile).charset(charset).build(); + } + } + + @Override + public String info(String info, Object... vals) { + String msg = logRecorder.info(info, vals); + this.msgCallback(msg); + return msg; + } + + @Override + public String system(String info, Object... vals) { + String msg = logRecorder.system(info, vals); + this.msgCallback(msg); + return msg; + } + + @Override + public String systemError(String info, Object... vals) { + String msg = logRecorder.systemError(info, vals); + this.msgCallback(msg); + return msg; + } + + @Override + public String systemWarning(String info, Object... vals) { + String msg = logRecorder.systemWarning(info, vals); + this.msgCallback(msg); + return msg; + } + + /** + * 输出消息后的回调 + * + * @param msg 消息 + */ + protected abstract void msgCallback(String msg); + + /** + * 结束执行 + * + * @param msg 异常方法 + */ + protected abstract void end(String msg); + + @Override + public void close() { + // windows 中不能正常关闭 + IoUtil.close(inputStream); + CommandUtil.kill(process); + IoUtil.close(logRecorder); + } + + /** + * 清理 脚本文件执行缓存 + */ + public static void clearRunScript() { + String dataPath = JpomApplication.getInstance().getDataPath(); + File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY); + if (!FileUtil.isDirectory(scriptFile)) { + return; + } + File[] files = scriptFile.listFiles(pathname -> { + Date lastModifiedTime = FileUtil.lastModifiedTime(pathname); + DateTime now = DateTime.now(); + long between = DateUtil.between(lastModifiedTime, now, DateUnit.HOUR); + // 文件大于一个小时才能被删除 + return between > 1; + }); + Optional.ofNullable(files).ifPresent(files1 -> { + for (File file : files1) { + try { + FileUtil.del(file); + } catch (Exception ignored) { + } + } + }); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/script/CommandParam.java b/modules/common/src/main/java/org/dromara/jpom/script/CommandParam.java new file mode 100644 index 0000000000..0bf4caa0eb --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/script/CommandParam.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.script; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.model.BaseJsonModel; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONValidator; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.StringUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 脚本参数 + * + * @author bwcx_jzy + * @since 2023/3/13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommandParam extends BaseJsonModel { + /** + * 参数值 + */ + private String value; + /** + * 描述 + */ + private String desc; + + public static String convertToParam(String defArgs) { + JSONValidator.Type type = StringUtil.validatorJson(defArgs); + if (type == null || type == JSONValidator.Type.Value) { + // 旧版本的数据 + List commandParams = CommandParam.convertLineStr(defArgs); + return commandParams == null ? null : JSONObject.toJSONString(commandParams); + } else if (type == JSONValidator.Type.Object) { + return defArgs; + } else { + return defArgs; + } + } + + public static String toCommandLine(String params) { + JSONValidator.Type type = StringUtil.validatorJson(params); + if (type == null || type == JSONValidator.Type.Value) { + // 兼容旧数据 + return params; + } + List paramList = params(params); + return Optional.ofNullable(paramList) + .map(commandParams -> commandParams.stream() + .map(CommandParam::getValue) + .collect(Collectors.joining(StrUtil.SPACE))) + .orElse(StrUtil.EMPTY); + } + + public static List toCommandList(String params) { + JSONValidator.Type type = StringUtil.validatorJson(params); + if (type == null || type == JSONValidator.Type.Value) { + // 兼容旧数据 + return StrUtil.splitTrim(params, StrUtil.SPACE); + } + List paramList = params(params); + return Optional.ofNullable(paramList) + .map(commandParams -> commandParams.stream() + .map(CommandParam::getValue) + .filter(Objects::nonNull) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } + + public static List params(String defParams) { + return StringUtil.jsonConvertArray(defParams, CommandParam.class); + } + + public static String checkStr(String str) { + return Opt.ofBlankAble(str) + .map(s -> { + List params = params(s); + return JSONObject.toJSONString(params); + }).orElse(StrUtil.EMPTY); + } + + public static List convertLineStr(String defArgs) { + List list = StrUtil.splitTrim(defArgs, StrUtil.SPACE); + return Optional.ofNullable(list) + .map(strings -> { + List commandParams1 = new ArrayList<>(strings.size()); + for (int i = 0; i < strings.size(); i++) { + CommandParam commandParam = new CommandParam(); + commandParam.setValue(strings.get(i)); + commandParam.setDesc(I18nMessageUtil.get("i18n.parameter.3d0a") + (i + 1)); + commandParams1.add(commandParam); + } + return commandParams1; + }) + .orElse(null); + } + +} 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 new file mode 100644 index 0000000000..bfe4f300c3 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/socket/ConsoleCommandOp.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import lombok.Getter; + +/** + * 控制台socket 操作枚举 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Getter +public enum ConsoleCommandOp { + /** + * 启动 + */ + start(true), + stop(true), + restart(true), + status, + /** + * 重载 + */ + reload(true), + /** + * 运行日志 + */ + showlog, + /** + * 心跳 + */ + 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/AopLogInterface.java b/modules/common/src/main/java/org/dromara/jpom/system/AopLogInterface.java new file mode 100644 index 0000000000..cac90fa903 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/system/AopLogInterface.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import org.aspectj.lang.ProceedingJoinPoint; + +/** + * 日志接口 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +public interface AopLogInterface { + /** + * 进入前 + * + * @param joinPoint point + */ + void before(ProceedingJoinPoint joinPoint); + + /** + * 执行后 + * + * @param value 结果 + */ + void afterReturning(Object value); +} diff --git a/modules/common/src/main/java/org/dromara/jpom/system/BaseSystemConfig.java b/modules/common/src/main/java/org/dromara/jpom/system/BaseSystemConfig.java new file mode 100644 index 0000000000..a699ecfc39 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/system/BaseSystemConfig.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.cron.CronUtil; +import cn.hutool.system.SystemUtil; +import lombok.Data; +import org.dromara.jpom.common.JpomApplicationEvent; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.RemoteVersion; + +import java.nio.charset.Charset; + +/** + * @author bwcx_jzy + * @since 2022/12/17 + */ +@Data +public abstract class BaseSystemConfig { + + /** + * 是否开启秒级匹配 + */ + private boolean timerMatchSecond = false; + /** + * 允许降级 + */ + private boolean allowedDowngrade = false; + /** + * 旧包文件保留个数 + */ + private int oldJarsCount = 2; + /** + * 远程更新地址 + */ + private String remoteVersionUrl; + /** + * 系统日志编码格式 + */ + private Charset logCharset; + /** + * 控制台编码格式 + */ + private Charset consoleCharset; + /** + * 执行系统主要命名是否填充 sudo(sudo xxx) + * 使用前提需要配置 sudo 免密 + */ + private boolean commandUseSudo = false; + /** + * 系统语言:zh-CN、en-US + */ + private String lang; + + public void setTimerMatchSecond(boolean timerMatchSecond) { + this.timerMatchSecond = timerMatchSecond; + // 开启秒级 + CronUtil.setMatchSecond(timerMatchSecond); + } + + public void setOldJarsCount(int oldJarsCount) { + this.oldJarsCount = oldJarsCount; + JpomApplicationEvent.setOldJarsCount(oldJarsCount); + } + + public void setRemoteVersionUrl(String remoteVersionUrl) { + this.remoteVersionUrl = remoteVersionUrl; + RemoteVersion.setRemoteVersionUrl(remoteVersionUrl); + } + + public void setLang(String lang) { + this.lang = lang; + SystemUtil.set("JPOM_LANG", lang); + } + + /** + * 默认 utf-8 + * + * @return 日志文件编码格式 + */ + public Charset getLogCharset() { + return ObjectUtil.defaultIfNull(logCharset, CharsetUtil.CHARSET_UTF_8); + } + + + public void setConsoleCharset(Charset consoleCharset) { + this.consoleCharset = consoleCharset; + ExtConfigBean.setConsoleLogCharset(consoleCharset); + } + + public void setAllowedDowngrade(boolean allowedDowngrade) { + this.allowedDowngrade = allowedDowngrade; + JpomManifest.setAllowedDowngrade(allowedDowngrade); + } + + public void setCommandUseSudo(boolean commandUseSudo) { + this.commandUseSudo = commandUseSudo; + SystemUtil.set("JPOM_COMMAND_USE_SUDO", String.valueOf(commandUseSudo)); + } +} 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 new file mode 100644 index 0000000000..6488765d29 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/system/ExtConfigBean.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.system.SystemUtil; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.function.Function; + +/** + * 外部资源配置 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Slf4j +public class ExtConfigBean { + /** + * 控制台日志编码 + */ + private static Charset consoleLogCharset; + + public static void setConsoleLogCharset(Charset consoleLogCharset) { + ExtConfigBean.consoleLogCharset = consoleLogCharset; + } + + public static Charset getConsoleLogCharset() { + return ObjectUtil.defaultIfNull(consoleLogCharset, CharsetUtil.systemCharset()); + } + + /** + * 项目运行存储路径 + */ + private static String path; + + public static void setPath(String path) { + ExtConfigBean.path = path; + } + + /** + * 动态获取外部配置文件的 resource + * + * @return File + */ + public static Resource getResource() { + String property = SpringUtil.getApplicationContext().getEnvironment().getProperty(ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY); + Resource configResource = Opt.ofBlankAble(property) + .map(FileSystemResource::new) + .flatMap((Function>) resource -> resource.exists() ? Opt.of(resource) : Opt.empty()) + .orElseGet(() -> { + ClassPathResource classPathResource = new ClassPathResource(Const.FILE_NAME); + return classPathResource.exists() ? classPathResource : new ClassPathResource("/config_default/" + Const.FILE_NAME); + }); + Assert.state(configResource.exists(), I18nMessageUtil.get("i18n.config_file_not_found.fc87")); + return configResource; + } + + /** + * 判断是否存在对应的配置资源 + * + * @param name 名称 + * @return true 存在 + */ + public static boolean existConfigResource(String name) { + File configResourceFile = getConfigResourceFile(name); + if (configResourceFile == null) { + return false; + } + return FileUtil.exist(configResourceFile) && FileUtil.isFile(configResourceFile); + } + + /** + * 获取对应的配置资源 file 对象 + * + * @param name 名称 + * @return true 存在 + */ + public static File getConfigResourceFile(String name) { + FileUtils.checkSlip(name); + File resourceDir = getConfigResourceDir(); + return Opt.ofBlankAble(resourceDir).map(file -> FileUtil.file(file, name)).orElse(null); + } + + /** + * 获取对应的配置资源目录 对象 + */ + public static File getConfigResourceDir() { + String property = SpringUtil.getApplicationContext().getEnvironment().getProperty(ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY); + return Opt.ofBlankAble(property).map(s -> { + File file = FileUtil.file(s); + return FileUtil.getParent(file, 1); + }).orElse(null); + } + + /** + * 动态获取外部配置文件的 resource + * + * @return File + */ + public static InputStream getConfigResourceInputStream(String name) { + InputStream inputStream = tryGetConfigResourceInputStream(name); + Assert.notNull(inputStream, I18nMessageUtil.get("i18n.config_file_not_found.310e") + name); + return inputStream; + } + + /** + * 动态获取外部配置文件的 resource + * + * @return File + */ + public static InputStream tryGetConfigResourceInputStream(String name) { + FileUtils.checkSlip(name); + File configResourceDir = getConfigResourceDir(); + return Opt.ofBlankAble(configResourceDir) + .map((Function) configDir -> { + File file = FileUtil.file(configDir, name); + if (FileUtil.isFile(file)) { + return FileUtil.getInputStream(file); + } + return null; + }) + .orElseGet(() -> { + log.debug(I18nMessageUtil.get("i18n.external_config_not_exist_or_not_configured.f24e"), name); + return tryGetDefaultConfigResourceInputStream(name); + }); + } + + /** + * 动态获取外部配置文件的 resource + * + * @return File + */ + public static InputStream tryGetDefaultConfigResourceInputStream(String name) { + String normalize = FileUtil.normalize("/config_default/" + name); + ClassPathResource classPathResource = new ClassPathResource(normalize); + if (!classPathResource.exists()) { + return null; + } + try { + return classPathResource.getInputStream(); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + } + + /** + * 动态获取外部配置文件的 resource + * + * @return File + */ + public static InputStream getDefaultConfigResourceInputStream(String name) { + InputStream inputStream = tryGetDefaultConfigResourceInputStream(name); + Assert.notNull(inputStream, name + I18nMessageUtil.get("i18n.config_file_not_exist.09dd")); + return inputStream; + } + + /** + * 模糊匹配获取配置文件资源 + * + * @param matchStr 匹配关键词 + * @return 资源 + */ + public static Resource[] getConfigResources(String matchStr) { + PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + File configResourceDir = getConfigResourceDir(); + return Opt.ofBlankAble(configResourceDir) + .map(file -> { + try { + String format = StrUtil.format("{}{}/{}", ResourceUtils.FILE_URL_PREFIX, file.getAbsolutePath(), matchStr); + Resource[] resources = pathMatchingResourcePatternResolver.getResources(format); + if (ArrayUtil.isEmpty(resources)) { + log.warn(I18nMessageUtil.get("i18n.config_file_not_exist_with_message.6a40"), format); + return null; + } + return resources; + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + }) + .orElse(null); + } + + /** + * 模糊匹配获取配置文件资源 + * + * @param matchStr 匹配关键词 + * @return 资源 + */ + public static Resource[] getDefaultConfigResources(String matchStr) { + PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + try { + String format = StrUtil.format("{}/config_default/{}", ResourceUtils.CLASSPATH_URL_PREFIX, matchStr); + return pathMatchingResourcePatternResolver.getResources(format); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + } + + + public static String getPath() { + if (StrUtil.isEmpty(path)) { + if (JpomManifest.getInstance().isDebug()) { + File newFile; + String jpomDevPath = SystemUtil.get("JPOM_DEV_PATH"); + if (StrUtil.isNotEmpty(jpomDevPath)) { + newFile = FileUtil.file(jpomDevPath, JpomApplication.getAppType().name().toLowerCase()); + } else { + // 调试模式 为根路径的 jpom文件 + newFile = FileUtil.file(FileUtil.getUserHomeDir(), "jpom", JpomApplication.getAppType().name().toLowerCase()); + } + path = FileUtil.getAbsolutePath(newFile); + } else { + // 获取当前项目运行路径的父级 + File file = JpomManifest.getRunPath(); + if (!file.exists() && !file.isFile()) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.configure_run_path_property.356c")); + } + File parentFile = file.getParentFile().getParentFile(); + path = FileUtil.getAbsolutePath(parentFile); + } + } + return FileUtil.normalize(path); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/system/JpomRuntimeException.java b/modules/common/src/main/java/org/dromara/jpom/system/JpomRuntimeException.java new file mode 100644 index 0000000000..d553696854 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/system/JpomRuntimeException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import cn.hutool.core.util.StrUtil; + +/** + * Jpom 运行错误 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +public class JpomRuntimeException extends RuntimeException { + + /** + * 程序是否需要关闭 + */ + private Integer exitCode; + + public JpomRuntimeException(String message) { + super(message); + } + + public JpomRuntimeException(String message, Integer exitCode) { + super(message); + this.exitCode = exitCode; + } + + public JpomRuntimeException(String message, Throwable throwable) { + super(StrUtil.format("{} {}", message, StrUtil.emptyToDefault(throwable.getMessage(), StrUtil.EMPTY)), throwable); + } + + public Integer getExitCode() { + return exitCode; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/system/LogbackConfig.java b/modules/common/src/main/java/org/dromara/jpom/system/LogbackConfig.java new file mode 100644 index 0000000000..7048d82cd6 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/system/LogbackConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import ch.qos.logback.core.PropertyDefinerBase; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.system.SystemUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2022/12/7 + */ +public abstract class LogbackConfig extends PropertyDefinerBase { + static String JPOM_LOG = "JPOM_LOG"; + + public static String getPath() { + String jpomLog = SystemUtil.get(JPOM_LOG); + Assert.hasText(jpomLog, I18nMessageUtil.get("i18n.jpom_log_not_configured.3153")); + return jpomLog; + } + + @Override + public String getPropertyValue() { + String jpomLog = SystemUtil.get(JPOM_LOG); + return Opt.ofBlankAble(jpomLog).orElseGet(() -> { + String locationPath = ClassUtil.getLocationPath(this.getClass()); + File file = FileUtil.file(FileUtil.getParent(locationPath, 2), "logs"); + String path = FileUtil.getAbsolutePath(file); + System.out.println(path); + SystemUtil.set(JPOM_LOG, path); + return path; + }); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/system/WebAopLog.java b/modules/common/src/main/java/org/dromara/jpom/system/WebAopLog.java new file mode 100644 index 0000000000..0cdc57ccab --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/system/WebAopLog.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Collection; +import java.util.Optional; + +/** + * 自动记录日志 + * + * @author bwcx_jzy + * @since 2017/5/11 + */ +@Aspect +@Component +@Slf4j +public class WebAopLog { + + private final Collection aopLogInterface; + + public WebAopLog() { + this.aopLogInterface = SpringUtil.getBeansOfType(AopLogInterface.class).values(); + } + + @Pointcut("execution(public * org.dromara.jpom..*.*.controller..*.*(..)) || execution(public * org.dromara.jpom.controller..*.*(..))") + public void webLog() { + // + } + + @Around(value = "webLog()", argNames = "joinPoint") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + // 接收到请求,记录请求内容 + Object proceed; + Object logResult = null; + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + // 可能其他方式执行 + return joinPoint.proceed(); + } + String requestUri = requestAttributes.getRequest().getRequestURI(); + try { + aopLogInterface.forEach(aopLogInterface -> aopLogInterface.before(joinPoint)); + proceed = joinPoint.proceed(); + logResult = proceed; + log.debug("{} {}", requestUri, Optional.ofNullable(proceed).orElse(StrUtil.EMPTY)); + } catch (Throwable e) { + // 不用记录异常日志,全局异常拦截里面会记录,此处不用重复记录 + // log.debug("发生异常 {}", requestUri, e); + logResult = e; + throw e; + } finally { + Object finalLogResult = logResult; + aopLogInterface.forEach(aopLogInterface -> aopLogInterface.afterReturning(finalLogResult)); + } + return proceed; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/util/ApacheExecUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/ApacheExecUtil.java new file mode 100644 index 0000000000..8cd5e55ecc --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/ApacheExecUtil.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.exec.*; +import org.apache.commons.exec.environment.EnvironmentUtils; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.ExtConfigBean; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 24/1/17 017 + */ +@Slf4j +public class ApacheExecUtil { + + private static final ShutdownHookProcessDestroyer shutdownHookProcessDestroyer = new ShutdownHookProcessDestroyer(); + private static final Map processMap = new SafeConcurrentHashMap<>(); + + public static void addProcess(Process process) { + shutdownHookProcessDestroyer.add(process); + } + + /** + * 关闭 Process + * + * @param execId 执行Id + */ + public static void kill(String execId) { + Process process = processMap.remove(execId); + if (process == null) { + return; + } + CommandUtil.kill(process); + } + + /** + * 执行脚本 + * + * @param scriptFile 脚本文件 + * @param baseDir 基础目录 + * @param env 环境变量 + * @param args 参数 + * @param logRecorder 日志记录 + * @return 退出码 + * @throws IOException io + */ + public static int exec(String execId, File scriptFile, File baseDir, Map env, String args, LogRecorder logRecorder) throws IOException { + List build = CommandUtil.build(scriptFile, args); + String join = String.join(StrUtil.SPACE, build); + CommandLine commandLine = CommandLine.parse(join); + log.debug(join); + Charset charset; + try { + charset = ExtConfigBean.getConsoleLogCharset(); + } catch (Exception e) { + // 直接执行,使用默认编码格式 + charset = CharsetUtil.systemCharset(); + } + Map procEnvironment = EnvironmentUtils.getProcEnvironment(); + procEnvironment.putAll(env); + final LogOutputStream logOutputStream = new LogOutputStream(1, charset) { + @Override + protected void processLine(String line, int logLevel) { + logRecorder.info(line); + } + }; + // 重定向stdout和stderr到文件 + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(logOutputStream, logOutputStream); + + // 创建执行器 + DefaultExecutor executor = DefaultExecutor.builder() + .setExecuteStreamHandler(pumpStreamHandler) + .setWorkingDirectory(baseDir) + .get(); + // + executor.setProcessDestroyer(new ProcessDestroyer() { + private int size = 0; + + @Override + public boolean add(Process process) { + processMap.put(execId, process); + size++; + return shutdownHookProcessDestroyer.add(process); + } + + @Override + public boolean remove(Process process) { + processMap.remove(execId); + size--; + return shutdownHookProcessDestroyer.remove(process); + } + + @Override + public int size() { + return size; + } + }); + pumpStreamHandler.stop(); + // 执行,打印退出码 + try { + return executor.execute(commandLine, procEnvironment); + } catch (ExecuteException executeException) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.execution_exception_with_detail.c142"), executeException.getMessage()); + return executeException.getExitValue(); + } + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/util/BaseFileTailWatcher.java b/modules/common/src/main/java/org/dromara/jpom/util/BaseFileTailWatcher.java new file mode 100644 index 0000000000..f9129b6ba2 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/BaseFileTailWatcher.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.Tailer; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * 文件跟随器工具 + * + * @author bwcx_jzy + * @since 2019/7/21 + */ +@Slf4j +public abstract class BaseFileTailWatcher { + + private static int initReadLine = 10; + + public static void setInitReadLine(int initReadLine) { + BaseFileTailWatcher.initReadLine = initReadLine; + } + + protected File logFile; + private final Charset charset; + /** + * 缓存近x条 + */ + private final LimitQueue limitQueue = new LimitQueue<>(initReadLine); + private Tailer tailer; + + /** + * 所有会话 + */ + protected final Set socketSessions = new HashSet<>(); + + public BaseFileTailWatcher(File logFile, Charset charset) { + this.logFile = logFile; + this.charset = charset; + } + + /** + * 发生消息 + * + * @param session 会话 + * @param msg 消息内容 + * @return 是否发送成功 + * @throws IOException io + */ + protected abstract boolean send(T session, String msg) throws IOException; + + /** + * 有新的日志 + * + * @param msg 日志 + */ + private void sendAll(String msg) { + Iterator iterator = socketSessions.iterator(); + while (iterator.hasNext()) { + T socketSession = iterator.next(); + try { + boolean send = this.send(socketSession, msg); + if (!send) { + // + this.errorAutoClose(socketSession); + iterator.remove(); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + this.errorAutoClose(socketSession); + iterator.remove(); + } + } + if (this.socketSessions.isEmpty()) { + this.close(); + } + } + + private void errorAutoClose(T socketSession) { + log.warn(I18nMessageUtil.get("i18n.message_send_failed.4dbe"), this.getId(socketSession)); + IoUtil.close(socketSession); + } + + private String getId(T session) { + Method byName = ReflectUtil.getMethodByName(session.getClass(), "getId"); + Assert.notNull(byName, I18nMessageUtil.get("i18n.no_get_id_method.2a65")); + return ReflectUtil.invoke(session, byName); + } + + /** + * 添加监听会话 + * + * @param name 文件名 + * @param session 会话 + */ + protected boolean add(T session, String name) throws IOException { + String id = getId(session); + Method byName = ReflectUtil.getMethodByName(session.getClass(), "getId"); + boolean match = this.socketSessions.stream() + .anyMatch(t -> { + String itemId = ReflectUtil.invoke(t, byName); + return StrUtil.equals(id, itemId); + }); + if (match) { + return false; + } + if (this.socketSessions.add(session)) { + this.send(session, StrUtil.format(I18nMessageUtil.get("i18n.listen_log_success_currently_sessions_viewing.a74a"), name, this.socketSessions.size())); + // 开发发送头信息 + for (String s : limitQueue) { + this.send(session, s); + } + } + return true; + } + + public void start() { + //this.tailWatcherRun = new FileTailWatcherRun(logFile, this::sendAll); + this.tailer = new Tailer(logFile, charset, line -> { + limitQueue.offer(line); + this.sendAll(line); + }, initReadLine, DateUnit.SECOND.getMillis()); + this.tailer.start(true); + } + + public void restart() { + if (this.tailer != null) { + this.tailer.stop(); + } + this.sendAll("Relisten to the file............"); + this.start(); + } + + /** + * 关闭 + */ + protected void close() { + this.tailer.stop(); + } +} 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 new file mode 100644 index 0000000000..5811189f17 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/CommandUtil.java @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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; +import cn.hutool.core.util.*; +import cn.hutool.system.OsInfo; +import cn.hutool.system.SystemUtil; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.ExtConfigBean; + +import java.io.File; +import java.io.IOException; +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; + +/** + * 命令行工具 + * + * @author bwcx_jzy + * @since 2019/4/15 + */ +@Slf4j +public class CommandUtil { + /** + * 系统命令 + */ + private static final List COMMAND = new ArrayList<>(); + /** + * 文件后缀 + */ + public static final String SUFFIX; + + public static final String SUFFIX_UNIX = "sh"; + public static final String SUFFIX_WINDOWS = "bat"; + /** + * 执行前缀 + */ + private static final String EXECUTE_PREFIX; + /** + * 是否缓存执行结果 + */ + private static final ThreadLocal CACHE_COMMAND_RESULT_TAG = new ThreadLocal<>(); + /** + * 缓存执行结果 + */ + private static final ThreadLocal> CACHE_COMMAND_RESULT = new ThreadLocal<>(); + + static { + OsInfo osInfo = SystemUtil.getOsInfo(); + if (osInfo.isLinux() || osInfo.isMac() || osInfo.isMacOsX() || osInfo.isIrix() || osInfo.isHpUx()) { + //执行linux系统命令 + COMMAND.add("/bin/bash"); + COMMAND.add("-c"); + } else if (osInfo.isWindows()) { + COMMAND.add("cmd"); + COMMAND.add("/c"); + } else { + log.error(I18nMessageUtil.get("i18n.unsupported_system_type_with_placeholder.d5cc"), osInfo.getName()); + } + // + if (osInfo.isWindows()) { + SUFFIX = SUFFIX_WINDOWS; + EXECUTE_PREFIX = StrUtil.EMPTY; + } else { + SUFFIX = SUFFIX_UNIX; + EXECUTE_PREFIX = "bash"; + } + } + + /** + * 填充执行命令的前缀 + * + * @param command 命令 + */ + public static void paddingPrefix(List command) { + if (EXECUTE_PREFIX.isEmpty()) { + return; + } + command.add(0, CommandUtil.EXECUTE_PREFIX); + } + + public static String generateCommand(File file, String args) { + String path = FileUtil.getAbsolutePath(file); + return generateCommand(path, args); + } + + public static String generateCommand(String file, String args) { + return StrUtil.format("{} {} {}", CommandUtil.EXECUTE_PREFIX, file, args); + //String command = CommandUtil.EXECUTE_PREFIX + StrUtil.SPACE + FileUtil.getAbsolutePath(scriptFile) + " restart upgrade"; + } + + /** + * 开启缓存执行结果 + */ + public static void openCache() { + CACHE_COMMAND_RESULT_TAG.set(true); + CACHE_COMMAND_RESULT.set(new ConcurrentHashMap<>(16)); + } + + /** + * 关闭缓存执行结果 + */ + public static void closeCache() { + CACHE_COMMAND_RESULT_TAG.remove(); + CACHE_COMMAND_RESULT.remove(); + } + + /** + * 获取执行命令的 前缀 + * + * @return list + */ + public static List getCommand() { + return ObjectUtil.clone(COMMAND); + } + + /** + * 执行命令 + * + * @param command 命令 + * @return 结果 + */ + public static String execSystemCommand(String command) { + Boolean cache = CACHE_COMMAND_RESULT_TAG.get(); + if (cache != null && cache) { + // 开启缓存 + Map cacheMap = CACHE_COMMAND_RESULT.get(); + return cacheMap.computeIfAbsent(command, key -> execSystemCommand(key, null)); + } + // 直接执行 + return execSystemCommand(command, null); + } + + /** + * 在指定文件夹下执行命令 + * + * @param command 命令 + * @param file 文件夹 + * @return msg + */ + public static String execSystemCommand(String command, File file) { + return execSystemCommand(command, file, null); + } + + /** + * 在指定文件夹下执行命令 + * + * @param command 命令 + * @param file 文件夹 + * @return msg + */ + public static String execSystemCommand(String command, File file, Map map) { + String newCommand = StrUtil.replace(command, StrUtil.CRLF, StrUtil.SPACE); + newCommand = StrUtil.replace(newCommand, StrUtil.LF, StrUtil.SPACE); + String result = "error"; + try { + List commands = getCommand(); + commands.add(newCommand); + String[] cmd = commands.toArray(new String[]{}); + result = exec(cmd, file, map); + } catch (Exception e) { + if (ExceptionUtil.isCausedBy(e, InterruptedException.class)) { + log.warn(I18nMessageUtil.get("i18n.execution_interrupted_message.2597"), command); + result += I18nMessageUtil.get("i18n.execution_interrupted.1bb6"); + } else { + log.error(I18nMessageUtil.get("i18n.command_execution_exception.4ccd"), e); + result += e.getMessage(); + } + } + return result; + } + + /** + * 执行命令 + * + * @param cmd 命令行 + * @return 结果 + * @throws IOException IO + */ + private static String exec(String[] cmd, File file) throws IOException { + return exec(cmd, file, null); + } + + /** + * 执行命令 + * + * @param cmd 命令行 + * @return 结果 + * @throws IOException IO + */ + private static String exec(String[] cmd, File file, Map env) throws IOException { + List resultList = new ArrayList<>(); + boolean isLog; + Charset charset; + try { + charset = ExtConfigBean.getConsoleLogCharset(); + isLog = true; + } catch (Exception e) { + // 不记录日志 + isLog = false; + // 直接执行,使用默认编码格式 + charset = CharsetUtil.systemCharset(); + } + int code = exec(file, env, charset, resultList::add, cmd); + String result = String.join(StrUtil.LF, resultList); + if (isLog) { + log.debug("exec[{}] {} {} {}", code, charset.name(), Arrays.toString(cmd), result); + } + return result; + } + + /** + * 执行命令 + * + * @param cmd 命令行 + * @param file 执行的目录 + * @param lineHandler 命令回调 + * @param env 环境变量 + * @throws IOException IO + */ + public static int exec(File file, Map env, LineHandler lineHandler, String... cmd) throws IOException { + Charset charset; + try { + charset = ExtConfigBean.getConsoleLogCharset(); + } catch (Exception e) { + // 直接执行,使用默认编码格式 + charset = CharsetUtil.systemCharset(); + } + return exec(file, env, charset, lineHandler, cmd); + } + + + /** + * 执行命令 + * + * @param cmd 命令行 + * @param charset 编码格式 + * @param file 执行的目录 + * @param lineHandler 命令回调 + * @param env 环境变量 + * @throws IOException IO + */ + public static int exec(File file, Map env, Charset charset, LineHandler lineHandler, String... cmd) throws IOException { + log.debug("exec file {} {}", ArrayUtil.join(cmd, StrUtil.SPACE), file == null ? StrUtil.EMPTY : file); + ProcessBuilder processBuilder = new ProcessBuilder(cmd); + Map environment = processBuilder.directory(file).environment(); + // 环境变量 + Optional.ofNullable(env).ifPresent(environment::putAll); + Process process = processBuilder.redirectErrorStream(true).start(); + Charset charset2 = ObjectUtil.defaultIfNull(charset, CharsetUtil.defaultCharset()); + InputStream in = null; + try { + in = process.getInputStream(); + IoUtil.readLines(in, charset2, lineHandler); + // 等待结束 + return process.waitFor(); + } catch (InterruptedException e) { + throw Lombok.sneakyThrow(e); + } finally { + IoUtil.close(in); + RuntimeUtil.destroy(process); + } + } + + /** + * 异步执行命令 + * + * @param file 文件夹 + * @param command 命令 + * @throws IOException 异常 + */ + public static void asyncExeLocalCommand(String command, File file) throws Exception { + asyncExeLocalCommand(command, file, null); + } + + /** + * 异步执行命令 + * + * @param file 文件夹 + * @param env 环境变量 + * @param command 命令 + * @throws IOException 异常 + */ + public static void asyncExeLocalCommand(String command, File file, Map env) throws Exception { + asyncExeLocalCommand(command, file, env, false); + } + + /** + * 异步执行命令 + * + * @param file 文件夹 + * @param env 环境变量 + * @param hopeUseSudo 是否期望填充 sudo + * @param command 命令 + * @throws IOException 异常 + */ + public static void asyncExeLocalCommand(String command, File file, Map env, boolean hopeUseSudo) throws Exception { + String newCommand = StrUtil.replace(command, StrUtil.CRLF, StrUtil.SPACE); + newCommand = StrUtil.replace(newCommand, StrUtil.LF, StrUtil.SPACE); + boolean jpomCommandUseSudo = SystemUtil.getBoolean("JPOM_COMMAND_USE_SUDO", false); + if (hopeUseSudo && jpomCommandUseSudo) { + // 期望使用 sudo 并且配置了开启 sudo + newCommand = StrUtil.addPrefixIfNot(newCommand, "sudo "); + } + // + log.debug(newCommand); + List commands = getCommand(); + commands.add(newCommand); + ProcessBuilder pb = new ProcessBuilder(commands); + if (file != null) { + pb.directory(file); + } + Map environment = pb.environment(); + if (env != null) { + environment.putAll(env); + } + pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); + pb.redirectInput(ProcessBuilder.Redirect.INHERIT); + pb.start(); + } + + + /** + * 判断是否包含删除命令 + * + * @param script 命令行 + * @return true 包含 + */ + public static boolean checkContainsDel(String script) { + // 判断删除 + String[] commands = StrUtil.splitToArray(script, StrUtil.LF); + for (String commandItem : commands) { + if (checkContainsDelItem(commandItem)) { + return true; + } + } + return false; + } + + /** + * 执行系统命令 快速删除. + * 执行删除后再检查文件是否存在 + * + * @param file 文件或者文件夹 + * @return true 文件还存在 + */ + public static boolean systemFastDel(File file) { + String path = FileUtil.getAbsolutePath(file); + String command; + if (SystemUtil.getOsInfo().isWindows()) { + // Windows + command = StrUtil.format("rd /s/q \"{}\"", path); + } else { + // Linux MacOS + command = StrUtil.format("rm -rf '{}'", path); + } + CommandUtil.execSystemCommand(command); + // 再次尝试 + boolean del = FileUtil.del(file); + if (!del) { + FileUtil.del(file.toPath()); + } + return FileUtil.exist(file); + } + + private static boolean checkContainsDelItem(String script) { + String[] split = StrUtil.splitToArray(script, StrUtil.SPACE); + if (SystemUtil.getOsInfo().isWindows()) { + for (String s : split) { + if (StrUtil.startWithAny(s, "rd", "del")) { + return true; + } + if (StrUtil.containsAnyIgnoreCase(s, " rd", " del")) { + return true; + } + } + } else { + for (String s : split) { + if (StrUtil.startWithAny(s, "rm", "\\rm")) { + return true; + } + if (StrUtil.containsAnyIgnoreCase(s, " rm", " \\rm", "&rm", "&\\rm")) { + return true; + } + } + } + return false; + } + + public static List build(File scriptFile, String args) { + List command = StrUtil.splitTrim(args, StrUtil.SPACE); + String script = FileUtil.getAbsolutePath(scriptFile); + command.add(0, script); + CommandUtil.paddingPrefix(command); + return command; + } + + /** + * 执行脚本 + * + * @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(); + // + List command = build(scriptFile, args); + log.debug(CollUtil.join(command, StrUtil.SPACE)); + processBuilder.redirectErrorStream(true); + processBuilder.command(command); + Optional.ofNullable(baseDir).ifPresent(processBuilder::directory); + Map environment = processBuilder.environment(); + environment.replaceAll((k, v) -> Optional.ofNullable(v).orElse(StrUtil.EMPTY)); + // 新增逻辑,将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()) { + IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), (LineHandler) line -> consumer.accept(line, process)); + } + 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(I18nMessageUtil.get("i18n.waiting_to_close_process.3634"), 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/CompressionFileUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/CompressionFileUtil.java new file mode 100644 index 0000000000..bd2b6dc6dc --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/CompressionFileUtil.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.extra.compress.CompressUtil; +import cn.hutool.extra.compress.extractor.Extractor; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.compressors.CompressorInputStream; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.Charset; + +/** + * 压缩文件工具 + * + * @author bwcx_jzy + */ +@Slf4j +public class CompressionFileUtil { + + private static final Charset[] CHARSETS = new Charset[]{CharsetUtil.CHARSET_GBK, CharsetUtil.CHARSET_UTF_8}; + + /** + * 解压文件 + * + * @param compressFile 压缩文件 + * @param destDir 解压到的文件夹 + */ + public static void unCompress(File compressFile, File destDir) { + unCompress(compressFile, destDir, 0); + } + + /** + * 解压文件 + * + * @param compressFile 压缩文件 + * @param destDir 解压到的文件夹 + * @param stripComponents 剔除文件夹 + */ + public static void unCompress(File compressFile, File destDir, int stripComponents) { + try { + unCompressTryCharset(compressFile, destDir, stripComponents); + } catch (Exception e) { + try { + unCompressByInputStreamTryCharset(compressFile, destDir, stripComponents); + } catch (Exception e2) { + // + e2.addSuppressed(e); + // + throw Lombok.sneakyThrow(e2); + } + } + } + + private static void unCompressTryCharset(File compressFile, File destDir, int stripComponents) { + for (int i = 0; i < CHARSETS.length; i++) { + Charset charset = CHARSETS[i]; + try (Extractor extractor = CompressUtil.createExtractor(charset, compressFile)) { + extractor.extract(destDir, stripComponents); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.unzip_exception.453e"), compressFile.getName(), charset, e.getMessage()); + if (i == CHARSETS.length - 1) { + // 最后一个 + throw Lombok.sneakyThrow(e); + } + } + } + } + + private static void unCompressByInputStreamTryCharset(File compressFile, File destDir, int stripComponents) { + for (int i = 0; i < CHARSETS.length; i++) { + Charset charset = CHARSETS[i]; + try (FileInputStream fileInputStream = new FileInputStream(compressFile); + CompressorInputStream compressUtilIn = CompressUtil.getIn(null, fileInputStream);) { + try (Extractor extractor = CompressUtil.createExtractor(charset, compressUtilIn)) { + extractor.extract(destDir, stripComponents); + } + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.unzip_exception.92cc"), charset, e.getMessage()); + if (i == CHARSETS.length - 1) { + // 最后一个 + throw Lombok.sneakyThrow(e); + } + } + } + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/util/DirTreeUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/DirTreeUtil.java new file mode 100644 index 0000000000..e47a379f53 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/DirTreeUtil.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.io.FileUtil; +import com.alibaba.fastjson2.JSONObject; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 目录树 + * + * @author bwcx_jzy + * @since 2019/7/21 + */ +public class DirTreeUtil { + + /** + * 获取树的json + * + * @param path 文件名 + * @return jsonArray + */ + public static List getTreeData(String path) { + File file = FileUtil.file(path); + return readTree(file, path); + } + + private static List readTree(File file, String logFile) { + File[] files = file.listFiles(); + if (files == null) { + return null; + } + return Arrays.stream(files) + .sorted((o1, o2) -> CompareUtil.compare(o2.lastModified(), o1.lastModified())) + .map(file1 -> { + JSONObject jsonObject = new JSONObject(); + String path = StringUtil.delStartPath(file1, logFile, true); + jsonObject.put("title", file1.getName()); + jsonObject.put("path", path); + if (file1.isDirectory()) { + List children = readTree(file1, logFile); + jsonObject.put("children", children); + } + return jsonObject; + }) + .collect(Collectors.toList()); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/util/FileUtils.java b/modules/common/src/main/java/org/dromara/jpom/util/FileUtils.java new file mode 100644 index 0000000000..e69c3eb5c3 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/FileUtils.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.io.NioUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.Lombok; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件工具 + * + * @author bwcx_jzy + * @since 2019/4/28 + */ +public class FileUtils { + + private static JSONObject fileToJson(File file, boolean disableScanDir) { + JSONObject jsonObject = new JSONObject(6); + boolean directory = file.isDirectory(); + jsonObject.put("isDirectory", directory); + if (!directory || !disableScanDir) { + long sizeFile = FileUtil.size(file); + jsonObject.put("fileSizeLong", sizeFile); + } + jsonObject.put("filename", file.getName()); + long mTime = file.lastModified(); + jsonObject.put("modifyTimeLong", mTime); + return jsonObject; + } + + /** + * 对文件信息解析排序 + * + * @param files 文件数组 + * @param time 是否安装时间排序 + * @param startPath 开始路径 + * @return 排序后的json + */ + public static List parseInfo(File[] files, boolean time, String startPath, boolean disableScanDir) { + return parseInfo(CollUtil.newArrayList(files), time, startPath, disableScanDir); + } + + /** + * 对文件信息解析排序 + * + * @param files 文件数组 + * @param time 是否安装时间排序 + * @param startPath 开始路径 + * @return 排序后的json + */ + public static List parseInfo(Collection files, boolean time, String startPath, boolean disableScanDir) { + if (files == null) { + return new ArrayList<>(); + } + return files.stream() + .map(file -> { + JSONObject jsonObject = FileUtils.fileToJson(file, disableScanDir); + // + if (startPath != null) { + String levelName = StringUtil.delStartPath(file, startPath, false); + jsonObject.put("levelName", levelName); + } + return jsonObject; + }) + .sorted((jsonObject1, jsonObject2) -> { + if (time) { + return jsonObject2.getLong("modifyTimeLong").compareTo(jsonObject1.getLong("modifyTimeLong")); + } + return jsonObject1.getString("filename").compareTo(jsonObject2.getString("filename")); + }).collect(Collectors.toList()); + } + + /** + * 读取 日志文件 + * + * @param logFile 日志文件 + * @param line 开始行数 + * @return data + */ + public static JSONObject readLogFile(File logFile, int line) { + JSONObject data = new JSONObject(); + // 读取文件 + //int linesInt = Convert.toInt(line, 1); + LimitQueue lines = new LimitQueue<>(1000); + final int[] readCount = {0}; + FileUtil.readLines(logFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line1 -> { + readCount[0]++; + if (readCount[0] < line) { + return; + } + lines.add(line1); + }); + // 下次应该获取的行数 + data.put("line", readCount[0] + 1); + data.put("getLine", line); + data.put("dataLines", lines); + return data; + } + + /** + * 读取环境变量文件 + * + * @param baseFile 基础文件夹 + * @param attachEnv 要读取的文件列表 + * @return map + */ + public static Map readEnvFile(File baseFile, String attachEnv) { + HashMap map = MapUtil.newHashMap(10); + if (StrUtil.isEmpty(attachEnv)) { + return map; + } + List list2 = StrUtil.splitTrim(attachEnv, StrUtil.COMMA); + for (String itemEnv : list2) { + File envFile = FileUtil.file(baseFile, itemEnv); + if (FileUtil.isFile(envFile)) { + List list = FileUtil.readLines(envFile, CharsetUtil.CHARSET_UTF_8); + Map envMap = StringUtil.parseEnvStr(list); + // java.lang.UnsupportedOperationException + map.putAll(envMap); + } + } + return map; + } + + /** + * 判断目录是否有越级问题 + * + * @param dir 目录 + * @param function 异常 + */ + public static void checkSlip(String dir, Function function) { + try { + File tmpDir = FileUtil.getTmpDir(); + FileUtil.checkSlip(tmpDir, FileUtil.file(tmpDir, dir)); + } catch (IllegalArgumentException e) { + throw Lombok.sneakyThrow(function.apply(e)); + } + } + + /** + * 判断目录是否有越级问题 + * + * @param dir 目录 + */ + public static void checkSlip(String dir) { + checkSlip(dir, e -> new IllegalArgumentException(I18nMessageUtil.get("i18n.directory_cannot_skip_levels.179e") + e.getMessage())); + } + + /** + * 文件追加 + * + * @param file 被添加的文件 + * @param channel 需要添加的文件通道 + * @throws IOException io + */ + public static void appendChannel(File file, FileChannel channel) throws IOException { + try (FileInputStream fileInputStream = new FileInputStream(file)) { + try (FileChannel inChannel = fileInputStream.getChannel()) { + ByteBuffer bb = ByteBuffer.allocate(IoUtil.DEFAULT_MIDDLE_BUFFER_SIZE); + while (inChannel.read(bb) != NioUtil.EOF) { + bb.flip(); + channel.write(bb); + bb.clear(); + } + } + } + } + + /** + * 使用当前系统的换行符写文件 + * + * @param context 文件内容 + * @param scriptFile 文件路径 + * @param charset 编码格式 + */ + public static void writeScript(String context, File scriptFile, Charset charset) { + // 替换换行符 + String replace = StrUtil.replace(context, StrUtil.LF, FileUtil.getLineSeparator()); + FileUtil.writeString(replace, scriptFile, charset); + } + + /** + * 安全的方式 move 文件夹内容 + * + * @param src 源文件夹 + * @param target 目标文件夹 + */ + public static void tempMoveContent(File src, File target) { + if (FileUtil.isSub(src, target)) { + // 子目录 + // 将文件内容先复制到临时目录,避免递归出现自己 mv 自己的情况 + File tmpDir = FileUtil.getTmpDir(); + File tempMv = FileUtil.file(tmpDir, "mv", IdUtil.fastSimpleUUID()); + FileUtil.mkdir(tempMv); + FileUtil.moveContent(src, tempMv, true); + // 再将临时目录下的文件移动到目标路径 + FileUtil.mkdir(target); + FileUtil.moveContent(tempMv, target, true); + // + FileUtil.del(tempMv); + // 子目录不需要删除 + } else { + FileUtil.mkdir(target); + FileUtil.moveContent(src, target, true); + // 删除文件夹 + FileUtil.del(src); + } + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/util/JsonFileUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/JsonFileUtil.java new file mode 100644 index 0000000000..fb739825f6 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/JsonFileUtil.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.JpomRuntimeException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * json 文件读写工具 + * + * @author bwcx_jzy + * @since 2017/5/15 + */ +public class JsonFileUtil { + private static final ReentrantReadWriteLock FILE_LOCK = new ReentrantReadWriteLock(); + private final static ReentrantReadWriteLock.ReadLock READ_LOCK = FILE_LOCK.readLock(); + private final static ReentrantReadWriteLock.WriteLock WRITE_LOCK = FILE_LOCK.writeLock(); + + /** + * 读取json 文件,同步 + * + * @param file 路径 + * @return JSON + * @throws FileNotFoundException 文件异常 + */ + public static JSONObject readJson(File file) throws FileNotFoundException { + if (!file.exists()) { + throw new FileNotFoundException(I18nMessageUtil.get("i18n.no_config_file_found.9720") + file.getAbsolutePath()); + } + READ_LOCK.lock(); + // 防止多线程操作文件异常 + try { + String json = FileUtil.readString(file, CharsetUtil.CHARSET_UTF_8); + if (StrUtil.isEmpty(json)) { + return new JSONObject(); + } + try { + return JSONObject.parseObject(json); + } catch (Exception e) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.data_file_content_error.e86f") + file.getAbsolutePath(), e); + } + } finally { + READ_LOCK.unlock(); + } + } + + /** + * 读取json 文件,同步 + * + * @param path 路径 + * @return JSON + * @throws FileNotFoundException 文件异常 + */ + public static JSONObject readJson(String path) throws FileNotFoundException { + File file = new File(path); + return readJson(file); + } + + /** + * 保存json 文件,同步 + * + * @param path 路径 + * @param json 新的json内容 + */ + public static void saveJson(String path, Object json) { + WRITE_LOCK.lock(); + try { + // 输出格式化后的json 字符串 + String newsJson = JSON.toJSONString(json); + FileUtil.writeString(newsJson, path, CharsetUtil.UTF_8); + } finally { + WRITE_LOCK.unlock(); + } + } + + /** + * 保存json 文件,同步 + * + * @param path 路径 + * @param json 新的json内容 + */ + public static void saveJson(File path, Object json) { + saveJson(path.getAbsolutePath(), json); + } + + public static JSONObject arrayToObjById(JSONArray array) { + JSONObject jsonObject = new JSONObject(); + array.forEach(o -> { + JSONObject jsonObject1 = (JSONObject) o; + jsonObject.put(jsonObject1.getString("id"), jsonObject1); + }); + return jsonObject; + } + + public static JSONArray formatToArray(JSONObject jsonObject) { + if (jsonObject == null) { + return new JSONArray(); + } + Set setKey = jsonObject.keySet(); + JSONArray jsonArray = new JSONArray(); + for (String key : setKey) { + jsonArray.add(jsonObject.getJSONObject(key)); + } + return jsonArray; + } +} 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 new file mode 100644 index 0000000000..a26a8180ae --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/JvmUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.text.StrSplitter; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * jvm jmx 工具 + * + * @author bwcx_jzy + * @since 2019/4/13 + */ +public class JvmUtil { + + /** + * 状态服务器 jps 命令执行是否正常 + */ + public static boolean jpsNormal = false; + + /** + * Jps 异常消息回调 + */ + public final static Supplier JPS_ERROR_MSG = () -> { + checkJpsNormal(); + return I18nMessageUtil.get("i18n.server_jps_command_exception.e380"); + }; + + /** + * 支持的标签数组 + */ + private static final String[] JPOM_PID_TAG = new String[]{"DJpom.application", "Jpom.application"}; + + /** + * 检查 jps 命令是否正常 + */ + public static void checkJpsNormal() { + JvmUtil.jpsNormal = JvmUtil.exist(JpomManifest.getInstance().getPid()); + } + + /** + * 获取进程标识 + * + * @param id i + * @param path 路径 + * @return str + */ + public static String getJpomPidTag(String id, String path) { + return String.format("-%s=%s -DJpom.basedir=%s", JPOM_PID_TAG[0], id, path); + } + + + /** + * 获取当前系统运行的java 程序个数 + * + * @return 如果发生异常则返回0 + */ + public static int getJavaVirtualCount() { + String execSystemCommand = CommandUtil.execSystemCommand("jps -l"); + List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); + return Math.max(CollUtil.size(list) - 1, 0); + } + + /** + * 执行 jps 判断是否存在 对应的进程 + * + * @return true 存在 + */ + 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); + return StrUtil.isNotEmpty(pidCommandInfo); + } + + /** + * 工具Jpom运行项目的id 获取进程ID + * + * @param tag 项目id + * @return 进程ID + */ + public static Integer getPidByTag(String tag) { + String execSystemCommand = CommandUtil.execSystemCommand("jps -mv"); + List list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); + 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); + } + + /** + * 判断命令行是否为jpom 标识 + * + * @param commandLine 命令行 + * @param tag 标识 + * @return true + */ + public static boolean checkCommandLineIsJpom(String commandLine, String tag) { + if (StrUtil.isEmpty(commandLine)) { + return false; + } + String[] split = StrUtil.splitToArray(commandLine, StrUtil.SPACE); + String[] tags = Arrays.stream(JPOM_PID_TAG) + .map(s -> String.format("-%s=%s", s, tag)) + .collect(Collectors.toList()) + .toArray(new String[]{}); + for (String item : split) { + if (StrUtil.equalsAnyIgnoreCase(item, tags)) { + return true; + } + } + return false; + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/util/LimitQueue.java b/modules/common/src/main/java/org/dromara/jpom/util/LimitQueue.java new file mode 100644 index 0000000000..2b333213b3 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/LimitQueue.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import java.util.concurrent.ConcurrentLinkedDeque; + +/** + * 定长队列 + * + * @author bwcx_jzy + * @since 2019/3/16 + */ +public class LimitQueue extends ConcurrentLinkedDeque { + private final int limit; + + public LimitQueue(int limit) { + this.limit = Math.max(limit, 0); + } + + @Override + public boolean offerFirst(E s) { + if (full()) { + // 删除最后一个 + pollLast(); + } + return super.offerFirst(s); + } + + @Override + public boolean offerLast(E s) { + if (full()) { + // 删除第一个 + pollFirst(); + } + return super.offerLast(s); + } + + public boolean full() { + return size() > limit; + } +} 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 new file mode 100644 index 0000000000..440c4ec5b0 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/LogRecorder.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +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.Getter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.exception.LogRecorderCloseException; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.Charset; + +/** + * 日志记录 + * + * @author bwcx_jzy + * @since 2022/1/26 + */ +@Slf4j +@Getter +public class LogRecorder extends OutputStream implements ILogRecorder, AutoCloseable { + + private File file; + private PrintWriter writer; + private final Charset charset; + + private LogRecorder(File file, Charset charset) { + if (file == null) { + this.writer = null; + this.file = null; + this.charset = charset; + return; + } + this.file = file; + this.charset = charset; + 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 + ")"; + } + } + + + /** + * 记录错误信息 + * + * @param title 错误描述 + * @param throwable 堆栈信息 + */ + public void error(String title, Throwable throwable) { + log.error(title, throwable); + if (writer == null) { + throw new LogRecorderCloseException(); + } + writer.println(title); + String s = ExceptionUtil.stacktraceToString(throwable); + writer.println(s); + writer.flush(); + } + + /** + * 记录单行日志 + * + * @param info 日志 + */ + public String info(String info, Object... vals) { + if (writer == null) { + throw new LogRecorderCloseException(); + } + String format = StrUtil.format(info, vals); + writer.println(format); + writer.flush(); + return format; + } + + /** + * 记录单行日志 + * + * @param info 日志 + */ + public String system(String info, Object... vals) { + return this.info("[SYSTEM-INFO] " + info, vals); + } + + /** + * 记录单行日志 + * + * @param info 日志 + */ + public String systemError(String info, Object... vals) { + return this.info("[SYSTEM-ERROR] " + info, vals); + } + + /** + * 记录单行日志 + * + * @param info 日志 + */ + public String systemWarning(String info, Object... vals) { + return this.info("[SYSTEM-WARNING] " + info, vals); + } + + /** + * 记录单行日志 (不还行) + * + * @param info 日志 + */ + public void append(String info, Object... vals) { + if (writer == null) { + throw new LogRecorderCloseException(); + } + writer.append(StrUtil.format(info, vals)); + writer.flush(); + + } + + /** + * 获取 文件输出流 + * + * @return Writer + */ + public PrintWriter getPrintWriter() { + return writer; + } + + @Override + public void close() { + IoUtil.close(writer); + this.writer = null; + this.file = null; + } + + public long size() { + Assert.notNull(writer, I18nMessageUtil.get("i18n.log_recorder_not_enabled.5a4e")); + return FileUtil.size(this.file); + } + + @Override + public void write(int b) throws IOException { + if (writer == null) { + throw new LogRecorderCloseException(); + } + writer.write((byte) b); + } +} 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 new file mode 100644 index 0000000000..a65a29ad0a --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/util/StringUtil.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.cron.pattern.CronPattern; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONException; +import com.alibaba.fastjson2.JSONValidator; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.core.env.SimpleCommandLinePropertySource; + +import java.io.File; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * main 方法运行参数工具 + * + * @author bwcx_jzy + * @see SimpleCommandLinePropertySource + * @since 2019/4/7 + */ +public class StringUtil { + + public static final String GENERAL_STR = "^[a-zA-Z0-9_\\-]+$"; + + /** + * 支持的压缩包格式 + */ + public static final String[] PACKAGE_EXT = new String[]{"tar.bz2", "tar.gz", "tar", "bz2", "zip", "gz"}; + + /** + * 转换 文件内容 + * + * @param text 字符串,可能为文件协议地址 + * @param def 默认值 + * @return 如果存在文件 则读取文件内容 + */ + public static String convertFileStr(String text, String def) { + if (StrUtil.startWith(text, URLUtil.FILE_URL_PREFIX)) { + String path = StrUtil.removePrefix(text, URLUtil.FILE_URL_PREFIX); + if (FileUtil.isFile(path)) { + String fileText = FileUtil.readUtf8String(path); + return StrUtil.emptyToDefault(fileText, def); + } + } + return StrUtil.emptyToDefault(text, def); + } + + /** + * 删除文件开始的路径 + * + * @param file 要删除的文件 + * @param baseFile 开始的路径 + * @param inName 是否返回文件名 + * @return /test/a.txt /test/ a.txt + */ + public static String delStartPath(File file, File baseFile, boolean inName) { + FileUtil.checkSlip(baseFile, file); + // + String newWhitePath; + if (inName) { + newWhitePath = FileUtil.getAbsolutePath(file.getAbsolutePath()); + } else { + newWhitePath = FileUtil.getAbsolutePath(file.getParentFile()); + } + String itemAbsPath = FileUtil.getAbsolutePath(baseFile); + itemAbsPath = FileUtil.normalize(itemAbsPath); + newWhitePath = FileUtil.normalize(newWhitePath); + String path = StrUtil.removePrefix(newWhitePath, itemAbsPath); + //newWhitePath.substring(newWhitePath.indexOf(itemAbsPath) + itemAbsPath.length()); + path = FileUtil.normalize(path); + if (path.startsWith(StrUtil.SLASH)) { + path = path.substring(1); + } + return path; + } + + /** + * 删除文件开始的路径 + * + * @param file 要删除的文件 + * @param startPath 开始的路径 + * @param inName 是否返回文件名 + * @return /test/a.txt /test/ a.txt + */ + public static String delStartPath(File file, String startPath, boolean inName) { + return delStartPath(file, FileUtil.file(startPath), inName); + } + +// /** +// * 指定时间的下一个刻度 +// * +// * @return String +// */ +// public static String getNextScaleTime(String time, Long millis) { +// DateTime dateTime = DateUtil.parse(time); +// if (millis == null) { +// millis = 30 * 1000L; +// } +// DateTime newTime = dateTime.offsetNew(DateField.SECOND, (int) (millis / 1000)); +// return DateUtil.formatTime(newTime); +// } + + /** + * json 字符串转 bean,兼容普通json和字符串包裹情况 + * + * @param jsonStr json 字符串 + * @param cls 要转为bean的类 + * @param 泛型 + * @return data + */ + public static T jsonConvert(String jsonStr, Class cls) { + if (StrUtil.isEmpty(jsonStr)) { + return null; + } + if (ClassUtil.isPrimitiveWrapper(cls)) { + return Convert.convert(cls, jsonStr); + } + try { + return JSON.parseObject(jsonStr, cls); + } catch (Exception e) { + return JSON.parseObject(JSON.parse(jsonStr).toString(), cls); + } + } + + /** + * json 字符串转 bean,兼容普通json和字符串包裹情况 + * + * @param jsonStr json 字符串 + * @param cls 要转为bean的类 + * @param 泛型 + * @return data + */ + public static List jsonConvertArray(String jsonStr, Class cls) { + try { + if (StrUtil.isEmpty(jsonStr)) { + return null; + } + return JSON.parseArray(jsonStr, cls); + } catch (Exception e) { + Object parse = JSON.parse(jsonStr); + return JSON.parseArray(parse.toString(), cls); + } + } + + /** + * 根据 map 替换 字符串变量 + * + * @param str 字符串 + * @param evn map + * @return 替换后 + */ + public static String formatStrByMap(String str, Map evn) { + String replace = str; + Set> entries = evn.entrySet(); + for (Map.Entry entry : entries) { + replace = StrUtil.replace(replace, StrUtil.format("${{}}", entry.getKey()), entry.getValue()); + replace = StrUtil.replace(replace, StrUtil.format("${}", entry.getKey()), entry.getValue()); + } + return replace; + } + + /** + * 验证 json 类型 + * + * @param json json 字符串 + * @return type + */ + public static JSONValidator.Type validatorJson(String json) { + try { + JSONValidator from = JSONValidator.from(json); + return Optional.of(from).map(JSONValidator::getType).orElse(null); + } catch (JSONException jsonException) { + return null; + } catch (Exception e) { + // ArrayIndexOutOfBoundsException + return null; + } + } + + /** + * 验证 cron 表达式, demo 账号不能开启 cron + * + * @param cron cron + * @return 原样返回 + */ + public static String checkCron(String cron, Function function) { + String newCron = cron; + if (StrUtil.isNotEmpty(newCron)) { + if (StrUtil.startWith(newCron, "!")) { + // 不用验证 + return newCron; + } + newCron = function.apply(newCron); + try { + new CronPattern(newCron); + } catch (Exception e) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.cron_expression_format_error.6dcd")); + } + } + return ObjectUtil.defaultIfNull(newCron, StrUtil.EMPTY); + } + + /** + * 转换 cron 表达式,格式化 + * + * @param cron cron + * @return 转化后的 + */ + public static String parseCron(String cron) { + if (StrUtil.startWith(cron, "!")) { + // 存在前缀,直接返回空串 + return null; + } + return cron; + } + + public static Map parseEnvStr(String envStr) { + List list = StrUtil.splitTrim(envStr, StrUtil.LF); + return parseEnvStr(list); + } + + public static Map parseEnvStr(List envStrList) { + if (envStrList == null) { + 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()); + return CollStreamUtil.toMap(collect, objects -> objects.get(0), objects -> objects.get(1)); + } + +} diff --git a/modules/common/src/main/resources/LICENSE b/modules/common/src/main/resources/LICENSE new file mode 100644 index 0000000000..59be029215 --- /dev/null +++ b/modules/common/src/main/resources/LICENSE @@ -0,0 +1,127 @@ + 木兰宽松许可证, 第2版 + + 木兰宽松许可证, 第2版 + 2020年1月 http://license.coscl.org.cn/MulanPSL2 + + + 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: + + 0. 定义 + + “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 + + “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 + + “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + + “法人实体”是指提交贡献的机构及其“关联实体”。 + + “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + + 1. 授予版权许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 + + 2. 授予专利许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 + + 3. 无商标许可 + + “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + + 4. 分发限制 + + 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 + + 5. 免责声明与责任限制 + + “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + + 6. 语言 + “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 + + 条款结束 + + 如何将木兰宽松许可证,第2版,应用到您的软件 + + 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + + 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + + 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + + 3, 请将如下声明文本放入每个源文件的头部注释中。 + + Copyright (c) 2019 Code Technology Studio + Jpom is licensed under Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. + + + Mulan Permissive Software License,Version 2 + + Mulan Permissive Software License,Version 2 (Mulan PSL v2) + January 2020 http://license.coscl.org.cn/MulanPSL2 + + Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: + + 0. Definition + + Software means the program and related documents which are licensed under this License and comprise all Contribution(s). + + Contribution means the copyrightable work licensed by a particular Contributor under this License. + + Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. + + Legal Entity means the entity making a Contribution and all its Affiliates. + + Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. + + 1. Grant of Copyright License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. + + 2. Grant of Patent License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. + + 3. No Trademark License + + No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. + + 4. Distribution Restriction + + You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. + + 5. Disclaimer of Warranty and Limitation of Liability + + THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 6. Language + + THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. + + END OF THE TERMS AND CONDITIONS + + How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software + + To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: + + i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; + + ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; + + iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. + + + Copyright (c) 2019 Code Technology Studio + Jpom is licensed under Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. diff --git a/modules/common/src/main/resources/META-INF/spring.factories b/modules/common/src/main/resources/META-INF/spring.factories index 59245fe783..20c306578f 100644 --- a/modules/common/src/main/resources/META-INF/spring.factories +++ b/modules/common/src/main/resources/META-INF/spring.factories @@ -1,3 +1,12 @@ -org.springframework.boot.env.EnvironmentPostProcessor=io.jpom.system.ExtConfigEnvironmentPostProcessor -org.springframework.context.ApplicationListener=\ -io.jpom.JpomLogo \ No newline at end of file +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +org.springframework.context.ApplicationContextInitializer=org.dromara.jpom.plugin.PluginFactory +org.springframework.context.ApplicationListener=org.dromara.jpom.plugin.PluginFactory diff --git a/modules/common/src/main/resources/banner.txt b/modules/common/src/main/resources/banner.txt new file mode 100644 index 0000000000..ec672f8d8f --- /dev/null +++ b/modules/common/src/main/resources/banner.txt @@ -0,0 +1,10 @@ + _ + | | + | |_ __ ___ _ __ ___ + _ | | '_ \ / _ \| '_ ` _ \ + | |__| | |_) | (_) | | | | | | + \____/| .__/ \___/|_| |_| |_| + | | + |_| + ➜ Jpom is licensed under Mulan PSL v2. + ➜ \ (•◡•) / (v2.11.6.6) diff --git a/modules/common/src/main/resources/i18n/messages_en_US.properties b/modules/common/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000000..3b4350d008 --- /dev/null +++ b/modules/common/src/main/resources/i18n/messages_en_US.properties @@ -0,0 +1,1616 @@ +#i18n en +#Sun Jun 16 21:53:31 CST 2024 +i18n.ssh_info_does_not_exist.5ed0=SSH information does not exist +i18n.incompatible_program_versions.5291=The current program version {} The new version of the program is minimum compatible {} and cannot be upgraded directly +i18n.no_projects_configured.e873=No items are configured +i18n.docker_log_thread_ended.8230=Docker log thread ends\: {} {} +i18n.machine_ssh_info.8dbb=Machine SSH information +i18n.machine_info_not_exist.3468=The corresponding machine information does not exist. +i18n.unsupported_method.a1de=Unsupported way +i18n.service_name_in_cluster_required.5446=Please fill in the service name in the cluster +i18n.cluster_manager_node_not_found.1cd0=No cluster management node found +i18n.distribute_id_already_exists.2168=The distribution id already exists +i18n.node_authorized_distribution.c5d7=Node authorization distribution +i18n.operation_user.4c89=operator +i18n.no_uploaded_file.07ef=No file uploaded. +i18n.physical_node_pull.874e={} physical nodes are pulled to {} {}, the current workspace logical node has cached {} {}, updated {} {}, deleted {} caches +i18n.request_type_not_supported_for_decoding.ea2e=The current request type does not support decoding\: {} +i18n.account_login_failed_too_many_times_locked.23b2=The account has failed to log in too many times and has been locked {}, please don't try again. +i18n.no_corresponding_ssh_info.d864=There is no corresponding ssh information. +i18n.no_tag_name.40ff=No tag name +i18n.ssh_console_connection_timeout.8eb3=SSH console connection timed out +i18n.publish_task_execution_failed.b075=Failed to execute publish task +i18n.migration_completed.7a30=Migration completed, cumulative migration of {} pieces of data, time consuming\: {} +i18n.container_command_execution_exception.a14a=Execute container command exception +i18n.plugin_connection_failed.02a1=Plugin connection failed. +i18n.auto_migrate_exist_logs.c169=Automatic migration exists log {} -> {} +i18n.demo_account_password_change_not_supported.91f4=The current account is a demo account, and password change is not supported. +i18n.file_type_not_supported3.f551=Unsupported file types\: {} +i18n.trigger_project_reload_event.a7dc=Trigger project reload event\: {} +i18n.alert_contact_exception_message.1072=Abnormal alarm contact\: +i18n.chunk_upload_exception.87c1=Shard upload exception\: {} {} +i18n.start_waiting_for_data_migration.e76f=Start waiting for data migration +i18n.empty_file_or_folder_for_publish.cae8=The published file or folder is empty and cannot continue publishing +i18n.demo_account_cannot_use_feature.a1a1=The demo account cannot use this function. +i18n.repository_type_required.9414=Please select a warehouse type +i18n.async_resource_expired.2ddc=The asynchronous resource has expired and needs to be actively closed, {} {} +i18n.mark_cannot_be_empty.1927=Tag cannot be empty +i18n.get_success.fb55=Get success +i18n.execution_interrupted_reason.e3d7=Execute interrupt {} process, cause event script interrupt +i18n.forbidden_operation_time_period.86a3=[Prohibited operation] The current time is not within the executable time period, and the time period is limited\: +i18n.host_cannot_be_empty.644a=Parameter error host cannot be empty +i18n.pull_code_exception_with_cleanup.a887=The pull code is abnormal, and the local repository cache content has been actively cleaned up. Please try again manually. +i18n.correct_retention_days_required.d542=Please fill in the correct retention days +i18n.external_config_not_exist_or_not_configured.f24e=External configuration does not exist or is not configured\: {}, use default configuration +i18n.cannot_read_tar_archive_entry.85d7=Cannot read tarArchiveEntry {} +i18n.cluster_not_exist.4098=The corresponding cluster does not exist +i18n.upload_failed_no_matching_project.b219=Upload failed, no corresponding distribution item was found +i18n.no_branches_or_tags_in_repository.76b6=The warehouse does not have any branches or labels. +i18n.download_file_description.10cb=Download file {} {} {} +i18n.delay_build.7d62=Start building after a delay of {} seconds +i18n.default_cluster.38cf=default cluster +i18n.node_and_check_project_failed.ac4b=Node and check item failed +i18n.load_oauth2_config.da42=Load oauth2 configuration :{} {} +i18n.cluster_not_bound_to_group_for_node_monitoring.1586=The current cluster has not been bound to a group, so the asset information of the cluster nodes cannot be monitored +i18n.cannot_execute_error.4c29=Cannot execute\: error +i18n.no_environment_variable.c79f=No corresponding environment variables +i18n.restart_completed.42b8=Restart complete +i18n.node_return_info_exception.0961=The node returns abnormal information. Please check whether the node address is configured correctly or whether the proxy configuration is correct. +i18n.project_associated_with_other_workspaces_message.299c=The current project has been associated with other workspaces, please check and confirm before operating or use lonely data correction +i18n.start_executing_distribution_package.a2cc=Start executing the distribution package, please go to the distribution to check the detailed status. +i18n.clear_old_version_package_failed.021c=Empty the old version and repackage failed. +i18n.node_online_upgrade.f144=Node online upgrade +i18n.execution_ended_with_detail.8f93=End of execution \: {} {} +i18n.ssh_file_upload_exception.5c1c=SSH upload file is abnormal +i18n.authorization_exception.acc0={} Authorization exception {} +i18n.invalid_or_expired_token.bc43=Token error, or has expired +i18n.no_docker_info.d685=There is no corresponding docker. +i18n.invalid_http_proxy_address.1da1=The HTTP proxy address format is incorrect +i18n.execute_dsl_script_exception.0882=Executing DSL script exception\: {} +i18n.package_product.bfbb=packaged products +i18n.cancel_success.285f=Cancellation successful +i18n.node_name_required.ac0f=Node name, cannot be empty +i18n.class_path_and_java_ext_dirs_cp_required.7557=ClassPath, JavaExtDirsCp mode MainClass required +i18n.parameter_validation_failed.f0a1=Parameter validation failed +i18n.asset_cluster_and_node_mismatch.8964=Asset cluster and node mismatch +i18n.script_template.54f2=Script template +i18n.no_project_specified.0076=There is no corresponding project\: +i18n.connection_successful_with_message.5cf2=Connection successful\: +i18n.old_password_incorrect.9cf6=The old password is incorrect\! +i18n.script_template_execution_record.374b=script template execution record +i18n.ssh_authorization_directory_cannot_be_root.8125=SSH authorization directory cannot be root directory +i18n.node_connection_failure_message.aacc=Node connection failed\: +i18n.email_service_not_configured.3180=Unconfigured mailbox service cannot send emails\: {} {} +i18n.node_service_stopped_abnormal_restart.a5c0=The [{}] item {} of the [{}] node has been stopped, and the restart operation is abnormal. +i18n.same_distribution_project_exists.ff41=The same distribution project already exists\: +i18n.unbind_success.1c43=Untied successfully +i18n.project_log.2926=Project log +i18n.private_key_file_not_found.4ad9=The private key file does not exist. +i18n.unsupported_prefix.4f8c=Currently not supported\: +i18n.file_download_failed.7983=File download failed\: +i18n.no_corresponding_distribution_project.6dcd=There is no corresponding distribution item +i18n.build_log.7c0e=build log +i18n.no_current_static_directory_permission.ed70=No current static directory permissions +i18n.select_workspace_error.426e=Workspace selection error +i18n.task_already_exists.f59a=The mission already exists +i18n.start_distribution_with_count.cdc7=To start distributing, you need to distribute {} items +i18n.get_port_error.0698=Get port error +i18n.node_communication_failure.00fb=Node communication fails, please give priority to check whether the upload size configuration is reasonable, or whether the network connection is proxied end point, firewall end point, etc. +i18n.static_file_management.6ac2=static file management +i18n.error_info.99ed=, error message\: +i18n.no_corresponding_docker_asset.6f06=There is no corresponding docker asset. +i18n.build_data_not_exist.0225=The build data does not exist\: {}, the task is automatically discarded\: {} +i18n.project_does_not_exist.3029=Project does not exist +i18n.corresponding_node_does_not_exist.72cb=The current corresponding node does not exist +i18n.upload_success_and_distribute.f446=Upload successful, start distributing\! +i18n.data_table_not_supported_for_grouping.6678=The current data table does not support grouping +i18n.node_sync_project_failed.a2a7=Node synchronization project failed +i18n.get_container_execution_result_interrupted.4a48=The fetch container execution result operation was interrupted\: +i18n.no_ssh_entry_found.d0e1=No corresponding ssh entry found\: {} +i18n.build_runs_on_image_interrupted.00fd=Building the runsOn image was interrupted +i18n.incorrect_parameter_format.9efb=The format of the passed parameter is incorrect +i18n.multiple_certificate_files_found.bee3=Found more than 2 certificate files +i18n.invalid_runs_on_image_name.4b96=runsOn image name is invalid +i18n.dockerfile_path_required.69ac=Please fill in the path to the Dockerfile to be executed +i18n.file_upload_mode_not_configured.b3b2=No profile upload mode +i18n.file_full_path.16cc=File full path\: {} +i18n.container_startup_failure.532e=Container startup failed\: +i18n.environment_variables_not_found.dbd4=No environment variables +i18n.workspace_name_already_exists.0f82=The corresponding workspace name already exists +i18n.alarm_contact_or_webhook_required.6c24=Please select an alarm contact or fill in the webhook. +i18n.upload_exception.cd6c=Upload exception\: +i18n.user_not_exist.5387=There is no corresponding user +i18n.no_corresponding_file_colon.8970=There is no corresponding file\: +i18n.start_executing_upload_post_command.1c1b=Start executing the post-upload command +i18n.data_type_not_configured_correctly.bf16=The data type is not properly configured +i18n.monitoring_logs.2217=monitoring log +i18n.ssh_script_batch_trigger_exception.70e1=SSH script batch triggering exception +i18n.select_cluster.f8c3=Please select a cluster +i18n.no_build_history.39f7=There is no corresponding build history +i18n.docker_does_not_exist.bb41=The corresponding docker does not exist +i18n.trigger_exception.d624=trigger exception +i18n.remote_addresses_not_configured.275e=The allowed remote address has not been configured +i18n.table_error_workspace_data.9021=Table {}[{}] {} error workspace data - > {} +i18n.directory_cannot_skip_levels.179e=Directories cannot be leapfrogged\: +i18n.normal_end.3bfe=end normally +i18n.start_building.1039=Start building +i18n.empty_execution_result.9fe8=The execution result is empty. +i18n.database_exception_due_to_resources.dbf1=The database is abnormal, and the data may be shut down abnormally due to insufficient server resources (memory, hard disk). You need to manually restart the server level to recover,\: +i18n.prepare_rollback.dba6=Start preparing to roll back :{} -> {} +i18n.monitor_node_exception.6ff1=Monitor {} Node Abnormal {} +i18n.add_new_success.431a=Added success\! +i18n.no_parameters_added_with_minus_one.e47d=No parameters were added\: -1 +i18n.welcome_join_session.1c16=Welcome to\: {} Session id\: {} +i18n.backup_old_package_failure_due_to_new_package_absence.b90c=Backup of the old package failed\: {}, because the new package does not exist\: {} +i18n.completed_count_insufficient.02e9=Insufficient number of completions {}/{} +i18n.cluster_info_incomplete_with_code.246b=The cluster information is incomplete and cannot join the cluster\: -1 +i18n.log_recorder_not_enabled.5a4e=Logger is not enabled +i18n.exit_code.ea65=Execute exit code\: {} +i18n.start_executing.f0b9=Start execution\: {} +i18n.authentication_config.964c=authentication configuration +i18n.please_fill_in_information_and_check_validity.771a=Please fill in the information and check if it is legal. +i18n.publish_exception.cf0b=Execute publish exception +i18n.detect_local_docker_exception.ccfc=Detecting local docker exceptions +i18n.run_status_not_configured.e959=No run.status configured +i18n.build_log_recorder_closed.1cc7=The build logger is turned off, it may be manually cancelled to stop the build, process\: {} +i18n.no_data_found.4ffb=No corresponding data +i18n.supported_comparison_operators_message.6d7a=Expression currently only supports \=\= and\! \= comparisons +i18n.get_container_log_failure.915d=Failed to get container log +i18n.content_is_empty.3122=content is empty +i18n.invalid_jar_file.e80a=The jar package file is invalid +i18n.alert_contact_exception.2cec=Abnormal alarm contact +i18n.publish_command_length_limit.66b0=Publish command length is limited to 4000 characters +i18n.unbind.6633=unbind +i18n.asset_monitoring_thread_pool_rejected_task.222e=Asset monitoring thread pool rejected task\: {} +i18n.container_cluster.a5b4=container cluster +i18n.workspace_ssh_already_exists.ccc0=The corresponding workspace already exists in the current SSH. +i18n.non_plaintext_variable_cannot_view.50ca=Non-plaintext variables cannot be viewed +i18n.connect_plugin_failed.e492=Connection plug-in failed :{} {} {} +i18n.restore_project_failed.7f7c=Restore project failed +i18n.no_nodes.17b4=There are no nodes. +i18n.scheduled_backup_log_failure.a0d7=Timed backup log failed +i18n.folder_download_not_supported.c3b7=Download folder is not supported for the time being. +i18n.user_not_exist_trigger_invalid.f375=The corresponding user does not exist, the trigger has expired +i18n.download_remote_file_exception.3ee0=Abnormal download of remote file +i18n.only_tar_files_supported.dcc4=Only supports tar files +i18n.file_management_center.0f5f=Document Management Center +i18n.no_file_found.7d40=Does not correspond to file +i18n.server_captcha_generation_exception.54d0=The current server generates a verification code abnormally, and the verification code is automatically disabled. +i18n.node_script_template_execution_record.704a=Node script template execution record +i18n.response_exception_status_code.cbca={} response exception, status code error\: {} {} +i18n.current_project_associated_with_online_build_and_repository.96c5=The [Online Build] associated [Repository ({}) ] associated with the current [Project] is bound by other {} [Online Build] with different publishing methods, and migration is not supported for the time being +i18n.current_address_no_repository.db31=There is no warehouse at the current address\: +i18n.dsl_not_configured.8a57=The DSL is not configured with run management or {} processes are not configured +i18n.file_write_success.804a=File written successfully +i18n.node_address_required.71f1=Node address cannot be empty +i18n.cannot_edit_corresponding_config_file.8d10=Cannot edit the corresponding configuration file +i18n.auth_directory_cannot_be_under_jpom.bb67=The authorization directory cannot be located in the Jpom directory +i18n.docker_data_repair_not_needed.0fb9=The machine DOCKER table already has {} data, there is no need to repair the machine DOCKER data +i18n.invalid_project_path.04f7=The project path cannot be empty, cannot be a top-level directory, and cannot contain Chinese +i18n.file_type_not_supported2.d497=Unsupported file types\: +i18n.docker_management.e7e5=Docker Management +i18n.no_project_id_found.0f21=No corresponding item id found\: +i18n.agent_response_empty.cc8e=The agent side response content is empty +i18n.no_available_maven_versions.dffe=No available version of maven was found in the maven image library +i18n.static_directory_cannot_contain_relation.1a90=Inclusion relationships cannot exist in a static directory. +i18n.cluster_info_incomplete.84a1=The cluster information is incomplete and cannot be joined +i18n.import_success.b6d1=Import successful +i18n.distribute_thread_exception.9725=distribution thread exception +i18n.need_handle_build_queue_count.c01e=Number of {} build queues to process\: {} +i18n.config_file_not_found.310e=Configuration file not found\: +i18n.correct_verification_code_required.ff0d=Please enter the correct verification code +i18n.no_corresponding_ssh.aa68=There is no corresponding ssh. +i18n.chunk_upload_file_exception.0dc3=Shard upload file is abnormal +i18n.no_execution_id.68dc=No execution ID +i18n.command_execution_record.56d5=command execution log +i18n.task_not_exist.47e9=There is no corresponding task +i18n.configure_dsl_content.42e3=Please configure dsl content +i18n.select_correct_node.1b4e=Please select the correct node +i18n.select_node_error.dc0f=Error selecting node +i18n.docker_certificate_file_missing.ad46=Docker certificate file is missing +i18n.select_correct_post_publish_script.49d2=Please select the correct post-release script +i18n.distribute_management.3a2d=distribution management +i18n.please_fill_in_correct_node_version.8483=Please fill in the correct node version number +i18n.sql_statement_too_long.38d6=SQL statement is too long +i18n.strict_mode_image_build_failure.ecea=Mirror build failed in strict mode, terminate task +i18n.synchronization_failed.091a={} Synchronization failed {} +i18n.security_warning_h2_console.4669=[Security Warning] It is not recommended to open the h2 data web console by default if the database account password is used. +i18n.start_queuing_for_execution.7417=Start queuing for execution +i18n.disallowed_download.06a3=Do not allow downloading files at the current address +i18n.cluster_id_changed.6e49=Cluster ID changes :{} -> {} +i18n.import_success_message.2df3=Import successful (encoding format\: {}), update {} pieces of data, because the node distribution/project copy ignores {} pieces of data +i18n.close_success.8a31=Closed successfully +i18n.no_config_file_found.9720=The corresponding configuration file was not found\: +i18n.server_captcha_available.5570=The current server verification code is available +i18n.download_remote_file.ae84=Download remote file +i18n.backup_old_package.a7fc=Backup old packages\: {} +i18n.certificate_file_missing.c663=Certificate file missing +i18n.incorrect_range_information.a41c=The incoming information from range is incorrect +i18n.user_directory_not_found.cfe3=User directory not found +i18n.main_class_attribute_not_found.93e8=No corresponding MainClass property found in +i18n.file_system_monitoring_exception.d4c0=File system monitoring exception\: +i18n.account_name_nickname_required.b757=Please enter your account nickname +i18n.docker_info_not_found.4f64=No docker information found in the current cluster +i18n.distribution_exception_saving.8285={} {} Distribute exception save +i18n.no_main_class_found.b001=No corresponding MainClass found\: +i18n.save_succeeded.3b10=Save successfully +i18n.select_node_to_modify.6617=Please select the section to modify +i18n.correct_dingtalk_address_required.2b4a=Please enter the correct DingTalk address +i18n.query_folder_sftp_failed.9d35=Failed to query folder SFTP, +i18n.name_field_required.e0c5=Line {} name field cannot be empty +i18n.plugin_end_log_connection_successful.9035=Connection successful\: plugin-side log +i18n.start_building_with_number_and_path.c41c=Start building \#{} Build execution path\: {} +i18n.start_executing_upload_pre_command.fb5c=Start executing the pre-upload command +i18n.delete_container_exception.9ad8=Delete container exception +i18n.query_folder_failed.3f0e=Failed to query folder, +i18n.script_library.aed1=script library +i18n.container_build_product_path_cannot_use_ant_pattern.ddc7=Container-built product paths cannot use the ant pattern +i18n.get_folder_failure.0fda=Failed to get folder +i18n.system_already_initialized.743c=The system has been initialized, please do not repeat the initialization. +i18n.trigger_auto_execute_server_script_exception.8e84=Trigger an autoexecute server script exception +i18n.cleaned_data.0e9d={} cleaned {} data +i18n.no_implemented_feature.af80=This function is not implemented +i18n.no_corresponding_repository.dde9=There is no corresponding warehouse. +i18n.get_docker_cluster_info_failure_with_code.fa77=Failed to get docker cluster information\: -1 +i18n.get_container_execution_result_failure.1828=Failed to get container execution result +i18n.ssh_already_exists_with_message.d284=The corresponding SSH already exists. +i18n.build_finished.7f38=end of build +i18n.not_jpom_install_package.2cca=This file is not a jpom installation package +i18n.republishing.131d=Republishing +i18n.node_id_not_found.2f9e=No node id. +i18n.no_description.c231=\ No description. +i18n.certificate_in_use_by_docker.dd63=The current certificate is associated with docker and cannot be deleted directly +i18n.no_corresponding_build_record.b3b2=There is no corresponding build record. +i18n.auto_delete_data.ca62=\ Automatically delete {} pieces of data in the {} table +i18n.active_clearance_colon.96a6=Active clearance\: +i18n.correct_encoding_format_required.1f7f=Please fill in the correct encoding format. +i18n.notice_script_invocation_error.9002=NoticeScript call error +i18n.go_plugin_version_required.ccf6=Go plugin version cannot be empty +i18n.select_correct_log_path_or_no_auth_configured.9a9b=Please select the correct log path, or authorization has not been configured yet +i18n.service_info_incomplete.968d=The service information is incomplete and cannot be operated. +i18n.session_already_closed.8dcc=The session has been closed and messages cannot be sent\: {} +i18n.modify_or_add_data.e1f0=Modify and add data +i18n.alias_code_validation.8b99=The alias code can only be English and numbers. +i18n.configure_run_path_property.356c=Please configure the run path property [jpom.path] +i18n.node_script_template_log.85e3=Node script template log +i18n.compression_success.80b3=Compression success +i18n.initialize_workspace.bc97=Initialize the {} workspace +i18n.login_name_cannot_contain_chinese_and_special_characters.48a8=Login name cannot contain Chinese characters and cannot contain special characters +i18n.cannot_join_cluster_as_role.01d4=Cannot join cluster as {} role +i18n.not_an_enumeration.8244=Not an enumeration +i18n.script_not_exist.b180=The corresponding script no longer exists +i18n.already_offline.d3b5=It's offline. +i18n.rebuild_success.5938=Rebuild successfully +i18n.global_workspace_variable_edit_in_system_management.58d2=Please go to system management to modify global workspace variables +i18n.project_management.4363=Project Management +i18n.node_service_not_running.ad89=The [{}] project {} of the [{}] node is no longer running +i18n.node_script_template_title.4e74=Node script template +i18n.no_build_record.66a2=No corresponding build record +i18n.node_exception.bca7=Node exception\: +i18n.no_distribution_project.d4d1=No items were distributed +i18n.missing_package_in_root_dir.8bab=There is no% s package in the first-level directory, please go to the file management to upload the% s of the program first. +i18n.exported_project_data.fd1f=Exported project data +i18n.permission_distribution_config_error.e7fb=Permission distribution configuration error\: {} {} +i18n.node_info.2dcf=Node information +i18n.delete_success_with_colon.d44a=Delete successfully\: +i18n.delete_table_data.c813=Delete the data of {} workspaces in table {} with id\: {} +i18n.socket_exception.d836=socket exception +i18n.file_does_not_exist_for_download.8dd6=The file does not exist and cannot be downloaded +i18n.jpom_project_maintenance_system.7f8e=Jpom project operation and maintenance system +i18n.parameter_error_id_cannot_be_empty.86cc=Parameter error id cannot be empty +i18n.prepare_backup.7970=Start preparing backup project files\: {} {} +i18n.login_log.3fb2=login log +i18n.hard_drive_monitoring_error.43e7=Hard disk resource monitoring is abnormal\: +i18n.dockerfile_not_found_in_repository.4168=Dockerfile not found in repository directory\: {} +i18n.login_name_format_incorrect.f789=The login name format is incorrect (letters, numbers, and underscores), and the length must be {} - {} +i18n.no_asset_machine.c77c=There is no corresponding asset machine. +i18n.reconnect_plugin_failure_after_upgrade.73e3=Failed to reconnect the plug-in after the upgrade\: +i18n.current_system_is_windows.91d1=The current system is Windows. +i18n.execute.1a6a=execute +i18n.unknown_error.84d3=Unknown\: +i18n.monitor_docker_exception_detail.e334=Monitor docker [{}] exception {} +i18n.database_username_not_configured.a048=Database username not configured (not resolved to) +i18n.upload_progress_template.ac3f=Upload file progress \:{}/{} {} +i18n.mark_already_exists.0ccc=tag already exists +i18n.docker_not_found.2a2e=\ No docker found. Maybe the docker tag is filled in incorrectly, you need to configure the tag for docker. +i18n.download_remote_file_failed.fcc3=Failed to download remote file\: +i18n.no_file_found.6f1b=No {} file found +i18n.distribute_result.a230=Distribution result\: {} +i18n.multiple_ssh_addresses_found.b3f7=SSH address {} has multiple data, and configuration information using {} SSH will be automatically merged +i18n.no_management_permission.fd25=You do not have the corresponding administrative authority\: -2 +i18n.workspace_env_vars.f7e8=Workspace environment variables +i18n.unsupported_mode.a3d3=Unsupported modes\: +i18n.select_node_and_project.6021=Please select a node and project +i18n.operation_succeeded.3313=Operation successful +i18n.url_length_exceeded.ca1c=The URL length cannot exceed 200. +i18n.waiting_to_close_process.3634=Waiting to close the [Process] process\: {} +i18n.node_id.c90a=Node ID +i18n.search_result_display.d2c3=Search for and display {} rows in {} rows +i18n.reset_super_admin_password_success.50c6=Reset the super administrator account password successfully, the login account is\: {} The new password is\: {} +i18n.current_address_may_not_be_git.41c6=The current address may not be the git repository address. +i18n.no_trigger_type_specified.5628=There is no corresponding trigger type\: +i18n.user_not_exist.4892=User does not exist +i18n.cleanup_history_build_failed_retrying.088e=Failed to clean history bundle, tried again +i18n.no_jpom_type_config_found.aa57=Jpom type configuration not found +i18n.repository_info_does_not_exist.4142=Warehouse information does not exist +i18n.no_run_found_in_steps.a141=No run found in steps, run is used to execute commands +i18n.start_migrating_h2_data_to.f478=Start migrating h2 data to {} +i18n.no_distribution_project_found.90b0=No corresponding distribution item found +i18n.no_branch_name.1879=No branch name +i18n.message_send_failed.4dbe=Message sending failed, this session is automatically removed\: {} +i18n.send_failed.9ca6=Send failed +i18n.select_run_mode.5a5d=Please select the operating mode +i18n.database_backup_label.62d8=database backup +i18n.auto_start_project_failed.c7b5=Autostart project failed\: {} {} +i18n.incorrect_type_passed.d42e=Incoming type error\: {} +i18n.build_trigger_batch_exception.47d5=build trigger batch trigger exception +i18n.id_cannot_be_empty.8f2c=ID cannot be empty +i18n.webhooks_invocation_error.9792=WebHooks call error +i18n.data_modification_time_format_incorrect.7ffe=The data modification time format is incorrect {} {} +i18n.cannot_delete_recent_logs.ee19=Logs related to the past day cannot be deleted (file modification time) +i18n.agent_jar_not_exist.28ac=Agent JAR package does not exist +i18n.parse_certificate_unknown_error.c43c=An unknown error occurred in parsing the certificate. +i18n.node_info_incomplete.3b69=The corresponding node information is incomplete and cannot continue. +i18n.ssh_import_template_csv.14fa=Ssh import template.csv +i18n.build_info.224a=build information +i18n.rename_failed.0c76=Rename failed\: +i18n.execution_command_required.1cf3=Please select the command to execute +i18n.auto_migrate_associated_build_and_repo.0b3f=Auto-migration association build\: {} and repository\: {} +i18n.system_logs.84aa=system log +i18n.machines_ssh_data_fixed.1387=Successfully repaired {} machine SSH data +i18n.docker_label_required.b690=Please fill in the docker tag to execute +i18n.no_matching_permission.09cf=Insufficient permissions not matched +i18n.data_id_does_not_exist.a566=Data ID does not exist +i18n.no_ssh_info.a8ec=No corresponding SSH information +i18n.distribution_with_build_items_message.45f5=There are construction items in the current distribution and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.please_pass_body_parameter.4e5c=Please pass in body parameters +i18n.scanning_in_progress.7444=Currently scanning +i18n.get_decrypt_distribution_failure.4feb=Failed to get decryption distribution +i18n.retention_days.3c7d=, retention days\: {} +i18n.no_log_info.d551=No log information yet +i18n.authorized_cannot_be_reloaded.6ece=Authorized cannot load repeatedly +i18n.private_key_not_found_in_zip.e103=The private key file was not found in the compressed package. +i18n.restore_authorization_data_exception.015a=The recovery authorization data is abnormal or the authorization directory is not selected. +i18n.select_correct_script.ff2d=Please select the correct script +i18n.get_decrypt_implementation_failure.e77a=Failed to get decryption implementation +i18n.no_machine.89ed=There is no corresponding machine. +i18n.modify_success.69be=Modified successfully +i18n.publish_project_package_success.b0ce=Successful release of project package\: {} +i18n.service_info_incomplete_with_code3.8612=The service information is incomplete and cannot be operated\: -3 +i18n.repository_info_error.5b0a=Line {}, the warehouse information is wrong. +i18n.node_not_enabled.10ef={} Node is not enabled +i18n.client_id_not_configured.ab8e=No clientId configured +i18n.build_product_dir_not_empty.ba06=The bundle directory cannot be empty, length 1-200 +i18n.trigger_token_error_or_expired.8976=Triggered token error, or has expired +i18n.auth_info_error.c184=Authorization information error +i18n.download_file_error.5bcd=Abnormal download file\: +i18n.rollback_ended.fb1d=End of execution rollback\: {} +i18n.reload_project_exception.b566=Overloaded item exception +i18n.machine_name_required.e8cf=Please fill in the machine name +i18n.cumulative_filter_files.448d={} Cumulative filtering\: {} files +i18n.project_path_already_exists_as_file.a900=The project path is an existing file +i18n.decrypt_failure.ad83=Decryption failed +i18n.disallowed_file_format.d6e4=Unallowed file formats +i18n.operation_monitoring.0cd5=Operation monitoring +i18n.cron_expression_format_error.6dcd=The cron expression format is incorrect +i18n.id_already_exists.6208=ID already exists +i18n.ssh_info.ebe6=SSH information +i18n.current_status.81c0=\ Currently still available\: +i18n.project_path_no_spaces.263c=The project path cannot contain spaces +i18n.please_fill_in_address_of.9e02=Please fill in the address of% s +i18n.rsa_private_key_file_invalid.5f12=The RSA private key file does not exist or is incorrect +i18n.ssh_node_required.4566=Please select the ssh node +i18n.folder_or_file_exists.c687=The folder or file already exists +i18n.parse_error.da6d=\ Parsing error\: +i18n.no_corresponding_docker.733e=There is no corresponding docker. +i18n.file_published.d1d9=file release +i18n.java_ext_dirs_cp_required.1f4a=JavaExtDirsCp mode javaExtDirsCp required +i18n.correct_url_required.67a3=Please fill in the correct URL. +i18n.pagination_error.6759=There is a problem with the filtered pagination, and no data can be found for the current page number. +i18n.correction_success.38bc=Correction successful +i18n.project_id_in_use.1adb=The current project ID is already occupied by a running program +i18n.import_success_with_count.22b9=Import successful, add {} pieces of data, modify {} pieces of data +i18n.file_upload_exception.a5f6=File upload exception occurred\: {} {} +i18n.please_fill_in_correct_maven_version.468c=Please fill in the correct maven version number. The available versions are as follows\: +i18n.node_has_log_search_projects_cannot_delete.a388=The node has a log search (read) item and cannot +i18n.copy_success.20a4=Copy successful +i18n.selected_node_required.d65a=Select at least one node +i18n.docker_already_exists_in_workspace.a0de=The corresponding docker already exists in the corresponding workspace. +i18n.upload_exception_mismatch.0b25=Upload exception, completed quantity does not match +i18n.multiple_docker_addresses_found.0f82=There are multiple data at the DOCKER address {}, and the configuration information of the DOCKER using {} will be automatically merged +i18n.download_progress.898a=Current progress\: {}, Total file size\: {}, Downloaded\: {} +i18n.data_not_exist.41f9=Corresponding data does not exist +i18n.create_folder_failure.b632=Failed to create folder (folder name may already exist)\: +i18n.gradle_plugin_version_required.b983=Gradle plugin version cannot be empty +i18n.node_account_required.2d90=Please fill in the node account number. +i18n.publish_project_package_failed.9514=Failed to publish project package\: +i18n.online_agent_close_not_supported.d81d=Shutting down the Agent process online is not supported +i18n.port_configuration_check.d888=Is the port number configured correctly, firewall rules, +i18n.local_docker_exists.ec31=The local docker information already exists, don't add it repeatedly\: +i18n.close_client_session_exception.530a=Close client side reply exception +i18n.start_execution.00d7=Start execution +i18n.current_operation_not_supported.3aec=The current operation is not supported. +i18n.repository_info_cannot_be_empty.67d2=Warehouse information cannot be empty +i18n.permission_distribution_config_error_class_not_found.ca67=Privilege distribution configuration error :{} {} class not found +i18n.invalid_email_format.7526=The email format is incorrect. +i18n.load_plugin.1f64=Load\: {} plugin +i18n.host_field_required.5c36=Line {} The host field cannot be empty +i18n.container_build_interrupted.a17b=Container build was interrupted\: +i18n.upload_action.d5a7=upload +i18n.delete_file_failure.041f=Failed to delete file, please check +i18n.no_cluster_info_found.fb40=The corresponding cluster information was not found. +i18n.script_template_log.30cb=Script template log +i18n.file_name_not_configured.39fa=No fileName configured +i18n.ssh_asset_management.3b6c=SSH Asset Management +i18n.operation_succeeded_with_details.c773=Operation successful\: +i18n.no_cache_info.fba1=There is no corresponding cache information. +i18n.save_distribution_project_failed.ceec=Failed to save distribution project +i18n.download_success_and_distribute.ae94=Download successful and start distributing\! +i18n.start_executing_process.9cb8=Start executing the {} process +i18n.select_monitoring_function.c6e4=Please select the function to monitor. +i18n.project_path_auth_required.9e58=Project path authorization cannot be empty +i18n.second_level_directory_cannot_skip_levels.c9fb=The secondary directory cannot be leapfrogged\: +i18n.ssh_error_or_folder_not_configured.c087=SSH error or, this folder is not configured +i18n.static_directory_auth_cannot_be_under_jpom.8879=Static directory authorization cannot be located in the Jpom directory +i18n.file_size_exceeds_limit.8272=Upload file size exceeds limit +i18n.please_fill_in_from.7268=Please fill in from +i18n.unsupported_system_type_with_placeholder.d5cc=Unsupported system types\: {} +i18n.configure_correct_cluster_id.5a78=Please configure the correct cluster Id, [jpom.clusterId] +i18n.ssh_not_exist.2e40=There is no corresponding ssh. +i18n.ssl_connection_failed.e26c=SSL could not connect (please check whether the trusted address of the certificate matches the configured docker host)\: +i18n.soft_link_project_does_not_exist.8ad2=The soft-chained project no longer exists. +i18n.command_name_required.49fa=Please enter a command name +i18n.service_exception.3821=Service exception\: +i18n.jpom_log_not_configured.3153=No configuration JPOM_LOG +i18n.log_reading.a4c8=log reading +i18n.no_corresponding_data_or_permission.1291=No corresponding data or no permission for this data +i18n.no_log_info_or_log_file_error.2c25=No log information or log file errors yet +i18n.disable_monitoring.4615=Disable monitoring +i18n.account_not_bound_to_any_workspace.fd61=The current account is not bound to any workspace, please contact the administrator to deal with it. +i18n.initialize_user_failure.fe27=Failed to initialize user +i18n.container_cli_interrupted.b67f=The container cli is interrupted\: +i18n.project_data_lost.2ae3=Project data loss +i18n.permission_function_not_configured_correctly.84dd=The permission function is not configured correctly {} +i18n.execution_exception_with_detail.c142=Execution exception\: {} +i18n.download_failed.65e2=Download failed +i18n.remote_download_host_cannot_be_empty.cdf5=The host running the remote download cannot be configured as empty +i18n.backup_old_package_failure_due_to_old_package_absence.53aa=Backup of the old package failed\: {}, because the old package does not exist +i18n.no_workspace_found_for_data.ac0f=The workspace corresponding to the data was not found, so the operation cannot be performed. +i18n.container_build_host_config_field_not_exist.6f61=Container build hostConfig field [{}] does not exist +i18n.clear_script_file_failed.f595=Failed to clean script file +i18n.import_save_failure.001a=Failed to import the {} data save\: {} +i18n.not_connected.fa55=It's not connected yet. +i18n.unsupported_request_method.45d7=Unsupported request method +i18n.incomplete_data_not_supported.b5d3=The data is incomplete and the operation is not supported for the time being. +i18n.node_delete_project_failed.534c=Node delete item failed +i18n.wrong_id.ab4d=Wrong ID. +i18n.soft_link_project_does_not_exist.4e4f=The soft chain project no longer exists +i18n.invalid_zip_file.3092=The compressed package uploaded is not a Jpom [{}] package +i18n.publish_command_non_zero_exit_code.ea80=Execute the publish command Exit code is not 0, {} +i18n.project_path_promotion_issue.2250=There is an issue with the promoted directory in the project path +i18n.handle_node_deletion_script_failure_duplicate.821e=Failed to process {} node deletion script {} +i18n.no_matching_process_type.b468=Did not match the appropriate processing type +i18n.unsupported_type.7495=Unsupported types +i18n.submit_task_queue_success.5f5b=Submit task queue successfully, current queue number\: +i18n.login_failed_please_enter_correct_password_and_account.03b2=Login failed, please enter the correct password and account number. Multiple failures will lock the account. +i18n.no_docker_info_no_need_to_fix_machine_data.f45e=No DOCKER information, no need to repair machine DOCKER data +i18n.cluster_info_incomplete_for_operation.ad96=The cluster information is incomplete and cannot be operated +i18n.yml_configuration_content_error.08f8=YML configuration content error +i18n.password_cannot_be_empty.89b5=Password cannot be empty +i18n.ssh_not_exist.08a2=SSH does not exist +i18n.file_merge_exception_details.e9d0=File merge exception {}\:{} -> {} +i18n.configure_correct_self_hosted_gitlab_address.ad50=Please configure the correct self-built gitlab address. +i18n.unknown_jsch_log_level.6a5c=Unknown jsch log level\: {} +i18n.no_oauth2_found.ea74=The corresponding oauth2 was not found. +i18n.no_workspace_info.75ae=No workspace information +i18n.do_not_reinitialize_database.9bb5=Do not repeatedly initialize the database +i18n.update_container_service_exception.2249=Update container service invocation container exception +i18n.file_cleanup_failed.511e=Failed to clean file +i18n.cluster_not_bound_to_group_for_ssh_monitoring.c894=The current cluster has not yet bound packets and cannot monitor SSH asset information +i18n.container_log_fetch_exception.591a=Pull, container log exception +i18n.project_path_conflict.8c6f=Project path and [{}] project conflict\: {} +i18n.cannot_delete_default_workspace.0c06=The default workspace cannot be deleted +i18n.operation_failed_with_details.7280=Operation failed\: +i18n.project_type_not_supported_for_startup.7bd1=The current project type does not support startup +i18n.no_changes_in_repository_code.b1aa=There is no change to the repository code to terminate this build\: {} +i18n.content_cannot_be_empty.9f0d=Content cannot be empty +i18n.remote_repository_does_not_exist.7009=There is no warehouse at the current address remotely\: +i18n.please_fill_in_host.7922=Please fill in the host. +i18n.no_type_specified.8c65=There is no corresponding type\: +i18n.distribute_name_cannot_be_empty.0637=Distribution name cannot be empty +i18n.repository_password_cannot_be_empty.20b3=The warehouse password cannot be empty. +i18n.delete_file_failure_with_full_stop.6c96=Failed to delete file\: +i18n.distribution_not_exist.cf8a=The corresponding distribution does not exist +i18n.user_or_group_bindings_exist_in_workspace.d57b=User (permission group) information is also bound under the current workspace +i18n.user_account.cbf7=user account +i18n.not_logged_in.c89f=You are not currently logged in and cannot manipulate this data +i18n.current_system_is_mac.0139=The current system is\: mac +i18n.docker_certificate_migrated.b3d3=Docker [{}] certificate successfully migrated to certificate management +i18n.certificate_type_not_found.6706=No certificate type +i18n.no_permission_to_execute_command.04d4=No permission to execute relevant commands +i18n.type_error.395f=Type error +i18n.node_service_stopped_successful_restart.603b=The [{}] project {} of the [{}] node has been stopped, and the restart operation has been performed, and the result is successful. +i18n.git_reset_hard_failed_status_code.d818=Git reset --hard failure status code\: +i18n.no_certificate_files_found.ff6d=No certificate files were found +i18n.introducing_script_content.a55b=Introducing script content :{}[{}] +i18n.script_cannot_be_empty.f566=Script cannot be empty +i18n.user_permission_group.52a4=user permission group +i18n.python3_plugin_version_required.a0ce=Python3 plugin version cannot be empty +i18n.update_condition_not_found.0870=No update conditions +i18n.repository_id_cannot_be_empty.a42c=Warehouse ID cannot be empty +i18n.workspace_required.b3bd=Please select a workspace +i18n.get_container_log_interrupted_message.83a5=Getting the container log was interrupted\: +i18n.trigger_auto_execute_ssh_command_template_exception.7451=Trigger an autoexecute SSH command template exception +i18n.start_syncing_to_file_management_center.0a03=Start syncing to File Management Center {} +i18n.delete_success.0007=Deleted successfully +i18n.select_correct_pre_publish_script.d230=Please select the correct pre-release script +i18n.greeting.5ecd=Hello, Jpom. +i18n.ssh_connection_failed.4719=SSH connection failed +i18n.oauth2_redirect_failed.6dcd=Failed to jump to oauth2, {} {} +i18n.data_workspace_mismatch.ae1d=The data workspace and the operation workspace are inconsistent +i18n.process_file_event_exception.e8e6=Handling file event exceptions +i18n.current_docker_cluster_has_no_management_nodes_online.56cd=The current {} docker cluster has no management nodes online +i18n.machine_docker_info.9914=Machine DOCKER INFORMATION +i18n.manual_cancel_distribution.7bf6=Manually cancel distribution +i18n.correct_information_required.5e12=Please enter the correct information +i18n.cluster_status_code_exception.9d89=Cluster status code exception\: {} {} +i18n.restart_operation.5e3a=Perform a restart operation +i18n.no_h2_data_info_for_migration.5799=No h2 data information does not need to be migrated +i18n.publish_success.2fff=Published successfully +i18n.system_cache.c4a8=system cache +i18n.distribution_machine_required.5921=Please select the machine to distribute +i18n.build_call_container_exception.6e04=build call container exception +i18n.process_killed_successfully.a4c3=Successful kill +i18n.build_name_not_empty.4154=Build name cannot be empty +i18n.auto_clear_data_errors.112f=Automatically clear data errors {} {} +i18n.publish_directory_is_empty.79c6=Publish directory is empty +i18n.file_or_directory_not_found.f03e=File does not exist or is a directory\: +i18n.clear_file_cache_failed.5cd1=Failed to clear file cache +i18n.file_downloading_status.c995=File downloading\: +i18n.system_IP_authorization.9c08=System configuration IP authorization +i18n.node_failed.20d5=Node failed\: +i18n.project_has_node_distribution_cannot_delete.41b0=The current project has node distribution and cannot be deleted directly. +i18n.oshi_system_monitoring_exception.5c1c=Oshi system monitoring abnormality +i18n.empty_folder_cannot_be_packed.5a75=The folder is empty and cannot be packed. +i18n.backup_file_not_exist.9628=Backup file does not exist +i18n.auto_migrate_associated_build.a060=Automatic migration association construction\: {} +i18n.node_upgrade_failed.4493=Node upgrade failed\: +i18n.current_docker_offline.a509=Currently {} docker is not online +i18n.protocol_field_value_error.2b41=Line {} protocol field value error (http/http/ssh) +i18n.invalid_file_type.7246=The uploaded file is not a zip. +i18n.database_exception.4894=database exception +i18n.clear_success_message.51f4=Clear successfully +i18n.data_not_supported_for_sorting.5431=The current data does not support sorting +i18n.command_non_zero_exit_code.a6e1=Execute command exit code non-0, {} +i18n.table_info_configuration_error.b050=Table information misconfigured +i18n.close_session_exception_with_detail.85f0=Close session exception\: {} +i18n.auto_delete_expired_build_history_files.723b=Automatically delete outdated build history related files\: {} {} +i18n.build_product_file_sync_failed.0e64=Failed to synchronize the bundle file to the file management center. The current file already exists in the file management center +i18n.manager_node_not_found.df04=No management node found in the current cluster +i18n.system_restart_cancel_download.444e=System restart to cancel the download task +i18n.yml_config_format_error_illegal_field.16ea=The format of the yml configuration content is wrong, please check and re-operate (please check if there are illegal fields)\: +i18n.delete_failure_with_colon_and_full_stop.bc42=Delete failed\: +i18n.product_directory_cannot_skip_levels.3ad4=The product catalog cannot be upgraded\: +i18n.fix_null_workspace_data.4d0b=Fix data {} {} with null workspace +i18n.soft_link_project_department_exists.fa97=The project department of Soft Chain exists +i18n.docker_info.00d2=Docker information +i18n.log_file_does_not_exist.f6c6=Log file does not exist +i18n.system_admin_not_found.6f6c=No system admin found +i18n.no_file_info.db01=There is no corresponding file information. +i18n.record_operation_log_exception.8012=Log operation log exception +i18n.corresponding_file_required.57b3=Please select the corresponding file +i18n.build_command_no_delete.df52=Build commands cannot contain delete commands +i18n.file_merge_error.f32f=After the file is merged, the file may be damaged if it is not completed. +i18n.script_info_not_found.bd8d=No corresponding script information found. +i18n.cannot_modify_own_info.4036=You can't modify your information. +i18n.modify_db_password_must_restart.d08d=To change the database password, you must restart it +i18n.ssh_already_bound_to_other_node.2d4e=The corresponding SSH has been bound by other nodes. +i18n.not_jpom_package.ea3e=This package is not a Jpom [{}] package. +i18n.auth_failed.2765=Authorization failed\: +i18n.node_name.b178=Node name +i18n.current_system_is_linux.e377=The current system is Linux. +i18n.project_id_cannot_contain_spaces.251d=Item IDs cannot contain spaces +i18n.start_publishing_file.a14e=Start publishing files +i18n.checkout_version.a586=Version\:% s checked out +i18n.check_docker_url_exception.4302=Check docker url exception {} +i18n.execution_frequency.d014=Execute once in {} seconds +i18n.parse_jar.a26e=Parse jar +i18n.jpom_verification_code.5b5b=Jpom verification code +i18n.please_fill_in_personal_token.970a=Please fill in your personal token +i18n.parent_table_info_config_error.2f52=The parent table information is misconfigured. +i18n.physical_node_pull_records.99df=The physical node pulls {} execution records and updates {} execution records +i18n.login_success.71fa=Login successful +i18n.clear_temp_file_failed_check_directory.7340=Failed to clear temporary files, please check the directory\: +i18n.incorrect_repository_credentials.f1c8=The warehouse account or password is wrong\: +i18n.request_failed_message.9c71=Request failed\: status\:% s body\:% s headers\:% s +i18n.send_alert_error.cd38=Error sending alarm message +i18n.binding_success.1974=Binding successful +i18n.docker_not_exist.7ed8=There is no corresponding docker. +i18n.monitored_directory_does_not_exist.fa4e=The monitored directory does not exist Ignore Create Listener\: {} +i18n.no_distribution_exists.4425=There is no distribution +i18n.old_version_project_logs_exist_while_running.75ab=There is an old version of the project log, but the project needs to be manually migrated after stopping running\: {} {} +i18n.data_backup.9e26=data backup +i18n.ssh_monitor_execution_error.2d3c={} There is an exception message in the ssh monitoring execution\: {} +i18n.download_file_size.d4de=Download successful file size\: +i18n.user_login_log.0c00=user login log +i18n.cluster_address_check_exception.cd92=The cluster address filled in is abnormal. Please confirm that the cluster address is the correct server level address. +i18n.no_shard_id_info.30f8=No sharding id information +i18n.socket_error.18c1=Socket error +i18n.synchronization_node_failure.8a2c=Synchronization node {} failed {} +i18n.build_thread_pool_rejected_task.3bad=Build thread pool rejected unknown task\: {} +i18n.need_configure_absolute_path.f2e6=You need to configure the absolute path\: +i18n.auto_start_timed_task_message.9637={} Timed task has been automatically started\: {} +i18n.need_execute_callbacks.b708={} callbacks need to be executed +i18n.protocol_type_not_supported2.e519=Unsupported protocol types +i18n.parameter_error_id_error.58ce=Parameter error id error +i18n.unsupported_type_with_placeholder.71a2=Unsupported types\: {} +i18n.publish_method_format.4622=How to publish\: {} +i18n.ssh_bound_to_node_message.7b64=The current ssh is bound by the node and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.send_alert_notification_exception.6788=Send alarm notification exception +i18n.container_build_host_config_conversion_failure.27aa=Container build hostConfig parameter {} conversion failed\: {} +i18n.build_task_count_and_queue_count.f0b6=Number of tasks currently under construction\: {}, Number of tasks in queue\: {} {} +i18n.node_cache.d68c=Node cache +i18n.cluster_node_not_in_system.0645=The node corresponding to the current cluster cannot exit the cluster if it is not in this system. +i18n.file_search_failed.231b=File search failed +i18n.execution_ended_with_duration.a59b=Execute the end {} process, time consuming\: {} +i18n.ssh_monitor_info_result.a660={} ssh monitoring information Result\: {} {} +i18n.certificate_serial_number_not_found.c8d1=No certificate serial number +i18n.configure_table_name.f6fd=Please configure the table Name +i18n.project_has_monitoring_items_cannot_migrate.c7f6=The current project has monitoring items and cannot be migrated directly. +i18n.date_format_error.3d1c=Date format error\: +i18n.unexpected_exception_with_details.247d=Exception occurred\: {} {} +i18n.static_directory_not_configured.acbc=Static directory not configured +i18n.exported_repo_data.bac5=Exported, warehouse information, data +i18n.current_cluster_is_bound_to_workspace_cannot_be_deleted_directly.94c2=The current cluster is still bound by the workspace and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.repository_import_template.5e2d=Warehouse information import template.csv +i18n.execution_node_required.d747=Please select an execution node +i18n.program_error_null_pointer.12e1=Program error, null pointer +i18n.delete_build_cache.c7f3=Delete build cache +i18n.system_interruption.e37c=System interrupt exception +i18n.stop_running.1d4e=stop running +i18n.selected_weekday_incorrect.4cd4=The selected day of the week is incorrect +i18n.node_has_monitoring_items_cannot_delete.0304=There are monitoring items in this node and cannot +i18n.existing_project_cannot_be_soft_link.aa5a=Existing projects cannot be modified to soft chain projects +i18n.git_fetch_failed_status_code.5187=Git fetch failed status code\: +i18n.ssh_folder_creation_exception.6ed2=SSH create folder exception +i18n.ssh_batch_command_execution_exception.029a=SSH batch execution command exception +i18n.content_type_not_supported.81a9=unsupported contentType +i18n.cloud_server_network_issues.a865=Troubleshooting and positioning of network-related issues such as security group configuration of Cloud as a Service. +i18n.configure_correct_redirect_url.058e=Please configure the correct redirect URL. +i18n.no_cache_info_with_minus_one.52f2=No corresponding cache information\: -1 +i18n.no_node_entry_found.b1ef=No corresponding node item found\: {} +i18n.execution_interrupted_message.2597=Execution was interrupted\: {} +i18n.build_not_exist.c2ac=There is no corresponding construction +i18n.demo_account_not_support_reset_password.a595=The demo account does not support resetting passwords. +i18n.build_command_execution.a55c=Execute the build command +i18n.no_docker_info_found.6d38=No corresponding docker information was found. +i18n.update_docker_machine_id_failed.063d=Updating DOCKER table machine id failed\: +i18n.build_status_abnormal.8ca1=The build status is abnormal or cancelled +i18n.node_connection_failure.896d=Node connection failed, please check if the node is online. +i18n.login_name_already_exists.2511=Login name already exists +i18n.port_error.312e=port error +i18n.account_disabled.9361=The account has been disabled and cannot be used. +i18n.file_does_not_exist_anymore.2fab=The file no longer exists +i18n.system_error.9417=System error\! +i18n.file_type_not_supported_with_placeholder.db22=Unsupported file types\: {} +i18n.suffix_cannot_be_empty.ec72=The file suffix allowed for editing cannot be empty +i18n.read_system_parameter_exception.ee72=Abnormal reading system parameters +i18n.check_docker_cert_exception.8042=Check docker certificate for exception {} +i18n.ssh_terminal.ec50=SSH end point +i18n.batch_trigger_project_exception.3c28=project batch trigger exception +i18n.data_name_label.5a14=data name +i18n.cannot_create_config_file_in_environment.55bb=The current environment cannot create a configuration file +i18n.ssh_file_manager.1482=SSH file management +i18n.i18n_node_already_exists.632d=The corresponding node already exists. +i18n.address_not_configured.f2eb=Address not configured +i18n.migration_success.b20d={} migrated successfully {} pieces of data +i18n.no_corresponding_file.97b5=No corresponding file +i18n.please_fill_in_name.52f3=Please fill in the name. +i18n.unable_to_access_node_network.4e09=Unable to access the node network (unknown name or service), please check whether the hostname or DNS is available. +i18n.ssh_already_exists_in_workspace.569e=The corresponding workspace already has the ssh\: +i18n.update_operation_log_failed.d348=Update operation log failed +i18n.cron_expression_incorrect.b41a=The cron expression is incorrect, +i18n.script_template_id_required.f339=Please fill in the script template id. +i18n.start_rolling_back.f020=Start rollback\: {} +i18n.execution_exception.b0d5=execution exception +i18n.corresponding_function.5bb5=Corresponding function 【{}-{}】 +i18n.plugin_parameter_incorrect.a355=The plugin-side usage parameters are incorrect. +i18n.operation_failed.3d94=Operation failed +i18n.start_publishing.c0b9=Launching now. +i18n.monitor_info.f299=monitoring information +i18n.node_already_exists.28ea=The corresponding node already exists. +i18n.request_needs_decoding.d4d7=The current request needs to be decoded\: {} +i18n.no_parameters_added.1721=No parameters were added +i18n.repo_already_exists.38a3=The corresponding warehouse information already exists. +i18n.publish_script_exit_code.0f69=The exit code for executing the release script is\: {} +i18n.method_not_supported.90c4=The current method is not supported and cannot be used for the time being +i18n.general_execution_exception.62e9=Execution exception\: +i18n.distribute_info_error_no_projects.e75f=Distribution information error, no items +i18n.unknown_prune_type.0931=pruneType unknown +i18n.error_sql.15ff=Error sql\: {} +i18n.verification_method_not_configured.7358={} Authentication method not configured\: {} +i18n.local_git_certificate_not_supported.b395=Local git-specified certificate pull code is temporarily not supported +i18n.unsupported_method_with_colon.eae8=Unsupported methods\: +i18n.current_docker_has_no_cluster_info.0b52=The current docker has no cluster information. +i18n.cannot_delete_self.fec9=Can't delete yourself +i18n.operation_monitoring_error.8036=execution action monitoring error +i18n.alias_or_token_error.d5c6=The alias or token is wrong, or has expired +i18n.delete_old_package.ca95=Delete the old package\: {} +i18n.table_info_configuration_error_message.6452=The table information is misconfigured. +i18n.login_name_already_taken.5b46=The current login name is already occupied by the system +i18n.auto_backup_h2_database.2ed0=Automatically backup the h2 database file, the backup file is located at\: {} +i18n.verification_code_disabled.349b=Captcha disabled +i18n.unsupported_type_with_colon2.7de2=Unsupported types\: +i18n.node_service_stopped_failed_restart.4307=The [{}] project {} of the [{}] node has been stopped, and the restart operation has been performed, and the result fails. +i18n.associated_workspace.885b=Affiliated workspace +i18n.auto_clear_machine_node_stats_logs.5279=Automatically clean up {} machine node statistical logs +i18n.unsupported_mode.501d=Unsupported mode +i18n.missing_script_message.af89=No corresponding script found +i18n.start_migrating.20d6=Start migration {} {} +i18n.incompatible_database_version.8f7b=Database versions are not compatible, and cross-version upgrades need to be handled. +i18n.admin_account_required.31e0=The number of system admin accounts in the system must be more than one +i18n.process_does_not_exist.4e39=Process does not exist +i18n.no_ssh_commands_to_execute_after_publish.89ba=There is no need to execute the post-publication ssh command +i18n.correct_remote_address_required.0ce1=Please enter the correct remote address +i18n.comparison_data_not_found.413e=No data to compare +i18n.invalid_remote_address_format.7f32=The configured remote address is not standardized, please fill in again\: +i18n.docker_console_connection_timeout.b2c7=Docker console connection timed out +i18n.send_success.9db9=Sent successfully +i18n.start_distribution.bce5=Start distribution +i18n.heartbeat_message_forwarding_failed.89cc=Heartbeat message forwarding failed {} {} +i18n.asset_machine_node_statistics.4a03=Asset Machine Node Statistics +i18n.repository_does_not_exist.3cdb=The warehouse does not exist. +i18n.select_correct_build_method.84c4=Please choose the correct build method +i18n.project_file_manager.c8cb=Project file management +i18n.no_ssh_info_no_need_to_fix_machine_data.0946=No SSH information, no need to repair machine SSH data +i18n.data_id_already_exists.28b6=The data ID already exists :{} \: {} +i18n.event_script_interrupted.8c79=Event script interrupt\: +i18n.configure_user_notification.250d=Please configure user notifications +i18n.operation_status_code.8231=operation status code +i18n.agent_jar_damaged.74a8=Agent JAR damaged, please re-upload, +i18n.read_error.7fa5=Read error +i18n.network_resource_monitoring_error.4ede=Network interface card resource monitoring abnormality\: +i18n.select_workspace_to_modify.ac87=Please select the workspace you want to modify +i18n.decode_failure.822e=Decoding failed +i18n.restart_failed.f92a=Restart failed. +i18n.login_name_already_taken_message.b01f=The current login name has been occupied by the system +i18n.forbidden_operation_time.d83d=[Prohibited operation] Prohibited execution during the current time period +i18n.log_file_not_found.7f2e=No log file\: +i18n.protocol_type_not_supported.7a66=Protocol type not supported +i18n.execution_result_file_not_found_in_container.cf18=The execution result file was not found in the container\: {} +i18n.execution_exception_with_flow.6d4b=Execute exception [{}] flow\: {} +i18n.trigger_success.f9d1=Triggered successfully +i18n.start_pulling.57ab=Start pulling +i18n.publish_task_execution_exception.c296=Execute publish task exception +i18n.no_node_specified.fa3d=There is no corresponding node\: +i18n.no_corresponding_command.165e=No response order +i18n.exclusion_success.7d46=cull success +i18n.oauth2_not_enabled.c8b7=This {} oauth2 is not enabled +i18n.cannot_delete_online_cluster.11ad=Cannot delete an online cluster +i18n.no_corresponding_script_info_or_global_script_selected.765b=There is no corresponding script information or global script is selected +i18n.select_file.9feb=Please select a file +i18n.project_has_build_items_cannot_delete.c2df=The current project has a build item and cannot be deleted directly. +i18n.data_id_label.81b6=Data ID +i18n.key_field_not_configured.7b22=The KEY field is not configured. +i18n.upgrade_duration_message.bab4=It takes about 30 seconds to 2 minutes during the upgrade (restart). +i18n.git_submodule_update_failed_status_code.2218=Git submodule update failed status code\: +i18n.project_id_not_found.b87e=No project id. +i18n.operation_type.de9c=operation type +i18n.project_has_logs_cannot_migrate.2e0e=The current project has log reading and cannot be migrated directly. +i18n.delete_failure_with_colon.b429=Delete failed\: +i18n.tag_cannot_contain_colon.f9ae=Labels cannot contain\: +i18n.distribute_id_already_exists_globally.6478=The distribution id already exists, and the distribution id needs to be globally unique. +i18n.import_exception.04b6=Import the {} data exception\: {} +i18n.script_template_log2.6b2c=Script template log +i18n.id_is_empty.3bbf=ID is empty +i18n.mfa_incorrect_code.8783=\ MFA verification code is incorrect +i18n.parameter_parsing_exception.0056=Parameter parsing exception\: {} +i18n.file_upload_failure_due_to_missing_chunks.1865=File upload failed, there is a case of sharding loss , {} \!\= {} +i18n.root_path.1396=root path +i18n.node_not_exist.0027=There is no corresponding node +i18n.public_key_or_private_key_does_not_exist.dc0d=The public or private key does not exist +i18n.no_user_info.0355=There is no corresponding user information. +i18n.correct_verification_code2_required.df13=Please enter the correct verification code +i18n.please_fill_in_correct_python3_version.abb1=Please fill in the correct python3 version number. +i18n.incorrect_account_credentials_or_unsupported_auth.1ef9=Incorrect account password or unsupported authentication, +i18n.file_already_exists.d60c=The current file already exists, please do not upload it repeatedly. +i18n.start_executing_pre_release_command.6c7e=Start executing the {} pre-release command +i18n.publish_to_ssh_directory_required.56a6=Please enter the directory to publish to ssh +i18n.config_path_exceeds_length_limit.f684=Configuration path exceeds {} length limit\: {} +i18n.cluster_response_incorrect.c08a=The cluster response information is incorrect, please confirm that the cluster address is the correct server level address +i18n.monitor_docker_timeout.b03b=Monitor docker [{}] timeout {} +i18n.project_soft_linked_by.8556=The project is in the {} soft chain +i18n.log_recorder_error_message.ee3e=Logger is turned off/or not enabled +i18n.distribution_project_required.2560=Please select a distribution item +i18n.test_result.8441=Test Result\: {} {} +i18n.environment_variable.3867=environment variables +i18n.unsupported_mode_with_colon.c6de=Unsupported modes\: +i18n.message_conversion_exception.cce8=message conversion exception +i18n.oauth2_login_failure.3841=OAuth 2 login failed, the platform account does not meet the requirements of this system +i18n.node_did_not_pull_anything.8af5=Node did not pull any +i18n.project_has_monitoring_items_cannot_delete.c9a3=The current project has monitoring items and cannot be deleted directly. +i18n.compression_type_not_supported.9dea=Unsupported compression types, +i18n.cache_plugin_path_required.2093=Cache plugin path cannot be empty +i18n.data_file_content_error.e86f=The content of the data file is wrong, please check whether the file has been illegally modified\: +i18n.parameter_error_path_error.f482=Parameter error path error +i18n.script_template_not_exist.e05f=The script template does not exist\: +i18n.execute_event_script_error.7c69=execution event script error +i18n.associated_group2_required.bd05=Please select the associated group +i18n.gradle_plugin_depends_on_java.2bb3=The gradle plugin depends on java, and the use of the gradle plugin must give priority to the introduction of the java plugin +i18n.no_server_management_permission.ee19=You do not have server level administrative privileges\: -2 +i18n.no_static_directory_configured.d3c0=There is currently no static directory configured, and the scheduled task is automatically cancelled. +i18n.distribute_log.c612=distribution log +i18n.trigger_result.364e=[{}]-{} trigger result\: {} +i18n.no_parameters_added_with_minus_two.a7cf=No parameters were added\: -2 +i18n.distribute_id_requirements.9c63=The distribution id cannot be empty and has a length of 2-20 (letters, numbers, and underscores). +i18n.repository_authorization_error.4f50=Warehouse authorization information error +i18n.ssh_rename_failed_exception.94aa=SSH Rename Failed Exception +i18n.associated_data_and_exist_in_workspace.5fa7=There are still associations under the current workspace\: {} and {} data +i18n.delete_log_file_failure.bf0b=Failed to delete log file +i18n.content_format_error_with_detail.c846=Content formatting error, please check the correction\: +i18n.project_log_storage_path_required.d0bb=Please fill in the project log storage path, or authorization has not been configured +i18n.forbidden_operation_range.247f=[Prohibited operation 】{} {} to {} +i18n.please_do_not_delete_this_file.0a7f=Do not delete this file, the associated ID will be invalid after deletion +i18n.strict_execution_mode_event_script_error.c82a=Strict execution mode, the event script returns an abnormal status code, +i18n.file_missing_cannot_publish.3818=The current file is missing and the publish task cannot be executed +i18n.log_file_does_not_exist_or_error.a0e7=The log file does not exist or is incorrect +i18n.build_task_waiting.e303=Build task continues to wait\: {} {} +i18n.no_manager_node_found.5934=No management nodes were found in the current cluster +i18n.file_transfer_exception.bda6=Forwarded file exception +i18n.select_node.f8a6=Please select a node +i18n.upload_progress_with_units.44ad=Upload file progress \:{} {}/{} {} +i18n.ssh_terminal_execution_log.58f1=SSH end point execution log +i18n.project_monitor.d2ff=project monitoring +i18n.push_image_interrupted.6377=Push image is interrupted\: +i18n.default_value.1aa9={} [Default] +i18n.read_additional_variables.5eb0=Read additional variables\: {} {} +i18n.node_transfer_info_encoding_exception.12c8=Node transmission information encoding is abnormal\: +i18n.user_operation_log.2233=User operation log +i18n.handle_node_deletion_script_failure.071b=Failed to process {} node deletion script {} +i18n.ssh_terminal_log.775f=SSH end point log +i18n.query_data_error.45e7=query data error +i18n.build_image_call_container_exception.7e13=Build image call container exception +i18n.machines_docker_data_fixed.af8a=Successfully repaired {} machine DOCKER data +i18n.project_name.31ec=project +i18n.load_success.154e=Loaded successfully +i18n.start_checking_backup_project_files.baa7=Start checking the backup project file\: {} {} +i18n.no_script_template_specified.7d14=There is no corresponding script template\: +i18n.push_image_container_exception.2090=Push image call container exception +i18n.ssh_with_build_items_message.0f6d=The current ssh has a build item and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.ssh_item_distribution_required.2884=Please choose to distribute SSH items +i18n.file_name_already_exists.0d4e=The file name already exists +i18n.waiting_to_start.b267=Wait to start\: +i18n.no_corresponding_data.4703=No corresponding data +i18n.info_to_retrieve_not_found.96d7=No information to obtain +i18n.http_proxy_address_unavailable.b3f2=HTTP proxy address is not available\: +i18n.maven_plugin_depends_on_java.23f8=The maven plugin relies on java, and the use of the maven plugin must give priority to the introduction of the java plugin +i18n.join_beta_program.5c1f=Whether to join the beta program +i18n.cluster_does_not_exist.97a4=The current cluster does not exist +i18n.publish_command_contains_forbidden_command.097d=The publish command contains a command that prohibits execution +i18n.close_exception.5b86=close exception +i18n.no_project_info_found.725a=No corresponding project information found. +i18n.upgrade_database_process.e604=Upgrade the database process\: +i18n.no_type.9153=No corresponding type +i18n.user_operation_alarm.15b9=User operation alarm +i18n.start_executing_build_task.a5ac=Start executing the build task, task wait time\: {} +i18n.event_type_not_supported.e9c3=Unsupported event types\: {} +i18n.user_directory_not_found_private_key_info.6ce4=The user directory did not find the private key information +i18n.upload_progress_message_format.b91c=Upload file progress :{}[{}/{}] {}/{} {} +i18n.download_failed_generic.be4f=Failed to download file +i18n.oshi_network_card_monitoring_exception.6d41=OSHI network interface card resource monitoring abnormality +i18n.connection_successful.b331=Connection successful +i18n.incomplete_upload_info_now_slice.34aa=Upload information not completed\: nowSlice +i18n.account_locked_cannot_change_password.d6ab=The current account is locked and the password cannot be changed. +i18n.no_publish_distribution_related_data_id.a077=No Release Distribution Corresponding Linked Data ID +i18n.build_failed.a79a=Build failed\: +i18n.delete_ssh_temp_file_failure.6e5f=Failed to delete ssh temporary file +i18n.branch_required.5095=Please select a branch +i18n.trigger_token.abe6=Trigger token +i18n.user_binding_warning.16b0=The current permission group is also bound to the user and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.account_already_bound_to_mfa.5122=The current account has been bound to mfa. +i18n.compare_files_result.bec4=Compare the file results, there are {} product files and {} need to be uploaded. +i18n.content_format_error.ce15=Content format error, please check for correction +i18n.login_name_cannot_be_empty.9a99=Login name cannot be empty +i18n.correct_host_required.8c49=Please fill in the correct host. +i18n.select_correct_project_path_or_no_auth_configured.366a=Please select the correct project path, or authorization has not been configured yet +i18n.need_initialize_system.fb62=The system needs to be initialized. +i18n.not_logged_in.6605=Not logged in +i18n.old_and_new_passwords_match.55b4=The old and new passwords are the same. +i18n.super_admin_cannot_reset_password_this_way.0761=Superadmins cannot reset passwords this way +i18n.protocol_required.b4f8=Please select an agreement +i18n.incorrect_ip_address.b872=IP address information is incorrect +i18n.main_class_not_found.8a12=No running main class found +i18n.data_creation_time_format_incorrect.7772=The data creation time format is incorrect {} {} +i18n.delete_backup_data_file_failure.2ebf=Failed to delete backup data file +i18n.user_custom_workspace.ef93=user-defined workspace +i18n.editable_suffixes_not_configured.5b41=No suffixes configured to allow editing +i18n.invalid_repository_info.b4ad=Invalid warehouse information +i18n.login_JPOM.0de6=Log in to JPOM +i18n.ssh_does_not_exist_with_message.de6c=The corresponding ssh does not exist +i18n.not_running.4f8a=not running +i18n.no_get_id_method.2a65=No getId method +i18n.project_id_keyword_occupied.1cae=Item id {} keyword is occupied by the system +i18n.delete_log_file_failure_with_colon.d867=Failed to delete log file\: +i18n.manual_cancel.8464=manual cancellation +i18n.super_admin_mfa_verification_disabled.b97d=Successfully close the super administrator account mfa verification\: {} +i18n.prepare_to_build.1830=Ready to build +i18n.unknown_exception_on_pull_code.2b2e=An unknown exception occurred in the pull code. It is recommended to clear the build and re-operate\: +i18n.close_connection_exception.c855=Closed connection exception +i18n.delete_action.2f4a=delete +i18n.configure_monitoring_interval.9741=Please configure the monitoring cycle +i18n.restart_self_exception.85b7=Restart itself exception +i18n.build_finished_duration.7f7c=End of build - cumulative time\: {} +i18n.handle_node_sync_script_failure.e99f=Failed to process {} node synchronization script {} +i18n.project_disabled.f8b3=The current project is disabled +i18n.certificate_already_exists.adf9=The current certificate already exists (in the global scope of the system) +i18n.oshi_file_system_monitoring_exception.dc24=Oshi file system resource monitoring exception +i18n.image_cannot_be_empty.1600=Mirror image cannot be empty +i18n.no_corresponding_node_info.cd24=There is no corresponding node information. +i18n.node_plugin_version_required.2318=Node plugin version cannot be empty +i18n.docker_exec_terminal_process_ended.c734=[{}] docker exec end point +i18n.node_migration_project_failure.d5ff=Node migration project failed +i18n.cannot_cancel_super_admin_permissions.99b5=Cannot revoke super administrator permissions +i18n.distribute_node_configuration_failure.8146=Distribution {} node configuration failed {} +i18n.file_upload_failed.462e=Uploading file failed\: +i18n.node_service_resumed_normal_operation.2cbd=The [{}] project {} of the [{}] node has resumed normal operation +i18n.file_type_no_restart.0977=File type project not restarted +i18n.current_distribution_data_lost.f9f8=The current distribution data is missing +i18n.deletion_success_message.4359=Delete successfully\! +i18n.pull_repository_code.3f51=Pull repository code +i18n.database_mode_config_missing.ae5d=Missing Database Mode Configuration +i18n.rsa_private_key_file_error.b687=Line {} The rsa private key file does not exist or is incorrect +i18n.protocol_not_supported.b906=Unsupported protocols +i18n.force_unbind_succeeded.5bfd=Forced unbinding successful +i18n.unexpected_exception.2b52=Abnormality occurs +i18n.check_email_error.636c=Check email information error\: {} +i18n.initialization_success.4725=Initialization successful +i18n.running_status.d679=Running +i18n.error_message.483d=Script library information not found\: {}, please check if the reference tag is correct or if the script has been deleted +i18n.check_git_client_exception.42a3=Check for git client side exceptions +i18n.update_success.55aa=Update successful +i18n.initialize_database_failure.2ef9=Failed to initialize database {} +i18n.docker_cluster_associated_workspaces_message.5520=At present, docker is also associated with {} workspace docker clusters, which cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.at_least_one_node_required.a290=Please select at least one node +i18n.reset_failed.5281=Reset failed\: +i18n.no_current_data_permission.17d7=There is no current data permission, and an administrator or data creator is required to operate the data. +i18n.database_not_initialized.e5e7=The database has not been initialized yet +i18n.project_path_occupied.cddd=The current project path is already occupied by [{}], please check +i18n.configuration_modification_not_supported.5872=Online modification of configuration files is not supported in the current environment +i18n.non_dsl_project_unsupported_operation.5c09=Non-DSL projects do not support this operation +i18n.contact_does_not_exist.3369=Contact person does not exist +i18n.check_cluster_info_exception.7b0c=Check for abnormal cluster information +i18n.docker_cluster_info.a2eb=Docker cluster information +i18n.line_number_error.c65d=wrong line number +i18n.node_machine_table_exists_no_need_to_fix.2625={} pieces of data already exist in the node machine table, no need to repair the machine data +i18n.execution_exception_message.ef79=Execution exception\: +i18n.auto_detect_local_docker_and_add.af72=The local docker is automatically detected and automatically added\: +i18n.database_event_execution_ended.690b=Database {} event execution ended,\: {} +i18n.build_history.a05c=Building history +i18n.machine_installation_id.d0b9=The local installation ID is\: {} +i18n.no_available_docker_server.6aaa=There is no docker server available +i18n.credential_cannot_be_empty.d055=Certificate cannot be empty +i18n.workspace_label.98d6=Workspace +i18n.file_modification_event.5bc2=File modification event\: {} {} +i18n.no_corresponding_build_record_ignore_deletion.86a0=There is no corresponding build record, ignore and delete +i18n.cannot_configure_root_path.d86e=The root path cannot be configured\: +i18n.completed_and_successful_count_insufficient.92fa=Insufficient number of completed and successful {}/{} +i18n.get_project_info_failure.ddff=Failed to obtain project information\: +i18n.name_required.856d=Please fill in the name. +i18n.build_source.2ef9=Build the source, +i18n.joined_beta_program.c4e2=Successfully joined the beta program +i18n.recover_abnormal_data.9adf={} Restore {} abnormal data +i18n.file_name_error_message.7a25=File name cannot contain/ +i18n.config_file_not_exist.09dd=Configuration file does not exist +i18n.parent_task_not_found.bac1=No parent task found +i18n.static_file_scanning_disabled.2b2b=Static file scanning is not enabled +i18n.no_resource_found.dc22=No corresponding resource found. +i18n.container_name_cannot_be_empty.14b1=Container name cannot be empty +i18n.token_parse_failed.cadf=Token parsing failed\: +i18n.distribute_exception.da82=distribution exception +i18n.script_content_cannot_be_empty.49be=Script content cannot be empty +i18n.need_execute_pre_events.b848={} preceding events need to be executed +i18n.cluster_binding_success.eb7e=Cluster binding successful +i18n.port_field_required_or_incorrect.8426=Line {} port field cannot be empty or incorrect +i18n.delete_failure.acf0=Delete failed +i18n.prepare_to_migrate_data.f251=Prepare to migrate data +i18n.ignore_log_record.48f5=Ignore logging {} +i18n.need_handle_build_micro_queue_count.3010=Number of build microqueues to process\: {} +i18n.user_management.7d94=user management +i18n.file_directory_too_long.c101=If the file directory length exceeds 500, such files are automatically ignored\: {} +i18n.temporary_result_file_does_not_exist.1c7e=Temporary result file does not exist\: {} +i18n.command_content_required.6005=Please enter the command content +i18n.system_parameters.c7b0=system parameters +i18n.node_address_not_found.f955=Without the node address, the operation cannot continue. +i18n.verification_code_incorrect_retry.d88d=Verification code is incorrect, please re-enter +i18n.git_installation_location.7984=Git installation location\: {} +i18n.scheduled_task_exception.f077=Timed task exception {} +i18n.monitor_docker_exception.e326=Monitor docker [{}] exception +i18n.restart_result.253f=Restart result\: +i18n.start_building_with_thread_execution.83cd=Start building, build thread execution +i18n.task_ended_successfully.e176=Mission ended normally +i18n.non_existent_build_product.1df4={} does not exist, processing bundle failed +i18n.current_not_supported.78b7=Currently not supported\: +i18n.no_user.3b69=No corresponding user +i18n.wait_for_seconds.ff7b=Execute wait {} seconds +i18n.unknown_script_template_or_workspace.27f1=Unknown script template or workspace +i18n.send_message_failure_prefix.6f8c=Failed to send message\: +i18n.select_correct_ssh.aa93=Please select the correct ssh. +i18n.synchronization_failed.d610=Synchronization failed +i18n.client_secret_not_configured.6923=No clientSecret configured +i18n.workspace_not_exist.a6fd=There is no corresponding workspace +i18n.no_user_specified.6650=There is no corresponding user\: +i18n.backup_database.9524=backup database +i18n.delete_project_file_failure.f007=Failed to delete project file\: +i18n.incorrect_certificate_info.aee1=The certificate information filled in is wrong. +i18n.build_command_not_empty.2e37=Build command cannot be empty +i18n.auto_migrate_exist_backup_logs.dc33=Automatic migration of backup logs {} -> {} +i18n.close_session_exception.3491=Close session exception +i18n.login_failure_O_auth2_message.3e91=Login failed (OAuth2), please contact the administrator\! +i18n.log_retention_days.99d1=Statistics log retention days {} +i18n.env_must_be_map_type.f8ad=Env must be of type map +i18n.batch_trigger_script_exception.8fb4=Server level script batch trigger exception +i18n.upgrade_failure.4ae2=Upgrade failed +i18n.no_branch_or_tag_message.8ae3=No {} branches/tags +i18n.general_error_message.728a=Ah, something seems to be wrong, please try again later~ +i18n.service_info_incomplete_with_code2.e9ca=The service information is incomplete and cannot be operated\: -2 +i18n.ignored_operation.edee=Ignored operation\: {} +i18n.monitor_name_cannot_be_empty.514a=Monitor name cannot be empty +i18n.get_project_pid_failure.17b0=Failed to get project pid +i18n.please_fill_in_steps.229d=Please fill in the steps +i18n.distribute_success.c689=Distributed successfully +i18n.unable_to_get_container_execution_result_file.7b2c=Unable to fetch container execution result file +i18n.import_data.8ef8=Import data +i18n.unsupported_mode_with_script_log.6a7a=Unsupported mode, script log +i18n.ssh_connection_failed.74ab=SSH connection failed, please check whether the username, password, host, port, etc. are filled in correctly, and whether the timeout time is reasonable\: +i18n.node_name_required.5bdf=Please fill in the node name +i18n.client_terminated_connection.6886=Client side terminates connection\: {} +i18n.close_thread_pool.4cd9=Close the {} thread pool +i18n.no_corresponding_folder.621f=No corresponding folder +i18n.no_online_manager_node_found.05d7=The current cluster does not find an online management node +i18n.command_execution_failed.90ef=Command execution failed +i18n.monitor_ssh_timeout.59fd=Monitor ssh [{}] timeout {} +i18n.execution_completed.24a1=Execution completed\: +i18n.close_project_failure.a1d2=Failed to close the project\: +i18n.select_folder_to_compress.915f=Please select a folder to compress +i18n.config_file_not_found.fc87=No configuration file found +i18n.update_ssh_machine_id_failed.bd24=Updating SSH table machine id failed\: +i18n.select_pull_code_protocol.fc24=Please select the protocol for pulling the code +i18n.auth_directory_cannot_be_empty.21ba=The authorization directory cannot be empty +i18n.status_not_in_progress.f410=The current status is not in progress. +i18n.image_tag_required.92cf=Please fill in the mirror label +i18n.privacy_variable_cannot_trigger.dbc9=Privacy variables cannot generate triggers +i18n.no_error_data_in_table.3092=The current table has no error data +i18n.update_restore_data.1b0b=Update restore data\: {} +i18n.script_template.1f77=script template +i18n.certificate_info_incorrect.a950=The certificate information is incorrect, and the certificate compressed package must contain\: ca.pem, key.pem, cert.pem +i18n.trim_completed_with_recovered_space.0463=Trim completed, total recycling space\: +i18n.default_workspace_cannot_delete.18b4=The default workspace of the system cannot be deleted +i18n.synchronization_node_failure_with_details.8660=Synchronizing node {} failed\: {} +i18n.email_verification_failed.5863=Failed to verify mailbox information, please check the configured mailbox information. Port number, authorization code, etc. +i18n.permission_group.ea59=permission grouping +i18n.node_exception_null_pointer.d408=Node exception, null pointer +i18n.node_system_logs.3ac9=Node system log +i18n.parameter_value_required.3a29=Please fill in the parameter value +i18n.node_connection_lost.b6c7=Node connection lost or not connected yet +i18n.associated_data_lost_error.becb=ERROR\: Loss of Linked Data +i18n.ssh_modify_permission_error.0cd3=Ssh modify file permissions exception...\: {} {} +i18n.please_fill_in_password.455f=Please fill in the pass +i18n.fuzzy_match_files.139d={} fuzzy match to {} files +i18n.no_distribution_id_found.8df2=Distribution ID not found +i18n.command_script_not_found_in_service.25ac=There is no command script in the current service\: {}. {} +i18n.search_project.7e9b=Search for items +i18n.machine_asset_management.36ea=Machine Asset Management +i18n.no_matching_data_found.fe9d=No matching data found +i18n.project_console.3a94=Project Console +i18n.script_template_not_exist.1d5b=The corresponding script template no longer exists +i18n.subnet_mask_incorrect.6c27=The submask is incorrect\: +i18n.no_implemented_publish_distribution.fcf8=Publish distribution without implementation\: {} +i18n.h2_connection_successful.11f3=Successfully connected to H2, start trying automatic backup +i18n.container_build_exception.a98f=Container build exception :{} -> {} +i18n.import_project_template_csv.c6f1=Project Import Template.csv +i18n.handle_message_exception_with_colon.56f0=Handle message exceptions\: +i18n.node_already_exists_in_workspace.9499=The node already exists in the corresponding workspace\: +i18n.plugin_not_initialized.3ea9=The corresponding plug-in has not been initialized yet +i18n.no_node_info.6366=No node information +i18n.associated_ssh_node_contains_nonexistent_node.c7f5=The associated SSH node contains a non-existent node +i18n.node_statistics.b4e1=Node statistics +i18n.user_workspace_relation_table.851e=User (permission group) workspace relationship table +i18n.dsl_info_not_configured.3487=DSL information not configured (project information is wrong) +i18n.migration_success_message.e546=Successful project migration :{} | {} +i18n.node_connection_failed.8497={} Node connection failed {} +i18n.ssh_management.9e0f=SSH management +i18n.synchronization_script_exception.9c70=Synchronization script exception +i18n.previous_node_distribution_failure.d556=The previous node failed to distribute, and the distribution was cancelled. +i18n.cleanup_token_exception.760e=Execute cleanup token [{}] exception +i18n.incorrect_node_info_node_does_not_exist.2fd8=The node information is incorrect, and the corresponding node does not exist +i18n.plugin_not_found.a6e5=Correspondence Find the corresponding plugin\: +i18n.file_storage_center.6acf=File Storage Center +i18n.system_administrator.181f=system admin +i18n.parameter.3d0a=parameter +i18n.ignore_execution_event_script.8872=Ignore the execution event script {} {} {} +i18n.login_info_expired_please_re_login.fbbc=Login information has expired, please log in again +i18n.clear_build_product_failed.edd4=Failed to clear bundle +i18n.prepare_to_delete_current_database_file.1e6a=Prepare to delete the current database file +i18n.distribution_in_progress.c3ae=It is still being distributed, please wait for the distribution to end. +i18n.initialization_failure.19e9=Initialization failed\: +i18n.project_exists.f4e0=There are still items under this node that cannot be deleted directly (you need to unbind or delete the associated data in advance before deleting) +i18n.modify_service_success.bd75=Modify service successfully +i18n.database_connection_not_configured.c80e=Database connection not configured +i18n.clear_temp_file_failed_manually.0dad=Failed to clear temporary file, please clean it manually\: +i18n.reset_log_failure.b3d3=Failed to reset log +i18n.type_field_required.7637=Line {} type field cannot be empty +i18n.migration_target_workspace_node_mismatch.d9cf=The target workspace and node to migrate to are inconsistent +i18n.start_deleting_files.210c=Start deleting {} file {} +i18n.download_success.5094=Download successful +i18n.running_project_cannot_change_path.5888=A running project cannot modify the path +i18n.please_fill_in_correct_gradle_version.6e19=Please fill in the correct gradle version number +i18n.get_node_monitoring_info_failure.595a=Failed to obtain node monitoring information +i18n.docker_authorization_failed.8ede=Docker authorization failed\: {} +i18n.supported_java_plugin_versions.bd70=Currently supported versions of java plugin\:% s +i18n.delete_success_with_cleanup.6155=Delete successfully, and clean up the history bundle successfully +i18n.cluster_info.32e0=cluster information +i18n.file_type_not_supported.ae5d=Unsupported file types\: +i18n.verification_code_incorrect.d8c0=Verification code is incorrect +i18n.main_class_not_found.b4b7=The corresponding MainClass was not found in\: +i18n.node_not_exist.760e=The corresponding node does not exist +i18n.start_distribution_exclamation.9fc2=Start distributing\! +i18n.failure_prefix.115a=Failure\: +i18n.connection_closed.6d4e=Connection closed {} {} +i18n.no_corresponding_workspace_permission.8402=No corresponding workspace permissions +i18n.post_distribution_action_required.8cc8=Please select the action after distribution +i18n.unsupported_item.bcf4=Unsupported\: +i18n.no_available_new_version_upgrade.d8f2=No new version upgrade available\: -1 +i18n.docker_associated_workspaces_message.de78=The current docker is still associated with {}, and the workspace docker cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.static_directory_not_configured.9bd6=Static directory has not been configured +i18n.server_jps_command_exception.e380=The current server jps command is abnormal, please check whether the jdk is complete and whether the java environment variables are configured correctly +i18n.data_download_failed.9499=Data download failed +i18n.delete_data.40f8=delete data +i18n.image_name_required.ab44=Please fill in the mirror name. +i18n.listen_log_changes.9081=Monitor log changes +i18n.configure_correct_auth_url.22e7=Please configure the correct authorization URL. +i18n.current_upload_chunk_info_incorrect.900e=The currently uploaded sharding information is wrong. +i18n.listen_log_success_currently_sessions_viewing.a74a=Listening to the {} log successfully, there are currently {} sessions being viewed +i18n.node_script_template.be6a=Node script template +i18n.post_packaging_action_required.bf66=Please select the operation after packaging. +i18n.multiple_clusters_exist.196b=There are multiple clusters in the system, no need to automatically bind data +i18n.variable_name_already_exists.70f2=The corresponding variable name already exists +i18n.node_management.b26d=node management +i18n.account_mfa_not_enabled.fd39=The current account has not started two-step verification. +i18n.exit_successful.8150=Exit successfully +i18n.main_class_attribute_not_found.24c9=MainClass property not found in manifest file +i18n.code_pull_conflict.6e8e=If there is a conflict in the pull code, you can try to clear the build or resolve the conflict in the repository and then re-operate.\: +i18n.cannot_delete_super_admin.68e2=Cannot delete superadmin +i18n.node_running_status_abnormal.3160=[{}] The running state of the node is abnormal +i18n.pull_code_failed.70d6=Failed to pull code\: {} +i18n.execution_succeeded.f56c=Successful execution +i18n.docker_already_exists.d9a5=The corresponding docker already exists. +i18n.build_task_queue_waiting.5f06=The build task starts to enter the queue and wait.... +i18n.system_process_monitoring_error.fe1d=System process monitoring exception\: +i18n.pull_log_exception.cc3e=pull log exception +i18n.create_file_watch_failure.bc1a=Failed to create file for listening +i18n.no_project_found.ef5e=No corresponding item found. +i18n.delete_project_file_failure_with_full_stop.85b8=Deleting project file failed\: +i18n.create_plugin_endpoint_connection_failure.30f8=Failed to create plug-in connection {} +i18n.manual_cache_refresh_exception.9d91=Manually flush cache exception +i18n.no_script_template_found.0498=No corresponding script template found +i18n.user_field_required.8732=Line {} The user field cannot be empty +i18n.cannot_delete_running_project.e56b=Cannot delete a running project +i18n.incorrect_parameter.02ce=Parameters are incorrect +i18n.update_node_machine_id_failed.51d9=Updating node table machine id failed\: +i18n.compare_files_result_with_delete.033d=Comparing the file results, there are {} product files, {} need to be uploaded, and {} need to be deleted. +i18n.build_trigger_queue_result.a1fe=Build trigger queue execution result\: {} +i18n.unsupported_type_with_colon.1050=Unsupported types\: +i18n.no_machine_found.c16c=No corresponding machine was found. +i18n.admin_email_not_configured.ecb8=The administrator has not configured the system mailbox, please contact the management to configure the shipping information. +i18n.node_response_error.efc6=\ Node response is abnormal, status code is wrong\: +i18n.target_database_info_not_specified.2ff6=No target database information specified +i18n.file_type_no_stop.00ff=File type item not stopped +i18n.monitor_ssh_exception.e9ce=Monitor ssh [{}] exception +i18n.running_node_cannot_be_empty.ffc6=Run node cannot be empty +i18n.push_registration_to_server_failed.5949=Push registration to server level failed {} +i18n.delete_script_template_execution_error.8bc5=Delete script template execution data error\: {} +i18n.no_menus_contact_admin.cfec=There is no menu, please contact the administrator. +i18n.check_passed.dce8=Check passed +i18n.upload_progress_with_colon.dd5b=Upload file progress :{} {}/{} {} +i18n.machine_node_info.6a75=machine node information +i18n.ssh_command_log.7fd1=SSH command log +i18n.upgrade_failure_with_colon.59f1=Upgrade failed\: +i18n.script_tag_modification_not_allowed.cb75=Script tags cannot be modified +i18n.auth_exception.27be=authorization exception +i18n.unsupported_plugin_message.2889=The {} plugin is not currently supported +i18n.operation_succeeded_refresh_backup.54a9=The operation was successful, please refresh later to check the backup status. +i18n.no_data.55a2=No data. +i18n.node_not_enabled.a14d=Node is not enabled +i18n.no_build_id.a0b8=No buildId +i18n.user_not_select_permission_group.1091=User did not select permission group +i18n.project_has_logs_cannot_delete.1d2a=The current project has log reading and cannot be deleted directly. +i18n.unsupported_encoding_with_placeholder.3bd9=Unsupported encoding methods\: {} +i18n.export_image_exception.cb1c=Export mirror exception +i18n.backup_data_not_exist.f88c=Backup data does not exist +i18n.illegal_character_encoding_format.af7a=The configured character encoding format is invalid\: +i18n.select_file_to_delete.33d6=Please select the file to delete +i18n.online_upgrade_cannot_downgrade.d419=Online upgrade cannot be downgraded +i18n.incorrect_cluster_address.893f=The cluster address filled in is incorrect. +i18n.check_docker_dependency_error.60f7=Check for docker dependency errors\: {} +i18n.program_already_running.96e1=The current program is running and cannot be started repeatedly, PID\: +i18n.password_change_success.8013=Password changed successfully\! +i18n.build_resource_cleanup_failed.c4cf=Failed to clean up build resources +i18n.incorrect_publish_method.e095=Incorrect publishing method +i18n.import_low_version_data_to_new_version.247b=2. Import the exported lower version data (sql file) into the new version [Add --replace-import-h2-sql\=/xxxx.sql to the startup parameters (the path needs to be replaced with the sql file save path output by the console in the first step) ] +i18n.incorrect_line_number.5877=Line number is incorrect +i18n.file_deletion_event_with_details.7537=File deletion event\: {} {} +i18n.configure_correct_token_url.7bba=Please configure the correct token URL. +i18n.maven_plugin_version_required.71f1=Maven plugin version cannot be empty +i18n.trigger_token_error_or_expired_with_code.393b=Triggered token error, or has expired\: -1 +i18n.invalid_webhooks_address.d836=WebHooks address is invalid +i18n.project_path_auth_not_under_jpom.0e18=Project path authorization cannot be located in the Jpom directory +i18n.reconnect_failure.7c01=Reconnect failed +i18n.project_operations.03d9=Project operation and maintenance +i18n.image_not_exist.ee17=Mirror does not exist +i18n.no_data.1ac0=No data. +i18n.load_file_failure.86cc=Failed to load file\: +i18n.no_corresponding_ssh_script_info.1c12=There is no corresponding ssh script information. +i18n.log_file_error.473b=log file error +i18n.compare_id_not_exist.43be=Comparison ID does not exist +i18n.private_file_not_found.ee45=No private documents +i18n.detect_local_docker_exception_with_details.7cc9=Detect local docker exceptions\: +i18n.prepare_restart.8251=Start preparing for project restart\: {} {} +i18n.save_node_data_failed.f314=Failed to save node data\: +i18n.range_format_not_supported.d69e=Unsupported range format +i18n.select_at_least_one_node_project.637c=Select at least 1 node project +i18n.close_beta_plan_success.5a94=Close the beta program successfully +i18n.start_rolling_back_execution.a019=Start rollback execution +i18n.get_container_log_interrupted.041d=The get container log operation was interrupted\: +i18n.node_has_no_workspace.69c0=Node has no workspace +i18n.correct_enterprise_wechat_address_required.5f2d=Please enter the correct WeCom address +i18n.unknown_jsch_log_level_with_details.1f9a=Unknown jsch log level\: {} {} +i18n.file_not_found.d952=File does not exist +i18n.node_communication_failure_signal.5aae=Node communication failure, an error signal occurs when the remote address and port. Usually, the remote host cannot be accessed because the firewall or router in the middle is turned off. +i18n.multiple_node_data_exists_merge_config.043f=There are multiple data at the node address {}, and the configuration information of the {} node will be automatically merged. +i18n.unknown_data_loss.5a24=Unknown (data loss) +i18n.command_execution_failed_details.77ed=The execution of the command failed, as follows\: +i18n.unknown_parameter.96dd=Unknown parameter +i18n.publish_file_second_level_directory_required.2f65=Please fill in the secondary directory for publishing documents +i18n.node_id_required_and_format.5926=Node ID cannot be empty and 2-50 (English letters, numbers and underscores) +i18n.restore_backup_data_failed.58af=Failed to restore backup data +i18n.system_task_execution_exception.d559=Execute system task exception +i18n.command_management.621f=Command Management +i18n.no_project_specified2.a7f5=There is no corresponding project\: +i18n.certificate_info_error_issuer_or_subject_DN_not_found.805d=Certificate information error, issuerDN or subjectDN not found +i18n.exported_ssh_data.2896=Exported ssh data +i18n.refreshing_cache.c969=The cache is being refreshed, please do not refresh repeatedly +i18n.start_executing_database_event.fc57=Start executing database events\: {} +i18n.static_file_storage.35f6=static file storage +i18n.project_log_is_existing_folder.a80a=The project log is an existing folder +i18n.project_data_workspace_id_inconsistency.7ed6=Project data workspace ID [{}] query out that the node ID is inconsistent, old data\: {}, new data\: {} +i18n.query_workspace_error.6a0d=Failed to query the wrong workspace +i18n.unknown_database_mode.f9e5=The current database schema is unknown +i18n.secondary_directory_match.0aec={} secondary directory fuzzy match to {} files, the current file retention method {} +i18n.ssh_server_alive_interval_config_error.1f11=Error configuring ssh serverAliveInterval +i18n.reset_success.faa3=Reset successful +i18n.please_fill_in_repository_name.9f0d=Please fill in the warehouse name. +i18n.parameter_error_ssh_name_cannot_be_empty.ff4f=Parameter error ssh name cannot be empty +i18n.current_docker_cluster_still_associated_with_workspaces.b301=The current docker cluster is also associated with {} workspace clusters and cannot exit the cluster +i18n.current_distribution_has_only_one_project.cd59=There is only one project in the current distribution, just delete the entire distribution. +i18n.no_corresponding_execution_log.9545=No corresponding execution log +i18n.import_success_with_details.a4a0=Import successful (encoding format\: {}), add {} pieces of data, modify {} pieces of data +i18n.current_distribution_has_build_items_cannot_unbind.a8e9=There is a build item in the current distribution and it cannot be untied. +i18n.listen_file_failed_may_not_exist.fd56=Listening to the file failed, maybe the file does not exist +i18n.log_file_cleanup_failed.3a3b=Failed to clean log file +i18n.parse_csv_exception.885e=Parsing CSV Exception +i18n.no_build_record_found.76f4=There is no corresponding build record yet. +i18n.login_info_required.973b=Please enter your login information +i18n.h2_database_backup_success.a099=H2 database backup was successful\: {} +i18n.uses_only_supports_string_type.ac54=Uses only support String types +i18n.link_id_required.5dc7=Link Mode LinkId Required +i18n.oshi_hard_disk_monitoring_exception.c642=Oshi hard disk resource monitoring is abnormal +i18n.get_docker_cluster_info_failure.c80d=Failed to get docker cluster information +i18n.exit_code.3b54=Exit code for this execution\: {} +i18n.compare_project_failure.e6ab=Failed to compare project files\: +i18n.parse_file_exception.374d=Parsing file exception, +i18n.certificate_info_table.fff8=Certificate Information Table +i18n.online_upgrade.da8c=Online upgrade +i18n.no_log_file.bacf=No log file yet +i18n.listen_task_lost_or_not_found.347f=The listening task is lost or not found\: {} +i18n.parse_project_csv_exception.ece1=Parse item CSV exception +i18n.ssh_file_deletion_exception.5ba5=SSH delete file exception +i18n.token_invalid_or_expired.cb96=Token error, or has expired\: -1 +i18n.list_and_query.c783=List, query +i18n.migration_docker_cert_error.a5ea=There was an exception migrating the docker [{}] certificate +i18n.file_downloading.7a8f=File downloading +i18n.mark_must_contain_letters_numbers_underscores.667d=Markers can only contain letters, numbers, and underscores +i18n.please_fill_in_runs_on.c2ff=Please fill in runsOn. +i18n.create_build_task_exception.06f1=Create build task exception +i18n.execution_interrupted.1bb6=Execution was interrupted +i18n.event_loss_or_execution_error.7b14=Event missing or execution error\: {} {} +i18n.public_key_and_private_key_mismatch.4aa2=Public and private keys do not match +i18n.restore_success.4c7f=Restore successful +i18n.monitoring_notifications.de94=monitoring notification +i18n.machines_node_data_fixed.7744=Successfully repaired {} machine node data +i18n.upload_success.a769=Upload successful +i18n.send_email_failure.1ab3=Failed to send email\: +i18n.email_configuration.b3f7=mailbox configuration +i18n.server_script_not_exist.de24=There is no corresponding server level script, please re-select +i18n.java_plugin_version_required.de39=Java plugin version cannot be empty +i18n.soft_link_project_mode_error.ffa0=Items being softchained cannot be in File or Link mode +i18n.node_null_pointer_exception.76fe={} Node, program null pointer exception +i18n.parse_certificate_exception.3b6c=Resolve certificate exception +i18n.get_build_status_exception.914e=Get build status exception +i18n.node_has_distribution_projects_cannot_delete.3987=The node has a distribution project and cannot +i18n.selected_user_status_abnormal.efcf=The selected user has an abnormal status +i18n.target_workspace_consistency.e04c=The target workspace is consistent with the current workspace and the target node is consistent with the current node +i18n.nickname_length_limit.6312=Nickname length can only be 2-10. +i18n.please_pass_parameter.3182=Please pass in parameters +i18n.node_no_data_pulled.0dae=The {} node did not pull any {}, but deleted the data\: {} +i18n.node_info_not_found.2c8c=No node information was found\: +i18n.please_fill_in_repository_address.0cf8=Please fill in the warehouse address. +i18n.table_without_primary_key.7392=Table has no primary key +i18n.invalid_shard_id.46fd=Illegal sharding id +i18n.execution_ended.b793=End of execution\: {} +i18n.user_trigger_unavailable.9866=The current user trigger is not available +i18n.task_ended.b341=End of mission\: +i18n.session_closed_reason.103a=Session [{}] closed for\: {} +i18n.static_directory_auth_cannot_be_empty.2cb2=Static directory authorization cannot be empty +i18n.file_publish_task_record.edc4=File release task record +i18n.no_files_in_zip.1af6=There are no files in the compressed package. +i18n.unable_to_connect_to_repository.52df=Unable to connect to this repository. +i18n.parent_task_not_exist.ca1b=Parent task does not exist +i18n.distribute_exception_with_detail.28fe=Distribution exception {} +i18n.unknown_database_dialect_type.951b=Unknown database dialect type\: +i18n.fill_download_address.763c=Fill in the download address +i18n.verification_code_is.5af5=The verification code is\: {} +i18n.pull_exception.b38d=pull exception +i18n.incomplete_upload_info_total_slice.7e85=Upload information not completed\: totalSlice +i18n.database_corrupted.944e=The database is abnormal. Maybe the database file is damaged (some data may be lost) and needs to be reinitialized. You can try adding --recover\: h2db to the startup parameter to automatically restore,\: +i18n.handle_node_synchronization_script_library_failure.14e4=Failed to process {} node synchronization script library {} +i18n.cluster_name_required.5ca6=Please fill in the cluster name +i18n.build_method_incorrect.5319=The build method is incorrect +i18n.data_does_not_exist.b201=Data does not exist +i18n.operation_time.7e95=operating time +i18n.communication_ip_cannot_be_empty.ae35=Communication IP cannot be empty +i18n.build_info_not_exist.4470=There is no corresponding build information +i18n.protocol_field_required.7cc2=Line {} protocol field cannot be empty +i18n.incorrect_mode_for_migration.caef=The current mode is incorrect and cannot be migrated directly to {} +i18n.socket_session_establishment_failed.4924=Socket session establishment failed, authorization information is wrong +i18n.handle_message_exception.0bdc=Handling message exceptions +i18n.index_field_not_configured.96d9=Index unconfigured field +i18n.file_name_not_found.b0ed=No filename +i18n.file_deletion_event.a51c=File deletion event\: {} +i18n.no_asset_management_permission.739e=You do not have asset management authority. +i18n.current_docker_already_in_other_cluster.e629=Docker has now joined other clusters +i18n.addition_succeeded.3fda=Added successfully +i18n.node_upgrade.3bf3=Node upgrade +i18n.server_system_config.3181=Server level system configuration +i18n.empty_file_cannot_upload.88df=Empty files cannot be uploaded +i18n.config_file_already_exists.c5fe=The corresponding configuration file already exists +i18n.login_password_required.9605=Please fill in the login password +i18n.current_repository_associated_with_build.4b6e=The current warehouse is associated and cannot be deleted directly. +i18n.unzip_exception.92cc=Unzip exception {} by InputStream {} +i18n.read_global_script_file_error.0d4c=Failed to read global script file +i18n.build_product_sync_success.f7d1=The bundle file was successfully synced to the file management center, {} +i18n.decrypt_parameter_failure.d10a=Decryption parameter failed +i18n.no_record.ff41=There is no corresponding record. +i18n.workspace_error_or_no_permission.7c8b=Workspace error, or no permission to edit this data +i18n.upload_failed.b019=Upload failed\: +i18n.no_permission.e343=You do not have the corresponding permission. +i18n.listener_key_not_found.6d3a=No key found +i18n.download_failed_retry.c113=Download failed. Please refresh the page and try again. +i18n.please_fill_in_correct_go_version.44ed=Please fill in the correct go version number +i18n.forbidden_operation_time_range.92bf=[Prohibited operation] Prohibited execution of {} to {} during the current period +i18n.no_matching_files.b7a6={} did not match any file +i18n.multi_download_not_supported.94b9=Does not support sharding multi-terminal download +i18n.parameter_error_port_error.810d=Parameter error port error +i18n.incomplete_node_info_missing_machine_id.1c9a=Incomplete node information, missing machine id +i18n.node_network_connection_exception_or_timeout.5904=The node network connection is abnormal or timed out, please check the running status of the plug-in first, and then check the IP address. +i18n.ssh_command_management.c40a=SSH command management +i18n.variable_name_rules.480a=Variable names 1-50 English letters, numbers and underscores +i18n.parameter_error_user_cannot_be_empty.9239=Parameter error user cannot be empty +i18n.no_database_config_header_found.9ee3=Database configuration identifier header not found +i18n.week_day_range_format.ebec=Week {} to {} +i18n.no_node_info_no_need_to_fix_machine_data.562e=No node information, no need to repair machine data +i18n.send_message_exception.7817=abnormal message sending +i18n.static_file_task_load_failure.b995=Static file task load failed +i18n.server_exception_occurred.9eb4=An exception occurred at the server level +i18n.ssh_unauthorized_directory.df78=This ssh is not authorized to operate on this directory +i18n.event_script_does_not_exist.e726=Event script does not exist\: {} {} +i18n.async_refresh_in_progress.5550=Asynchronous refresh, please refresh the page later to view +i18n.build_status_message.42a7=The number of tasks in the current build\: {}, the number of tasks in the queue\: {} The build task waiting timed out or exceeded the maximum number of waits, the number of tasks currently running :{}/{}, cancel the execution of the current build +i18n.download_action.f26e=download +i18n.project_is_not_node_distribution_project_cannot_delete.2a5a=This item is not a node distribution item and cannot be deleted at this time +i18n.node_has_build_items_cannot_delete.a952=The node has a build item and cannot +i18n.encoding_error.b685=coding exception +i18n.node_distribution.ae68=Node distribution +i18n.two_step_verification_code_required.7e86=Please enter 2-step verification code +i18n.close_docker_exec_terminal.fec3=Close [{}] docker exec end point\: {} +i18n.ssh_error_string.6bdb=SSH error\: {} +i18n.project_id_does_not_exist.6b9b=Project ID does not exist +i18n.cluster_not_bound_to_group_for_docker_monitoring.3926=The current cluster has not been bound to a group and cannot monitor Docker asset information +i18n.no_content_to_execute.66aa=There is nothing to execute. +i18n.online_build.6f7a=online build +i18n.export_low_version_data.f1aa=1. Export the lower version data [Add --backup-h2 to the startup parameters] +i18n.docker_asset_imported.0ab4=Docker [{}] Asset Import +i18n.associated_group_required.5889=Please select an associated group +i18n.backup_directory_conflict.c13e=Backup directory conflicts\: +i18n.delete_service_success.4d73=Delete service successfully +i18n.select_monitoring_operation.3057=Please select the monitored operation +i18n.config_file_database_config_not_parsed.47b2=Database configuration information in configuration file not resolved +i18n.data_already_exists.0397=The imported data already exists +i18n.please_check_in_time.3b4f=Please check in time. +i18n.installation_success.811f=Installation successful +i18n.cluster_management.74ea=cluster management +i18n.type_field_value_error.14cf=Line {} Type field value error (Git/Svn) +i18n.migrate_data.f556=migrate data +i18n.no_script.93c4=No corresponding script +i18n.node_does_not_exist.4ce4=Node does not exist +i18n.not_super_admin.962e=You are not a super administrator and have no permissions\: -2 +i18n.ssh_does_not_exist.5bec=The corresponding SSH does not exist +i18n.file_too_large.9994=The uploaded file is too large, please select a smaller file to upload again. +i18n.backup_product.53c0=Backup Product {} {} +i18n.command_error.d0b4=Error executing command +i18n.select_alarm_contact.d02a=Please select an alarm contact +i18n.file_signature_info_not_found.83bf=No file signature information +i18n.associated_nodes_warning.64d8=The current machine is also associated with {} nodes and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.build_unknown_error.dad6=An unknown error occurred in the build +i18n.new_version_exists_download_unavailable.4ba7=There is a new version, and the download address is not available. +i18n.compare_backup_failure.303e=Comparison Empty project file backup failed +i18n.upload_success_and_restart.7bc3=Upload successful and restart +i18n.no_deletion_condition.19d0=No delete condition +i18n.get_docker_cluster_failure_with_placeholder.06cb=Failed to get {} docker cluster {} +i18n.synchronize_project_files_failed.6aa4=Failed to synchronize project file\: +i18n.parse_system_start_time_error.112c=\ Parse system start-up time error\: +i18n.service_info_incomplete_with_code1.30f4=The service information is incomplete and cannot be operated\: -1 +i18n.at_least_one_project_required.2bbd=Please select at least one item +i18n.cluster_id_conflict.45b7={} Cluster ID conflict\: {} {} +i18n.modified_value_is_empty.e4fa=The modified value is empty +i18n.cannot_delete_root_dir.fcdc=Cannot delete root directory +i18n.manual_cancel_task.e592=Manually cancel the task +i18n.unzip_exception.453e={} Decompression exception {} {} +i18n.start_executing_event_script.377e=Start executing the event script\: {} +i18n.unlock_success.4cea=Unlocked successfully +i18n.login_name_email_format_length_range.25f3=If the login name is in mailbox format, the length must be {} - {} +i18n.ssh_data_repair_not_needed.203f=The machine SSH table already has {} pieces of data, no need to repair the machine SSH data +i18n.new_package_same_as_running_package.e25a=The new package is the same as the running package. +i18n.config_file_not_exist_with_message.6a40=Configuration file does not exist {} +i18n.no_workspace_selected.33d5=No workspace was selected. +i18n.associated_data_exists_in_workspace.8827=Linked data also exists under the current workspace\: +i18n.script_not_bound_to_ssh_node.3459=The current script is not bound to an SSH node and cannot be executed using a trigger +i18n.start_executing_post_release_command.fd06=Start executing the {} post-release command +i18n.alert_content_and_status.6ed1=Alarm content\: {} Status message\: {} +i18n.incorrect_project_id.5f70=Item ID is incorrect +i18n.certificate_management.4001=Certificate Management +i18n.download_exception.e616=Abnormal download file +i18n.process_file_deletion_exception.1c6e=Handling file deletion exceptions +i18n.trigger_auto_execute_command_template_exception.4e01=Trigger an autoexecute command template exception +i18n.no_build.d163=No corresponding build +i18n.start_executing_publishing_with_file_size.5039=To start publishing, the file size that needs to be published\: {} +i18n.command_execution_exception.4ccd=Execution command exception +i18n.operation_ip.cbd4=Operate IP +i18n.auth_directory_cannot_contain_hierarchy.d6ca=Inclusion relationships cannot exist in the authorization directory. +i18n.ip_authorization_interception_exception.8130=The IP authorization interception is abnormal, please check whether the configuration is correct. +i18n.refresh_token_timeout.3291=Refresh token timeout +i18n.missing_script_library_message.be9a=The corresponding script library does not exist\: +i18n.operation_file_permission_exception.5a41=The operation file permission is abnormal, please handle it manually\: +i18n.monitoring_user_actions.f2d5=Monitor user actions +i18n.please_fill_in_user.5f52=Please fill in user. +i18n.command_template_execution_link_exception.51cf=Command template execution link exception +i18n.cleanup_succeeded.02ea=Clean up successfully +i18n.distribute_node_authorization_failure.bb92=Distributing {} node authorization failed {} +i18n.monitoring_item_not_exist.32c8=There is no monitoring item. +i18n.workspace_id_required.c967=Workspace ID cannot be empty +i18n.please_fill_in_ipv4_address.d23a=Please fill in the IPv4 address\: +i18n.publish_product.5925=release product +i18n.no_workspace_info_contact_admin_for_authorization.825f=There is no workspace information, please contact the management authorization. +i18n.cannot_disable_super_admin.6429=Cannot disable superadmin +i18n.oshi_system_process_monitoring_exception.a4da=Oshi system process monitoring exception +i18n.start_async_download.78cc=Start asynchronous download +i18n.install_id_does_not_exist.6aee=Data error, installation ID does not exist +i18n.file_format_not_supported.eac4=Unsupported file formats +i18n.restore_backup_data_success.253a=Restore backup data successfully +i18n.build_info_missing.0ab0=missing build information +i18n.file_not_exist.5091=The corresponding file does not exist +i18n.max_concurrent_shard_ids.f89c=Sharding IDs up to 100 simultaneously +i18n.handle_node_deletion_script_library_failure.4205=Failed to process {} node to delete script library {} +i18n.data_id_not_found.1b0a=No data id. +i18n.yml_config_format_error_tab.f629=The format of the yml configuration content is wrong, please check and re-operate (do not use\\\\ t (TAB) indentation)\: +i18n.build_in_progress.4d33=The current build is still in progress +i18n.execute_script_exit_code.64a8=The exit code for executing a script of type {} is\: {} +i18n.user_does_not_exist.8363=\ The user does not exist, please contact the management to create it. +i18n.no_node_found.6f85=No corresponding node found. +i18n.current_docker_not_in_cluster.f70c=The current docker {} is not in the cluster +i18n.no_available_docker_server.9fc6=\ No docker server available +i18n.active_clearance.5870=active removal +i18n.login_info_expired_re_login.6bc4=Login information has expired, log in again +i18n.release_node_project_failed.764e=Failed to release node project\: +i18n.no_corresponding_task.3be5=There is no corresponding task. +i18n.account_does_not_exist.8402=Account does not exist +i18n.scan_succeeded.7975=Scan successful +i18n.websocket_error.2bb4=Websocket error\: {} +i18n.no_management_permission2.35d4=You do not have the corresponding administrative authority\: -3 +i18n.file_in_use_stop_project_first.a2c3=The file is occupied, please stop the project first +i18n.product_file_does_not_exist.ee13=Product file does not exist +i18n.default_setting.18c6=default +i18n.address_field_required.3bc8=Line {} address field cannot be empty +i18n.get_repository_branch_failure.37cc=Failed to get warehouse branch +i18n.file_type_project_no_running_status.32a2=File type project has no running status +i18n.project_info.6674=project information +i18n.no_changes_in_repository_code_with_details.fd9f=There is no change to the warehouse code to terminate this build\: {} {} +i18n.import_save_project_exception.cdbe=Import save project exception +i18n.in_progress.b851=In progress\: +i18n.current_distribution_data_lost_record_id_not_exist.ca07=The current distribution data is missing, the record id does not exist +i18n.oauth2_binding_warning.d8f0=The current permission group is bound by oauth2 [{}] and cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.node_status_code_abnormal.4d22=[{}] The status code of the node is abnormal\: {} +i18n.data_does_not_exist_with_details.d9b5=The data does not exist\: +i18n.data_type_not_supported.fd03=Unsupported data types\: +i18n.refresh_token_failure.de7f=Failed to refresh token +i18n.no_docker_details.3343=There is no corresponding docker information. +i18n.no_permission_for_function.b63d=You do not have the corresponding function [{}] administrative permission. +i18n.auth_config.3d48=authorization configuration +i18n.no_info.e59e=No information. +i18n.repository_info.22cd=Warehouse information +i18n.data_id_cannot_be_empty.403b=Data ID cannot be empty +i18n.ssh_does_not_exist.88d7=SSH does not exist +i18n.select_operation_type.63c6=Please select an operation type +i18n.clear_success.2685=Clear successfully +i18n.illegal_access.c365=Illegal visit +i18n.certificate_has_no_aliases.3a2f=Certificate without any\: aliases +i18n.docker_asset_management.96d9=Docker Asset Management +i18n.docker_tag_incorrect.8b62=The docker tag is incorrect, no docker was found. +i18n.release_successful.f2ca=Successful release +i18n.result_dir_file_required.5f02=resultDirFile cannot be empty +i18n.status_not_distributing.6298=The current status is not in distribution. +i18n.build_record_not_exist.8186=Build record does not exist +i18n.unable_to_connect_to_docker.2bb3=Unable to connect to docker, please check the host or TLS certificate and whether the warehouse information is configured correctly. +i18n.system_configuration_directory.0f82=System configuration directory +i18n.node_authorized_config.f934=Node authorization configuration +i18n.cannot_operate_current_directory.aa3d=Cannot manipulate current directory +i18n.script_template_exists.3f86=There is also a script template under this node, which cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.do_not_reopen.f86a=Do not open repeatedly +i18n.asset_ssh_not_exist.cd43=There is no corresponding asset SSH. +i18n.package_missing_info.e277=This package has no version number, packaging time, and minimum compatible version. +i18n.too_many_attempts.d88d=Too many attempts, please come back later +i18n.incorrect_account_credentials.b2c5=The account password is incorrect. +i18n.plugin_system_log.955c=plug-in system log +i18n.cluster_created_successfully.6bf3=Cluster created successfully +i18n.data_associated_id_inconsistent.59f7=The ID of the data association is inconsistent +i18n.script_exit_code.716e=The exit code for executing the script is\: {} +i18n.ssh_connections_warning.1ddb=The current machine SSH is also associated with {} ssh, which cannot be deleted directly (you need to unbind or delete the associated data in advance to delete it) +i18n.oauth2_not_configured.9c85=oauth2 not configured +i18n.disallowed_file_extension.eb05=File suffixes that do not allow editing +i18n.no_environment_variables_found.46ad=No environment variables found +i18n.remote_download_url.011f={} Remote download url\: {} +i18n.correction_data_failure.dac6=Failed to correct data\: +i18n.no_corresponding_ssh_item.2deb=No corresponding ssh entry +i18n.private_key_file_not_exist.49ed=The configured private key file does not exist +i18n.check_docker_exception.a6d1=Check for docker exceptions +i18n.only_git_repositories_have_branch_info.d7f7=Only the GIT warehouse has branch information. +i18n.select_monitoring_person.0756=Please select a monitor +i18n.associated_data_name_not_exist_error.583e=ERROR\: Linked Data Name does not exist +i18n.file_not_exist.ea6a=There is no corresponding file +i18n.demo_account_not_support_delete.f9a6=Demo account does not support deletion. +i18n.no_command_to_execute.340b=No commands to execute +i18n.no_files_in_project_directory.108e=There are no files in the project directory, please go to the project file management to upload the files first +i18n.build_record_lost.f6a2=The build record is lost and cannot continue building +i18n.no_node.2e83=There is no corresponding node. +i18n.reconnect_plugin_failure.cc6c=Reconnect plug-in failed +i18n.installation_success_with_machine_id.1cd6=The installation is successful, and the local installation ID is\: {} +i18n.query_success.d72b=Query successful +i18n.create_success.04a6=Created successfully +i18n.send_message_failure.9621=Failed to send message +i18n.project_has_node_distribution_cannot_migrate.cc0e=The current project has node distribution and cannot be migrated directly. +i18n.please_fill_in_node_address.e77e=Please fill in the node address. +i18n.login_name_length_range.fe8d=Login length range 3-50 +i18n.file_already_exists.983d=The file already exists +i18n.repository_key_file_does_not_exist_or_is_abnormal.1d78=The warehouse key file does not exist or is abnormal, please check it and operate it. +i18n.no_corresponding_docker_info.c47a=There is no corresponding docker information. +i18n.type_not_exist_error.09de=ERROR\: Type does not exist +i18n.configure_announcement_title_or_content.7894=Please configure the title or content of the announcement. +i18n.no_any_branch.d042=There are no branches. +i18n.connection_successful.0515=Successfully connected {} {} +i18n.docker_does_not_exist_with_code.689b=The corresponding docker does not exist\: -1 +i18n.multiple_worker_nodes_exist.7110=There are still multiple worker nodes, and the last management node cannot be exited +i18n.operation_log.cda8=operation log +i18n.close_resource_failure.dc66=Failed to close resource +i18n.free_script.7760=free script +i18n.project_id_length_range.7064=Item ID Length range 2-20 (English letters, numbers and underscores) +i18n.system_cancel.3df2=System Cancellation +i18n.configure_correct_user_info_url.1276=Please configure the correct user information URL. diff --git a/modules/common/src/main/resources/i18n/messages_zh_CN.properties b/modules/common/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000000..60984660cb --- /dev/null +++ b/modules/common/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,1616 @@ +#i18n zh +#Sun Jun 16 21:53:27 CST 2024 +i18n.ssh_info_does_not_exist.5ed0=ssh 信息不存在啦 +i18n.incompatible_program_versions.5291=当前程序版本 {} 新版程序最低兼容 {} 不能直接升级 +i18n.no_projects_configured.e873=没有配置任何项目 +i18n.docker_log_thread_ended.8230=docker log 线程结束:{} {} +i18n.machine_ssh_info.8dbb=机器SSH信息 +i18n.machine_info_not_exist.3468=对应的机器信息不存在 +i18n.unsupported_method.a1de=不支持的方式 +i18n.service_name_in_cluster_required.5446=请填写集群中的服务名 +i18n.cluster_manager_node_not_found.1cd0=没有找到集群管理节点 +i18n.distribute_id_already_exists.2168=分发id已经存在啦 +i18n.node_authorized_distribution.c5d7=节点授权分发 +i18n.operation_user.4c89=操作用户 +i18n.no_uploaded_file.07ef=没有上传文件 +i18n.physical_node_pull.874e={} 物理节点拉取到 {} 个{},当前工作空间逻辑节点已经缓存 {} 个{},更新 {} 个{},删除 {} 个缓存 +i18n.request_type_not_supported_for_decoding.ea2e=当前请求类型不支持解码:{} +i18n.account_login_failed_too_many_times_locked.23b2=该账户登录失败次数过多,已被锁定{},请不要再次尝试 +i18n.no_corresponding_ssh_info.d864=没有对应的ssh信息 +i18n.no_tag_name.40ff=没有 tag name +i18n.ssh_console_connection_timeout.8eb3=ssh 控制台连接超时 +i18n.publish_task_execution_failed.b075=执行发布任务失败 +i18n.migration_completed.7a30=迁移完成,累计迁移 {} 条数据,耗时:{} +i18n.container_command_execution_exception.a14a=执行容器命令异常 +i18n.plugin_connection_failed.02a1=插件端连接失败 +i18n.auto_migrate_exist_logs.c169=自动迁移存在日志 {} -> {} +i18n.demo_account_password_change_not_supported.91f4=当前账户为演示账号,不支持修改密码 +i18n.file_type_not_supported3.f551=不支持的文件类型:{} +i18n.trigger_project_reload_event.a7dc=触发项目 reload 事件:{} +i18n.alert_contact_exception_message.1072=报警联系人异常\: +i18n.chunk_upload_exception.87c1=分片上传异常:{} {} +i18n.start_waiting_for_data_migration.e76f=开始等待数据迁移 +i18n.empty_file_or_folder_for_publish.cae8=发布的文件或者文件夹为空,不能继续发布 +i18n.repository_type_required.9414=请选择仓库类型 +i18n.demo_account_cannot_use_feature.a1a1=演示账号不能使用该功能 +i18n.async_resource_expired.2ddc=异步资源过期,需要主动关闭,{} {} +i18n.mark_cannot_be_empty.1927=标记不能为空 +i18n.get_success.fb55=获取成功 +i18n.execution_interrupted_reason.e3d7=执行中断 {} 流程,原因事件脚本中断 +i18n.forbidden_operation_time_period.86a3=【禁止操作】当前时间不在可执行的时间段内,限制时间段\: +i18n.pull_code_exception_with_cleanup.a887=拉取代码异常,已经主动清理本地仓库缓存内容,请手动重试。 +i18n.host_cannot_be_empty.644a=参数错误host不能为空 +i18n.correct_retention_days_required.d542=请填写正确的保留天数 +i18n.external_config_not_exist_or_not_configured.f24e=外置配置不存在或者未配置:{},使用默认配置 +i18n.cannot_read_tar_archive_entry.85d7=不能读取tarArchiveEntry {} +i18n.cluster_not_exist.4098=对应的集群不存在 +i18n.upload_failed_no_matching_project.b219=上传失败,没有找到对应的分发项目 +i18n.no_branches_or_tags_in_repository.76b6=仓库没有任何分支或者标签 +i18n.download_file_description.10cb=下载文件 {} {} {} +i18n.delay_build.7d62=延迟 {} 秒后开始构建 +i18n.default_cluster.38cf=默认集群 +i18n.node_and_check_project_failed.ac4b=节点与检查项目失败 +i18n.load_oauth2_config.da42=加载 oauth2 配置 :{} {} +i18n.cluster_not_bound_to_group_for_node_monitoring.1586=当前集群还未绑定分组,不能监控集群节点资产信息 +i18n.cannot_execute_error.4c29=不能执行:error +i18n.no_environment_variable.c79f=没有对应的环境变量 +i18n.restart_completed.42b8=重启完成 +i18n.node_return_info_exception.0961=节点返回信息异常,请检查节点地址是否配置正确或者代理配置是否正确 +i18n.project_associated_with_other_workspaces_message.299c=当前项目已经被其他工作空间关联,请检查确认后再操作或者使用孤独数据修正 +i18n.start_executing_distribution_package.a2cc=开始执行分发包啦,请到分发中查看详情状态 +i18n.clear_old_version_package_failed.021c=清空旧版本重新包失败 +i18n.node_online_upgrade.f144=节点在线升级 +i18n.execution_ended_with_detail.8f93=执行结束\: {} {} +i18n.ssh_file_upload_exception.5c1c=ssh上传文件异常 +i18n.authorization_exception.acc0={} 授权异常 {} +i18n.invalid_or_expired_token.bc43=token错误,或者已经失效 +i18n.no_docker_info.d685=没有对应 docker +i18n.invalid_http_proxy_address.1da1=HTTP代理地址格式不正确 +i18n.execute_dsl_script_exception.0882=执行 DSL 脚本异常:{} +i18n.package_product.bfbb=打包产物 +i18n.cancel_success.285f=取消成功 +i18n.node_name_required.ac0f=节点名称 不能为空 +i18n.class_path_and_java_ext_dirs_cp_required.7557=ClassPath、JavaExtDirsCp 模式 MainClass必填 +i18n.parameter_validation_failed.f0a1=参数验证失败 +i18n.script_template.54f2=脚本模板 +i18n.asset_cluster_and_node_mismatch.8964=资产集群和节点不匹配 +i18n.no_project_specified.0076=没有对应的项目: +i18n.connection_successful_with_message.5cf2=连接成功: +i18n.old_password_incorrect.9cf6=旧密码不正确! +i18n.script_template_execution_record.374b=脚本模版执行记录 +i18n.ssh_authorization_directory_cannot_be_root.8125=ssh 授权目录不能是根目录 +i18n.node_connection_failure_message.aacc=节点连接失败: +i18n.email_service_not_configured.3180=未配置邮箱服务不能发送邮件:{} {} +i18n.node_service_stopped_abnormal_restart.a5c0=【{}】节点的【{}】项目{}已经停止,重启操作异常 +i18n.same_distribution_project_exists.ff41=已经存在相同的分发项目\: +i18n.unbind_success.1c43=解绑成功 +i18n.project_log.2926=项目日志 +i18n.private_key_file_not_found.4ad9=私钥文件不存在: +i18n.unsupported_prefix.4f8c=当前还不支持: +i18n.file_download_failed.7983=文件下载失败: +i18n.no_corresponding_distribution_project.6dcd=没有对应的分发项目 +i18n.select_workspace_error.426e=选择工作空间错误 +i18n.build_log.7c0e=构建日志 +i18n.no_current_static_directory_permission.ed70=没有当前静态目录权限 +i18n.task_already_exists.f59a=任务已经存在啦 +i18n.start_distribution_with_count.cdc7=开始分发,需要分发 {} 个项目 +i18n.get_port_error.0698=获取端口错误 +i18n.node_communication_failure.00fb=节点通讯失败,请优先检查限制上传大小配置是否合理,或者网络连接是否被代理终端、防火墙终端等。 +i18n.static_file_management.6ac2=静态文件管理 +i18n.error_info.99ed=,错误信息: +i18n.no_corresponding_docker_asset.6f06=没有对应的 docker 资产 +i18n.build_data_not_exist.0225=构建数据不存在:{},任务自动丢弃\:{} +i18n.project_does_not_exist.3029=项目不存在 +i18n.corresponding_node_does_not_exist.72cb=当前对应的节点不存在 +i18n.upload_success_and_distribute.f446=上传成功,开始分发\! +i18n.data_table_not_supported_for_grouping.6678=当前数据表不支持分组 +i18n.node_sync_project_failed.a2a7=节点同步项目失败 +i18n.get_container_execution_result_interrupted.4a48=获取容器执行结果操作被中断\: +i18n.no_ssh_entry_found.d0e1=没有找到对应的ssh项:{} +i18n.build_runs_on_image_interrupted.00fd=构建 runsOn 镜像被中断 +i18n.incorrect_parameter_format.9efb=传入的参数格式不正确 +i18n.multiple_certificate_files_found.bee3=找到 2 个以上的证书文件 +i18n.invalid_runs_on_image_name.4b96=runsOn 镜像名称不合法 +i18n.dockerfile_path_required.69ac=请填写要执行的 Dockerfile 路径 +i18n.file_upload_mode_not_configured.b3b2=没有配置文件上传模式 +i18n.file_full_path.16cc=文件全路径:{} +i18n.container_startup_failure.532e=容器启动失败\: +i18n.workspace_name_already_exists.0f82=对应的工作空间名称已经存在啦 +i18n.environment_variables_not_found.dbd4=没有环境变量 +i18n.user_not_exist.5387=不存在对应的用户 +i18n.upload_exception.cd6c=上传异常: +i18n.alarm_contact_or_webhook_required.6c24=请选择一位报警联系人或者填写webhook +i18n.start_executing_upload_post_command.1c1b=开始执行上传后命令 +i18n.no_corresponding_file_colon.8970=没有对应文件\: +i18n.data_type_not_configured_correctly.bf16=未正确配置数据类型 +i18n.monitoring_logs.2217=监控日志 +i18n.ssh_script_batch_trigger_exception.70e1=SSH 脚本批量触发异常 +i18n.select_cluster.f8c3=请选择集群 +i18n.no_build_history.39f7=没有对应的构建历史 +i18n.docker_does_not_exist.bb41=对应的 docker 不存在 +i18n.trigger_exception.d624=触发异常 +i18n.remote_addresses_not_configured.275e=还没有配置允许的远程地址 +i18n.table_error_workspace_data.9021=表 {}[{}] 存在 {} 条错误工作空间数据 -> {} +i18n.directory_cannot_skip_levels.179e=目录不能越级: +i18n.start_building.1039=开始构建中 +i18n.normal_end.3bfe=正常结束 +i18n.empty_execution_result.9fe8=执行结果为空, +i18n.database_exception_due_to_resources.dbf1=数据库异常,可能因为服务器资源不足(内存、硬盘)等原因造成数据异常关闭。需要手动重启服务端来恢复,: +i18n.prepare_rollback.dba6=开始准备回滚:{} -> {} +i18n.monitor_node_exception.6ff1=监控 {} 节点异常 {} +i18n.add_new_success.431a=新增成功! +i18n.no_parameters_added_with_minus_one.e47d=没有添加任何参数\:-1 +i18n.welcome_join_session.1c16=欢迎加入\:{} 会话id\:{} +i18n.backup_old_package_failure_due_to_new_package_absence.b90c=备份旧程序包失败:{},因为新程序包不存在:{} +i18n.completed_count_insufficient.02e9=完成的个数不足 {}/{} +i18n.cluster_info_incomplete_with_code.246b=集群信息不完整,不能加入该集群\:-1 +i18n.log_recorder_not_enabled.5a4e=日志记录器未启用 +i18n.exit_code.ea65=执行退出码:{} +i18n.start_executing.f0b9=开始执行\: {} +i18n.authentication_config.964c=认证配置 +i18n.please_fill_in_information_and_check_validity.771a=请填写信息,并检查是否填写合法 +i18n.publish_exception.cf0b=执行发布异常 +i18n.detect_local_docker_exception.ccfc=探测本地 docker 异常 +i18n.run_status_not_configured.e959=没有配置 run.status +i18n.build_log_recorder_closed.1cc7=构建日志记录器已关闭,可能手动取消停止构建,流程\:{} +i18n.supported_comparison_operators_message.6d7a=表达式目前仅支持 \=\= 和 \!\= 比较 +i18n.no_data_found.4ffb=没有对应数据 +i18n.get_container_log_failure.915d=获取容器日志失败 +i18n.content_is_empty.3122=内容为空 +i18n.invalid_jar_file.e80a=jar 包文件不合法 +i18n.alert_contact_exception.2cec=报警联系人异常 +i18n.publish_command_length_limit.66b0=发布命令长度限制在4000字符 +i18n.unbind.6633=解绑 +i18n.asset_monitoring_thread_pool_rejected_task.222e=资产监控线程池拒绝了任务:{} +i18n.container_cluster.a5b4=容器集群 +i18n.workspace_ssh_already_exists.ccc0=对应的工作空间已经存在当前 SSH 啦 +i18n.restore_project_failed.7f7c=还原项目失败 +i18n.non_plaintext_variable_cannot_view.50ca=非明文变量不能查看 +i18n.connect_plugin_failed.e492=连接插件端失败:{} {} {} +i18n.no_nodes.17b4=没有任何节点 +i18n.scheduled_backup_log_failure.a0d7=定时备份日志失败 +i18n.folder_download_not_supported.c3b7=暂不支持下载文件夹 +i18n.user_not_exist_trigger_invalid.f375=对应的用户不存在,触发器已失效 +i18n.download_remote_file_exception.3ee0=下载远程文件异常 +i18n.only_tar_files_supported.dcc4=只支持tar文件 +i18n.file_management_center.0f5f=文件管理中心 +i18n.no_file_found.7d40=没有对应到文件 +i18n.server_captcha_generation_exception.54d0=当前服务器生成验证码异常,自动禁用验证码 +i18n.node_script_template_execution_record.704a=节点脚本模版执行记录 +i18n.response_exception_status_code.cbca={} 响应异常 状态码错误:{} {} +i18n.current_project_associated_with_online_build_and_repository.96c5=当前【项目】关联的【在线构建】关联的【仓库({})】被其他 {} 个不同发布方式的【在线构建】绑定暂不支持迁移 +i18n.current_address_no_repository.db31=当前地址不存在仓库: +i18n.dsl_not_configured.8a57=DSL 未配置运行管理或者未配置 {} 流程 +i18n.file_write_success.804a=文件写入成功 +i18n.node_address_required.71f1=节点地址不能为空 +i18n.cannot_edit_corresponding_config_file.8d10=不能编辑对应的配置文件 +i18n.auth_directory_cannot_be_under_jpom.bb67=授权目录不能位于Jpom目录下 +i18n.docker_data_repair_not_needed.0fb9=机器 DOCKER 表已经存在 {} 条数据,不需要修复机器 DOCKER 数据 +i18n.invalid_project_path.04f7=项目路径不能为空,不能为顶级目录,不能包含中文 +i18n.file_type_not_supported2.d497=不支持的文件类型: +i18n.docker_management.e7e5=Docker管理 +i18n.no_project_id_found.0f21=没有找到对应的项目id\: +i18n.agent_response_empty.cc8e=agent 端响应内容为空 +i18n.no_available_maven_versions.dffe=maven 镜像库中没有找到任何可用的 maven 版本 +i18n.static_directory_cannot_contain_relation.1a90=静态目录中不能存在包含关系: +i18n.cluster_info_incomplete.84a1=集群信息不完整,不能加入该集群 +i18n.import_success.b6d1=导入成功 +i18n.distribute_thread_exception.9725=分发线程异常 +i18n.need_handle_build_queue_count.c01e=需要处理的 {} 构建队列数:{} +i18n.config_file_not_found.310e=未找到配置文件\: +i18n.correct_verification_code_required.ff0d=请输入正确的验证码 +i18n.no_corresponding_ssh.aa68=没有对应的ssh +i18n.chunk_upload_file_exception.0dc3=分片上传文件异常 +i18n.no_execution_id.68dc=没有执行ID +i18n.command_execution_record.56d5=命令执行记录 +i18n.task_not_exist.47e9=不存在对应的任务 +i18n.configure_dsl_content.42e3=请配置 dsl 内容 +i18n.select_correct_node.1b4e=请选择正确的节点 +i18n.select_node_error.dc0f=选择节点错误 +i18n.docker_certificate_file_missing.ad46=docker 证书文件丢失 +i18n.select_correct_post_publish_script.49d2=请选择正确的发布后脚本 +i18n.distribute_management.3a2d=分发管理 +i18n.please_fill_in_correct_node_version.8483=请填入正确的 node 版本号 +i18n.sql_statement_too_long.38d6=sql 语句太长啦 +i18n.strict_mode_image_build_failure.ecea=严格模式下镜像构建失败,终止任务 +i18n.synchronization_failed.091a={} 同步失败 {} +i18n.security_warning_h2_console.4669=【安全警告】数据库账号密码使用默认的情况下不建议开启 h2 数据 web 控制台 +i18n.start_queuing_for_execution.7417=开始排队等待执行 +i18n.disallowed_download.06a3=不允许下载当前地址的文件 +i18n.cluster_id_changed.6e49=集群ID 发生变化:{} -> {} +i18n.import_success_message.2df3=导入成功(编码格式:{}),更新 {} 条数据,因为节点分发/项目副本忽略 {} 条数据 +i18n.server_captcha_available.5570=当前服务器验证码可用 +i18n.close_success.8a31=关闭成功 +i18n.no_config_file_found.9720=没有找到对应配置文件: +i18n.download_remote_file.ae84=下载远程文件 +i18n.backup_old_package.a7fc=备份旧程序包:{} +i18n.certificate_file_missing.c663=证书文件丢失 +i18n.incorrect_range_information.a41c=range 传入的信息不正确 +i18n.user_directory_not_found.cfe3=用户目录没有找到 +i18n.main_class_attribute_not_found.93e8=中没有找到对应的MainClass属性 +i18n.file_system_monitoring_exception.d4c0=文件系统监控异常: +i18n.account_name_nickname_required.b757=请输入账户昵称 +i18n.docker_info_not_found.4f64=当前集群未找到 docker 信息 +i18n.distribution_exception_saving.8285={} {} 分发异常保存 +i18n.no_main_class_found.b001=没有找到对应的MainClass\: +i18n.save_succeeded.3b10=保存成功 +i18n.select_node_to_modify.6617=请选择要修改的节 +i18n.correct_dingtalk_address_required.2b4a=请输入正确钉钉地址 +i18n.query_folder_sftp_failed.9d35=查询文件夹 SFTP 失败, +i18n.name_field_required.e0c5=第 {} 行 name 字段不能位空 +i18n.start_building_with_number_and_path.c41c=开始构建 \#{} 构建执行路径 \: {} +i18n.plugin_end_log_connection_successful.9035=连接成功:插件端日志 +i18n.start_executing_upload_pre_command.fb5c=开始执行上传前命令 +i18n.delete_container_exception.9ad8=删除容器异常 +i18n.script_library.aed1=脚本库 +i18n.query_folder_failed.3f0e=查询文件夹失败, +i18n.container_build_product_path_cannot_use_ant_pattern.ddc7=容器构建的产物路径不能使用 ant 模式 +i18n.get_folder_failure.0fda=获取文件夹失败 +i18n.system_already_initialized.743c=系统已经初始化过啦,请勿重复初始化 +i18n.trigger_auto_execute_server_script_exception.8e84=触发自动执行服务器脚本异常 +i18n.cleaned_data.0e9d={} 清理了 {}条数据 +i18n.no_implemented_feature.af80=没有实现该功能 +i18n.no_corresponding_repository.dde9=没有对应的仓库 +i18n.get_docker_cluster_info_failure_with_code.fa77=获取 docker 集群信息失败\:-1 +i18n.get_container_execution_result_failure.1828=获取容器执行结果失败 +i18n.ssh_already_exists_with_message.d284=对应的SSH已经存在啦 +i18n.build_finished.7f38=构建结束 +i18n.not_jpom_install_package.2cca=此文件不是 jpom 安装包 +i18n.republishing.131d=重新发布中 +i18n.node_id_not_found.2f9e=没有节点id +i18n.no_description.c231=\ 没有描述 +i18n.certificate_in_use_by_docker.dd63=当前证书被 docker 关联中,不能直接删除 +i18n.no_corresponding_build_record.b3b2=没有对应构建记录. +i18n.auto_delete_data.ca62=\ 自动删除 {} 表中数据 {} 条数据 +i18n.active_clearance_colon.96a6=主动清除: +i18n.correct_encoding_format_required.1f7f=请填写正确的编码格式, +i18n.notice_script_invocation_error.9002=noticeScript 调用错误 +i18n.go_plugin_version_required.ccf6=go 插件 version 不能为空 +i18n.select_correct_log_path_or_no_auth_configured.9a9b=请选择正确的日志路径,或者还没有配置授权 +i18n.service_info_incomplete.968d=服务信息不完整不能操作 +i18n.session_already_closed.8dcc=会话已经关闭啦,不能发送消息:{} +i18n.modify_or_add_data.e1f0=修改、添加数据 +i18n.alias_code_validation.8b99=别名码只能是英文、数字 +i18n.configure_run_path_property.356c=请配置运行路径属性【jpom.path】 +i18n.node_script_template_log.85e3=节点脚本模板日志 +i18n.compression_success.80b3=压缩成功 +i18n.initialize_workspace.bc97=初始化{}工作空间 +i18n.login_name_cannot_contain_chinese_and_special_characters.48a8=登录名不能包含汉字并且不能包含特殊字符 +i18n.cannot_join_cluster_as_role.01d4=不能以 {} 角色加入集群 +i18n.not_an_enumeration.8244=不是枚举 +i18n.script_not_exist.b180=对应脚本已经不存在啦 +i18n.rebuild_success.5938=重建成功 +i18n.already_offline.d3b5=已经离线啦 +i18n.project_management.4363=项目管理 +i18n.global_workspace_variable_edit_in_system_management.58d2=全局工作空间变量请到系统管理修改 +i18n.node_service_not_running.ad89=【{}】节点的【{}】项目{}已经没有运行 +i18n.node_script_template_title.4e74=节点脚本模版 +i18n.no_build_record.66a2=没有对应的构建记录 +i18n.node_exception.bca7=节点异常: +i18n.no_distribution_project.d4d1=没有分发项目 +i18n.missing_package_in_root_dir.8bab=一级目录没有%s包,请先到文件管理中上传程序的%s +i18n.exported_project_data.fd1f=导出的项目数据 +i18n.permission_distribution_config_error.e7fb=权限分发配置错误:{} {} +i18n.node_info.2dcf=节点信息 +i18n.delete_success_with_colon.d44a=删除成功\: +i18n.socket_exception.d836=socket 异常 +i18n.delete_table_data.c813=删除表 {} 中 {} 条工作空间id为:{} 的数据 +i18n.file_does_not_exist_for_download.8dd6=文件不存在,无法下载 +i18n.jpom_project_maintenance_system.7f8e=Jpom项目运维系统 +i18n.parameter_error_id_cannot_be_empty.86cc=参数错误id不能为空 +i18n.prepare_backup.7970=开始准备备份项目文件:{} {} +i18n.login_log.3fb2=登录日志 +i18n.hard_drive_monitoring_error.43e7=硬盘资源监控异常: +i18n.dockerfile_not_found_in_repository.4168=仓库目录下没有找到 Dockerfile 文件\: {} +i18n.login_name_format_incorrect.f789=登录名格式不正确(英文字母 、数字和下划线),并且长度必须 {}-{} +i18n.no_asset_machine.c77c=没有对应的资产机器 +i18n.reconnect_plugin_failure_after_upgrade.73e3=升级后重连插件端失败\: +i18n.current_system_is_windows.91d1=当前系统为:windows +i18n.execute.1a6a=执行 +i18n.unknown_error.84d3=未知: +i18n.monitor_docker_exception_detail.e334=监控 docker[{}] 异常 {} +i18n.database_username_not_configured.a048=未配置(未解析到)数据库用户名 +i18n.upload_progress_template.ac3f=上传文件进度\:{}/{} {} +i18n.mark_already_exists.0ccc=标记已存在 +i18n.docker_not_found.2a2e=\ 没有找到任何 docker。可能docker tag 填写不正确,需要为 docker 配置标签 +i18n.download_remote_file_failed.fcc3=下载远程文件失败\: +i18n.no_file_found.6f1b=没有找到 {} 文件 +i18n.distribute_result.a230=分发结果:{} +i18n.multiple_ssh_addresses_found.b3f7=SSH 地址 {} 存在多个数据,将自动合并使用 {} SSH的配置信息 +i18n.no_management_permission.fd25=您没有对应管理权限\:-2 +i18n.workspace_env_vars.f7e8=工作空间环境变量 +i18n.unsupported_mode.a3d3=暂不支持的模式: +i18n.select_node_and_project.6021=请选择节点和项目 +i18n.operation_succeeded.3313=操作成功 +i18n.waiting_to_close_process.3634=等待关闭[Process]进程:{} +i18n.url_length_exceeded.ca1c=url 长度不能超过 200 +i18n.search_result_display.d2c3=在 {} 行中搜索到并显示 {} 行 +i18n.node_id.c90a=节点id +i18n.reset_super_admin_password_success.50c6=重置超级管理员账号密码成功,登录账号为:{} 新密码为:{} +i18n.current_address_may_not_be_git.41c6=当前地址可能不是 git 仓库地址: +i18n.no_trigger_type_specified.5628=没有对应的触发器类型: +i18n.user_not_exist.4892=用户不存在 +i18n.cleanup_history_build_failed_retrying.088e=清理历史构建产物失败,已经重新尝试 +i18n.no_jpom_type_config_found.aa57=没有找到 Jpom 类型配置 +i18n.repository_info_does_not_exist.4142=仓库信息不存在 +i18n.start_migrating_h2_data_to.f478=开始迁移 h2 数据到 {} +i18n.no_run_found_in_steps.a141=steps 中没有发现任何 run , run 用于执行命令 +i18n.no_distribution_project_found.90b0=没有找到对应的分发项目 +i18n.no_branch_name.1879=没有 branch name +i18n.message_send_failed.4dbe=消息发送失败,自动移除此会话\:{} +i18n.send_failed.9ca6=发送失败 +i18n.select_run_mode.5a5d=请选择运行模式 +i18n.database_backup_label.62d8=数据库备份 +i18n.auto_start_project_failed.c7b5=自动启动项目失败:{} {} +i18n.incorrect_type_passed.d42e=传入的类型错误:{} +i18n.build_trigger_batch_exception.47d5=构建触发批量触发异常 +i18n.id_cannot_be_empty.8f2c=id 不能为空 +i18n.webhooks_invocation_error.9792=WebHooks 调用错误 +i18n.data_modification_time_format_incorrect.7ffe=数据修改时间格式不正确 {} {} +i18n.cannot_delete_recent_logs.ee19=不能删除近一天相关的日志(文件修改时间) +i18n.agent_jar_not_exist.28ac=Agent JAR包不存在 +i18n.parse_certificate_unknown_error.c43c=解析证书发生未知错误: +i18n.node_info_incomplete.3b69=对应的节点信息不完整不能继续 +i18n.ssh_import_template_csv.14fa=ssh导入模板.csv +i18n.rename_failed.0c76=重命名失败\: +i18n.build_info.224a=构建信息 +i18n.execution_command_required.1cf3=请选择执行的命令 +i18n.auto_migrate_associated_build_and_repo.0b3f=自动迁移关联的构建:{} 和 仓库:{} +i18n.system_logs.84aa=系统日志 +i18n.machines_ssh_data_fixed.1387=成功修复 {} 条机器 SSH 数据 +i18n.docker_label_required.b690=请填要执行 docker 标签 +i18n.no_matching_permission.09cf=未匹配到合适的权限不足 +i18n.data_id_does_not_exist.a566=数据id 不存在 +i18n.no_ssh_info.a8ec=没有对应 SSH 信息 +i18n.distribution_with_build_items_message.45f5=当前分发存在构建项,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.scanning_in_progress.7444=当前正在扫描中 +i18n.please_pass_body_parameter.4e5c=请传入 body 参数 +i18n.get_decrypt_distribution_failure.4feb=获取解密分发失败 +i18n.retention_days.3c7d=,保留天数:{} +i18n.no_log_info.d551=还没有日志信息 +i18n.authorized_cannot_be_reloaded.6ece=authorized 不能重复加载 +i18n.private_key_not_found_in_zip.e103=压缩包里没有找到私钥文件 +i18n.restore_authorization_data_exception.015a=恢复授权数据异常或者没有选择授权目录 +i18n.select_correct_script.ff2d=请选择正确的脚本 +i18n.get_decrypt_implementation_failure.e77a=获取解密实现失败 +i18n.publish_project_package_success.b0ce=发布项目包成功:{} +i18n.no_machine.89ed=没有对应的机器 +i18n.modify_success.69be=修改成功 +i18n.service_info_incomplete_with_code3.8612=服务信息不完整不能操作:-3 +i18n.repository_info_error.5b0a=第 {} 行 仓库信息有误 +i18n.node_not_enabled.10ef={} 节点未启用 +i18n.client_id_not_configured.ab8e=没有配置 clientId +i18n.build_product_dir_not_empty.ba06=构建产物目录不能为空,长度1-200 +i18n.trigger_token_error_or_expired.8976=触发token错误,或者已经失效 +i18n.auth_info_error.c184=授权信息错误 +i18n.download_file_error.5bcd=下载文件异常\: +i18n.rollback_ended.fb1d=执行回滚结束:{} +i18n.reload_project_exception.b566=重载项目异常 +i18n.machine_name_required.e8cf=请填写机器名称 +i18n.project_path_already_exists_as_file.a900=项目路径是一个已经存在的文件 +i18n.cumulative_filter_files.448d={} 累积过滤:{} 个文件 +i18n.decrypt_failure.ad83=解密失败 +i18n.disallowed_file_format.d6e4=不允许的文件格式 +i18n.operation_monitoring.0cd5=操作监控 +i18n.cron_expression_format_error.6dcd=cron 表达式格式不正确 +i18n.ssh_info.ebe6=SSH 信息 +i18n.id_already_exists.6208=id已经存在啦 +i18n.current_status.81c0=\ 当前还在: +i18n.project_path_no_spaces.263c=项目路径不能包含空格 +i18n.please_fill_in_address_of.9e02=请填写 %s 的 地址 +i18n.rsa_private_key_file_invalid.5f12=rsa 私钥文件不存在或者有误 +i18n.ssh_node_required.4566=请选择 ssh 节点 +i18n.folder_or_file_exists.c687=文件夹或者文件已存在 +i18n.parse_error.da6d=\ 解析错误\: +i18n.no_corresponding_docker.733e=没有对应的 docker +i18n.file_published.d1d9=文件发布 +i18n.java_ext_dirs_cp_required.1f4a=JavaExtDirsCp 模式 javaExtDirsCp必填 +i18n.correct_url_required.67a3=请填写正确的 url +i18n.pagination_error.6759=筛选的分页有问题,当前页码查询不到任何数据 +i18n.correction_success.38bc=修正成功 +i18n.project_id_in_use.1adb=当前项目id已经被正在运行的程序占用 +i18n.import_success_with_count.22b9=导入成功,添加 {} 条数据,修改 {} 条数据 +i18n.file_upload_exception.a5f6=发生文件上传异常:{} {} +i18n.please_fill_in_correct_maven_version.468c=请填入正确的 maven 版本号,可用的版本如下: +i18n.node_has_log_search_projects_cannot_delete.a388=该节点存在日志搜索(阅读)项目,不能 +i18n.copy_success.20a4=复制成功 +i18n.selected_node_required.d65a=至少选择一个节点 +i18n.docker_already_exists_in_workspace.a0de=对应工作空间已经存在对应的 docker 啦 +i18n.upload_exception_mismatch.0b25=上传异常,完成数量不匹配 +i18n.multiple_docker_addresses_found.0f82=DOCKER 地址 {} 存在多个数据,将自动合并使用 {} DOCKER 的配置信息 +i18n.download_progress.898a=当前进度:{} ,文件总大小:{},已经下载:{} +i18n.data_not_exist.41f9=对应数据不存在 +i18n.create_folder_failure.b632=创建文件夹失败(文件夹名可能已经存在啦)\: +i18n.gradle_plugin_version_required.b983=gradle 插件 version 不能为空 +i18n.node_account_required.2d90=请填写节点账号 +i18n.publish_project_package_failed.9514=发布项目包失败: +i18n.online_agent_close_not_supported.d81d=不支持在线关闭 Agent 进程 +i18n.port_configuration_check.d888=端口号是否配置正确,防火墙规则, +i18n.local_docker_exists.ec31=已经存在本地 docker 信息啦,不要重复添加: +i18n.close_client_session_exception.530a=关闭客户端回话异常 +i18n.start_execution.00d7=开始执行 +i18n.repository_info_cannot_be_empty.67d2=仓库信息不能为空 +i18n.current_operation_not_supported.3aec=不支持当前操作: +i18n.permission_distribution_config_error_class_not_found.ca67=权限分发配置错误:{} {} class not find +i18n.invalid_email_format.7526=邮箱格式不正确 +i18n.load_plugin.1f64=加载:{} 插件 +i18n.host_field_required.5c36=第 {} 行 host 字段不能位空 +i18n.container_build_interrupted.a17b=容器 build 被中断\: +i18n.upload_action.d5a7=上传 +i18n.delete_file_failure.041f=删除文件失败,请检查 +i18n.script_template_log.30cb=脚本模板日志 +i18n.no_cluster_info_found.fb40=没有找到对应的集群信息 +i18n.file_name_not_configured.39fa=没有配置fileName +i18n.ssh_asset_management.3b6c=SSH资产管理 +i18n.operation_succeeded_with_details.c773=操作成功\: +i18n.no_cache_info.fba1=没有对应的缓存信息 +i18n.save_distribution_project_failed.ceec=保存分发项目失败 +i18n.download_success_and_distribute.ae94=下载成功,开始分发\! +i18n.start_executing_process.9cb8=开始执行 {}流程 +i18n.select_monitoring_function.c6e4=请选择监控的功能 +i18n.project_path_auth_required.9e58=项目路径授权不能为空 +i18n.second_level_directory_cannot_skip_levels.c9fb=二级目录不能越级: +i18n.ssh_error_or_folder_not_configured.c087=ssh error 或者 没有配置此文件夹 +i18n.static_directory_auth_cannot_be_under_jpom.8879=静态目录授权不能位于Jpom目录下 +i18n.file_size_exceeds_limit.8272=上传文件大小超出限制 +i18n.please_fill_in_from.7268=请填写from +i18n.unsupported_system_type_with_placeholder.d5cc=不支持的系统类型:{} +i18n.configure_correct_cluster_id.5a78=请配置正确的集群Id,【jpom.clusterId】 +i18n.ssh_not_exist.2e40=不存在对应ssh +i18n.ssl_connection_failed.e26c=SSL 无法连接(请检查证书信任的地址和配置的 docker host 是否一致)\: +i18n.soft_link_project_does_not_exist.8ad2=被软链的项目已经不存在啦, +i18n.command_name_required.49fa=请输入命令名称 +i18n.service_exception.3821=服务异常: +i18n.jpom_log_not_configured.3153=没有配置 JPOM_LOG +i18n.log_reading.a4c8=日志阅读 +i18n.no_corresponding_data_or_permission.1291=没有对应的数据或者没有此数据权限 +i18n.no_log_info_or_log_file_error.2c25=还没有日志信息或者日志文件错误 +i18n.disable_monitoring.4615=禁用监控 +i18n.account_not_bound_to_any_workspace.fd61=当前账号没有绑定任何工作空间,请联系管理员处理 +i18n.initialize_user_failure.fe27=初始化用户失败 +i18n.container_cli_interrupted.b67f=容器cli被中断\: +i18n.project_data_lost.2ae3=项目数据丢失 +i18n.permission_function_not_configured_correctly.84dd=权限功能没有配置正确 {} +i18n.execution_exception_with_detail.c142=执行异常:{} +i18n.download_failed.65e2=下载失败 +i18n.remote_download_host_cannot_be_empty.cdf5=运行远程下载的 host 不能配置为空 +i18n.backup_old_package_failure_due_to_old_package_absence.53aa=备份旧程序包失败:{},因为旧程序包不存在 +i18n.no_workspace_found_for_data.ac0f=没有找到数据对应的工作空间,不能进行操作 +i18n.container_build_host_config_field_not_exist.6f61=容器构建 hostConfig 字段【{}】不存在 +i18n.clear_script_file_failed.f595=清理脚本文件失败 +i18n.import_save_failure.001a=导入第 {} 条数据保存失败\:{} +i18n.not_connected.fa55=还没有连接上 +i18n.unsupported_request_method.45d7=不被支持的请求方式 +i18n.incomplete_data_not_supported.b5d3=数据不完整,暂不支持操作 +i18n.node_delete_project_failed.534c=节点删除项目失败 +i18n.wrong_id.ab4d=错误的ID +i18n.soft_link_project_does_not_exist.4e4f=软链项目已经不存在啦 +i18n.invalid_zip_file.3092=上传的压缩包不是 Jpom [{}] 包 +i18n.publish_command_non_zero_exit_code.ea80=执行发布命令退出码非0,{} +i18n.project_path_promotion_issue.2250=项目路径存在提升目录问题 +i18n.handle_node_deletion_script_failure_duplicate.821e=处理 {} 节点删除脚本失败{} +i18n.unsupported_type.7495=不支持的类型 +i18n.submit_task_queue_success.5f5b=提交任务队列成功,当前队列数: +i18n.no_matching_process_type.b468=未匹配到合适的处理类型 +i18n.login_failed_please_enter_correct_password_and_account.03b2=登录失败,请输入正确的密码和账号,多次失败将锁定账号 +i18n.no_docker_info_no_need_to_fix_machine_data.f45e=没有任何 DOCKER 信息,不需要修复机器 DOCKER 数据 +i18n.yml_configuration_content_error.08f8=yml 配置内容错误 +i18n.cluster_info_incomplete_for_operation.ad96=集群信息不完整,不能操作 +i18n.password_cannot_be_empty.89b5=密码不能为空 +i18n.ssh_not_exist.08a2=SSH不存在 +i18n.file_merge_exception_details.e9d0=文件合并异常 {}\:{} -> {} +i18n.configure_correct_self_hosted_gitlab_address.ad50=请配置正确的自建 gitlab 地址 +i18n.unknown_jsch_log_level.6a5c=未知的 jsch 日志级别:{} +i18n.no_oauth2_found.ea74=没有找到对应的 oauth2, +i18n.no_workspace_info.75ae=没有任何工作空间信息 +i18n.do_not_reinitialize_database.9bb5=不要重复初始化数据库 +i18n.update_container_service_exception.2249=更新容器服务调用容器异常 +i18n.file_cleanup_failed.511e=清理文件失败 +i18n.cluster_not_bound_to_group_for_ssh_monitoring.c894=当前集群还未绑定分组,不能监控 SSH 资产信息 +i18n.container_log_fetch_exception.591a=拉取 容器日志异常 +i18n.project_path_conflict.8c6f=项目路径和【{}】项目冲突\:{} +i18n.cannot_delete_default_workspace.0c06=不能删除默认工作空间 +i18n.project_type_not_supported_for_startup.7bd1=当前项目类型不支持启动 +i18n.operation_failed_with_details.7280=操作失败\: +i18n.no_changes_in_repository_code.b1aa=仓库代码没有任何变动终止本次构建:{} +i18n.content_cannot_be_empty.9f0d=内容不能为空 +i18n.remote_repository_does_not_exist.7009=当前地址远程不存在仓库: +i18n.please_fill_in_host.7922=请填写host +i18n.no_type_specified.8c65=没有对应类型: +i18n.distribute_name_cannot_be_empty.0637=分发名称不能为空 +i18n.repository_password_cannot_be_empty.20b3=仓库密码不能为空 +i18n.delete_file_failure_with_full_stop.6c96=删除文件失败: +i18n.distribution_not_exist.cf8a=对应的分发不存在 +i18n.user_or_group_bindings_exist_in_workspace.d57b=当前工作空间下还绑定着用户(权限组)信息 +i18n.user_account.cbf7=用户账号 +i18n.not_logged_in.c89f=当前未登录不能操作此数据 +i18n.current_system_is_mac.0139=当前系统为:mac +i18n.docker_certificate_migrated.b3d3=docker[{}] 证书成功迁移到证书管理中 +i18n.certificate_type_not_found.6706=没有证书类型 +i18n.no_permission_to_execute_command.04d4=没有执行相关命令权限 +i18n.type_error.395f=类型错误 +i18n.node_service_stopped_successful_restart.603b=【{}】节点的【{}】项目{}已经停止,已经执行重启操作,结果成功 +i18n.git_reset_hard_failed_status_code.d818=git reset --hard失败状态码\: +i18n.no_certificate_files_found.ff6d=没有找到任何证书文件 +i18n.introducing_script_content.a55b=引入脚本内容:{}[{}] +i18n.script_cannot_be_empty.f566=脚本不能为空 +i18n.user_permission_group.52a4=用户权限组 +i18n.python3_plugin_version_required.a0ce=python3 插件 version 不能为空 +i18n.update_condition_not_found.0870=没有更新条件 +i18n.workspace_required.b3bd=请选择工作空间 +i18n.repository_id_cannot_be_empty.a42c=仓库ID不能为空 +i18n.trigger_auto_execute_ssh_command_template_exception.7451=触发自动执行SSH命令模版异常 +i18n.get_container_log_interrupted_message.83a5=获取容器日志被中断\: +i18n.start_syncing_to_file_management_center.0a03=开始同步到文件管理中心{} +i18n.delete_success.0007=删除成功 +i18n.select_correct_pre_publish_script.d230=请选择正确的发布前脚本 +i18n.greeting.5ecd=您好,Jpom +i18n.ssh_connection_failed.4719=ssh连接失败 +i18n.oauth2_redirect_failed.6dcd=跳转 oauth2 失败,{} {} +i18n.data_workspace_mismatch.ae1d=数据工作空间和操作工作空间不一致 +i18n.process_file_event_exception.e8e6=处理文件事件异常 +i18n.current_docker_cluster_has_no_management_nodes_online.56cd=当前 {} docker 集群没有管理节点在线 +i18n.machine_docker_info.9914=机器DOCKER信息 +i18n.manual_cancel_distribution.7bf6=手动取消分发 +i18n.restart_operation.5e3a=执行重启操作 +i18n.correct_information_required.5e12=请输入正确的信息 +i18n.cluster_status_code_exception.9d89=集群状态码异常:{} {} +i18n.no_h2_data_info_for_migration.5799=没有 h2 数据信息不用迁移 +i18n.publish_success.2fff=发布成功 +i18n.system_cache.c4a8=系统缓存 +i18n.distribution_machine_required.5921=请选择分发的机器 +i18n.build_call_container_exception.6e04=构建调用容器异常 +i18n.process_killed_successfully.a4c3=成功kill +i18n.build_name_not_empty.4154=构建名称不能为空 +i18n.auto_clear_data_errors.112f=自动清除数据错误 {} {} +i18n.publish_directory_is_empty.79c6=发布目录为空 +i18n.file_or_directory_not_found.f03e=文件不存在或者是目录\: +i18n.clear_file_cache_failed.5cd1=清空文件缓存失败 +i18n.file_downloading_status.c995=文件下载中: +i18n.system_IP_authorization.9c08=系统配置IP授权 +i18n.node_failed.20d5=节点失败: +i18n.project_has_node_distribution_cannot_delete.41b0=当前项目存在节点分发,不能直接删除 +i18n.oshi_system_monitoring_exception.5c1c=oshi 系统监控异常 +i18n.empty_folder_cannot_be_packed.5a75=文件夹为空,不能打包 \# +i18n.backup_file_not_exist.9628=备份文件不存在 +i18n.auto_migrate_associated_build.a060=自动迁移关联的构建:{} +i18n.node_upgrade_failed.4493=节点升级失败: +i18n.current_docker_offline.a509=当前 {} docker 不在线 +i18n.protocol_field_value_error.2b41=第 {} 行 protocol 字段值错误(http/http/ssh) +i18n.invalid_file_type.7246=上传的文件不是 zip +i18n.database_exception.4894=数据库异常 +i18n.clear_success_message.51f4=清除成功 +i18n.data_not_supported_for_sorting.5431=当前数据不支持排序 +i18n.command_non_zero_exit_code.a6e1=执行命令退出码非0,{} +i18n.table_info_configuration_error.b050=表信息配置错误 +i18n.close_session_exception_with_detail.85f0=关闭会话异常:{} +i18n.auto_delete_expired_build_history_files.723b=自动删除过期的构建历史相关文件:{} {} +i18n.build_product_file_sync_failed.0e64=构建产物文件同步到文件管理中心失败,当前文件已经存文件管理中心存在啦 +i18n.system_restart_cancel_download.444e=系统重启取消下载任务 +i18n.manager_node_not_found.df04=当前集群未找到管理节点 +i18n.yml_config_format_error_illegal_field.16ea=yml 配置内容格式有误请检查后重新操作(请检查是否有非法字段): +i18n.delete_failure_with_colon_and_full_stop.bc42=删除失败: +i18n.product_directory_cannot_skip_levels.3ad4=产物目录不能越级: +i18n.fix_null_workspace_data.4d0b=修复工作空间为 null 的数据 {} {} +i18n.soft_link_project_department_exists.fa97=软链的项目部存在 +i18n.system_admin_not_found.6f6c=没有找到系统管理员 +i18n.docker_info.00d2=docker 信息 +i18n.log_file_does_not_exist.f6c6=日志文件不存在 +i18n.record_operation_log_exception.8012=记录操作日志异常 +i18n.no_file_info.db01=没有对应的文件信息 +i18n.corresponding_file_required.57b3=请选择对应到文件 +i18n.build_command_no_delete.df52=构建命令不能包含删除命令 +i18n.file_merge_error.f32f=文件合并后异常,文件不完成可能被损坏 +i18n.script_info_not_found.bd8d=找不到对应的脚本信息 +i18n.cannot_modify_own_info.4036=不能修改自己的信息 +i18n.modify_db_password_must_restart.d08d=修改数据库密码必须重启 +i18n.ssh_already_bound_to_other_node.2d4e=对应的SSH已经被其他节点绑定啦 +i18n.not_jpom_package.ea3e=此包不是Jpom【{}】包 +i18n.auth_failed.2765=授权失败\: +i18n.node_name.b178=节点名称 +i18n.current_system_is_linux.e377=当前系统为:linux +i18n.start_publishing_file.a14e=开始发布文件 +i18n.project_id_cannot_contain_spaces.251d=项目Id不能包含空格 +i18n.checkout_version.a586=把版本:%s check out +i18n.check_docker_url_exception.4302=检查 docker url 异常 {} +i18n.execution_frequency.d014={} 秒执行一次 +i18n.parse_jar.a26e=解析jar +i18n.jpom_verification_code.5b5b=Jpom 验证码 +i18n.please_fill_in_personal_token.970a=请填写个人令牌 +i18n.parent_table_info_config_error.2f52=父级表信息配置错误, +i18n.physical_node_pull_records.99df={} 物理节点拉取到 {} 个执行记录,更新 {} 个执行记录 +i18n.login_success.71fa=登录成功 +i18n.clear_temp_file_failed_check_directory.7340=清除临时文件失败,请检查目录: +i18n.request_failed_message.9c71=请求失败\: status\: %s body\: %s headers\: %s +i18n.incorrect_repository_credentials.f1c8=仓库账号或者密码错误: +i18n.send_alert_error.cd38=发送报警信息错误 +i18n.binding_success.1974=绑定成功 +i18n.docker_not_exist.7ed8=不存在对应的 docker +i18n.monitored_directory_does_not_exist.fa4e=被监控的目录不存在忽略创建监听器:{} +i18n.no_distribution_exists.4425=不存在分发 +i18n.old_version_project_logs_exist_while_running.75ab=存在旧版项目日志但项目在运行中需要停止运行后手动迁移:{} {} +i18n.data_backup.9e26=数据备份 +i18n.ssh_monitor_execution_error.2d3c={} ssh 监控执行存在异常信息:{} +i18n.download_file_size.d4de=下载成功文件大小: +i18n.user_login_log.0c00=用户登录日志 +i18n.cluster_address_check_exception.cd92=填写的集群地址检查异常,请确认集群地址是正确的服务端地址, +i18n.no_shard_id_info.30f8=没有分片 id 信息 +i18n.socket_error.18c1=socket 错误 +i18n.synchronization_node_failure.8a2c=同步节点 {} 失败 {} +i18n.build_thread_pool_rejected_task.3bad=构建线程池拒绝了未知任务:{} +i18n.need_configure_absolute_path.f2e6=需要配置绝对路径: +i18n.auto_start_timed_task_message.9637={} 定时任务已经自动启动\:{} +i18n.protocol_type_not_supported2.e519=不支持的协议类型 +i18n.need_execute_callbacks.b708=需要执行 {} 个回调 +i18n.parameter_error_id_error.58ce=参数错误id error +i18n.unsupported_type_with_placeholder.71a2=不支持的类型:{} +i18n.publish_method_format.4622=发布的方式:{} +i18n.ssh_bound_to_node_message.7b64=当前ssh被节点绑定,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.send_alert_notification_exception.6788=发送报警通知异常 +i18n.container_build_host_config_conversion_failure.27aa=容器构建 hostConfig 参数 {} 转换失败:{} +i18n.build_task_count_and_queue_count.f0b6=当前构建中任务数:{},队列中任务数:{} {} +i18n.node_cache.d68c=节点缓存 +i18n.cluster_node_not_in_system.0645=当前集群对应的节点,不在本系统中无法退出集群 +i18n.file_search_failed.231b=文件搜索失败 +i18n.execution_ended_with_duration.a59b=执行结束 {}流程,耗时:{} +i18n.ssh_monitor_info_result.a660={} ssh 监控信息结果:{} {} +i18n.certificate_serial_number_not_found.c8d1=没有证书序列号 +i18n.configure_table_name.f6fd=请配置 table Name +i18n.project_has_monitoring_items_cannot_migrate.c7f6=当前项目存在监控项,不能直接迁移 +i18n.date_format_error.3d1c=日期格式错误\: +i18n.unexpected_exception_with_details.247d=发生异常:{} {} +i18n.static_directory_not_configured.acbc=未配置静态目录 +i18n.exported_repo_data.bac5=导出的 仓库信息 数据 +i18n.current_cluster_is_bound_to_workspace_cannot_be_deleted_directly.94c2=当前集群还被工作空间绑定,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.repository_import_template.5e2d=仓库信息导入模板.csv +i18n.execution_node_required.d747=请选择执行节点 +i18n.program_error_null_pointer.12e1=程序错误,空指针 +i18n.delete_build_cache.c7f3=删除构建缓存 +i18n.system_interruption.e37c=系统中断异常 +i18n.stop_running.1d4e=停止运行 +i18n.selected_weekday_incorrect.4cd4=选择的周几不正确 +i18n.node_has_monitoring_items_cannot_delete.0304=该节点存在监控项,不能 +i18n.existing_project_cannot_be_soft_link.aa5a=已经存在的项目不能修改为软链项目 +i18n.git_fetch_failed_status_code.5187=git fetch失败状态码\: +i18n.ssh_folder_creation_exception.6ed2=ssh创建文件夹异常 +i18n.ssh_batch_command_execution_exception.029a=ssh 批量执行命令异常 +i18n.content_type_not_supported.81a9=不支持的 contentType +i18n.cloud_server_network_issues.a865=云服务器的安全组配置等网络相关问题排查定位。 +i18n.configure_correct_redirect_url.058e=请配置正确的重定向 url +i18n.no_cache_info_with_minus_one.52f2=没有对应的缓存信息:-1 +i18n.no_node_entry_found.b1ef=没有找到对应的节点项:{} +i18n.execution_interrupted_message.2597=执行被中断:{} +i18n.build_not_exist.c2ac=不存在对应的构建 +i18n.demo_account_not_support_reset_password.a595=演示账号不支持重置密码 +i18n.build_command_execution.a55c=执行构建命令 +i18n.no_docker_info_found.6d38=没有找到对应的 docker 信息 +i18n.update_docker_machine_id_failed.063d=更新 DOCKER 表机器id 失败: +i18n.build_status_abnormal.8ca1=构建状态异常或者被取消 +i18n.node_connection_failure.896d=节点连接失败,请检查节点是否在线 +i18n.login_name_already_exists.2511=登录名已经存在 +i18n.port_error.312e=端口错误 +i18n.account_disabled.9361=账号已经被禁用,不能使用 +i18n.system_error.9417=系统错误\! +i18n.file_does_not_exist_anymore.2fab=文件已经不存在啦 +i18n.suffix_cannot_be_empty.ec72=允许编辑的文件后缀不能为空 +i18n.file_type_not_supported_with_placeholder.db22=不支持的文件类型\:{} +i18n.read_system_parameter_exception.ee72=读取系统参数异常 +i18n.check_docker_cert_exception.8042=检查 docker 证书异常 {} +i18n.ssh_terminal.ec50=SSH终端 +i18n.batch_trigger_project_exception.3c28=项目批量触发异常 +i18n.data_name_label.5a14=数据名称 +i18n.cannot_create_config_file_in_environment.55bb=当前环境不能创建配置文件 +i18n.ssh_file_manager.1482=SSH文件管理 +i18n.i18n_node_already_exists.632d=对应的节点已经存在拉 +i18n.address_not_configured.f2eb=未配置地址 +i18n.migration_success.b20d={} 迁移成功 {} 条数据 +i18n.no_corresponding_file.97b5=没有对应文件 +i18n.please_fill_in_name.52f3=请填写 名称 +i18n.unable_to_access_node_network.4e09=无法访问节点网络(未知的名称或服务),请检查主机名或者 DNS 是否可用。 +i18n.ssh_already_exists_in_workspace.569e=对应工作空间已经存在该 ssh 啦\: +i18n.update_operation_log_failed.d348=更新操作日志失败 +i18n.cron_expression_incorrect.b41a=cron 表达式不正确, +i18n.script_template_id_required.f339=请填写脚本模板id +i18n.start_rolling_back.f020=开始回滚:{} +i18n.execution_exception.b0d5=执行异常 +i18n.corresponding_function.5bb5=对应功能【{}-{}】 +i18n.plugin_parameter_incorrect.a355=插件端使用参数不正确 +i18n.operation_failed.3d94=操作失败 +i18n.start_publishing.c0b9=开始发布中 +i18n.monitor_info.f299=监控信息 +i18n.node_already_exists.28ea=对应的节点已经存在啦 +i18n.request_needs_decoding.d4d7=当前请求需要解码:{} +i18n.no_parameters_added.1721=没有添加任何参数 +i18n.repo_already_exists.38a3=已经存在对应的仓库信息啦 +i18n.publish_script_exit_code.0f69=执行发布脚本的退出码是:{} +i18n.method_not_supported.90c4=当前方法不被支持,暂时不能使用 +i18n.general_execution_exception.62e9=执行异常: +i18n.distribute_info_error_no_projects.e75f=分发信息错误,没有任何项目 +i18n.unknown_prune_type.0931=pruneType 未知 +i18n.error_sql.15ff=错误 sql\:{} +i18n.verification_method_not_configured.7358={}未配置验证方法:{} +i18n.local_git_certificate_not_supported.b395=暂时不支持本地 git 指定证书拉取代码 +i18n.unsupported_method_with_colon.eae8=不支持的方式: +i18n.current_docker_has_no_cluster_info.0b52=当前 docker 没有集群信息 +i18n.cannot_delete_self.fec9=不能删除自己 +i18n.operation_monitoring_error.8036=执行操作监控错误 +i18n.alias_or_token_error.d5c6=别名或者token错误,或者已经失效 +i18n.delete_old_package.ca95=删除旧程序包:{} +i18n.table_info_configuration_error_message.6452=表信息配置错误, +i18n.login_name_already_taken.5b46=当前登录名已经被系统占用 +i18n.auto_backup_h2_database.2ed0=自动备份 h2 数据库文件,备份文件位于:{} +i18n.verification_code_disabled.349b=验证码已禁用 +i18n.unsupported_type_with_colon2.7de2=不支持的类型: +i18n.node_service_stopped_failed_restart.4307=【{}】节点的【{}】项目{}已经停止,已经执行重启操作,结果失败 +i18n.associated_workspace.885b=所属工作空间 +i18n.auto_clear_machine_node_stats_logs.5279=自动清理 {} 条机器节点统计日志 +i18n.unsupported_mode.501d=不支持的模式 +i18n.missing_script_message.af89=找不到对应的脚本 +i18n.start_migrating.20d6=开始迁移 {} {} +i18n.incompatible_database_version.8f7b=数据库版本不兼容,需要处理跨版本升级。 +i18n.admin_account_required.31e0=系统中的系统管理员账号数量必须存在一个以上 +i18n.process_does_not_exist.4e39=流程不存在 +i18n.no_ssh_commands_to_execute_after_publish.89ba=没有需要执行发布后的ssh命令 +i18n.correct_remote_address_required.0ce1=请输入正确的远程地址 +i18n.comparison_data_not_found.413e=没有要对比的数据 +i18n.invalid_remote_address_format.7f32=配置的远程地址不规范,请重新填写: +i18n.send_success.9db9=发送成功 +i18n.docker_console_connection_timeout.b2c7=docker 控制台连接超时 +i18n.start_distribution.bce5=开始分发 +i18n.heartbeat_message_forwarding_failed.89cc=心跳消息转发失败 {} {} +i18n.asset_machine_node_statistics.4a03=资产机器节点统计 +i18n.repository_does_not_exist.3cdb=仓库不存在 +i18n.select_correct_build_method.84c4=请选择正确的构建方式 +i18n.project_file_manager.c8cb=项目文件管理 +i18n.no_ssh_info_no_need_to_fix_machine_data.0946=没有任何ssh信息,不需要修复机器 SSH 数据 +i18n.data_id_already_exists.28b6=数据Id已经存在啦:{} \: {} +i18n.event_script_interrupted.8c79=事件脚本中断: +i18n.configure_user_notification.250d=请配置用户通知 +i18n.operation_status_code.8231=操作状态码 +i18n.agent_jar_damaged.74a8=Agent JAR 损坏请重新上传, +i18n.read_error.7fa5=读取错误 +i18n.select_workspace_to_modify.ac87=请选择要修改的工作空间 +i18n.network_resource_monitoring_error.4ede=网卡资源监控异常: +i18n.decode_failure.822e=解码失败 +i18n.restart_failed.f92a=重启失败 +i18n.login_name_already_taken_message.b01f=当前登录名已经被系统占用啦 +i18n.forbidden_operation_time.d83d=【禁止操作】当前时段禁止执行 +i18n.log_file_not_found.7f2e=没有日志文件\: +i18n.protocol_type_not_supported.7a66=不支持到协议类型 +i18n.execution_result_file_not_found_in_container.cf18=容器中没有找到执行结果文件\: {} +i18n.execution_exception_with_flow.6d4b=执行异常[{}]流程:{} +i18n.trigger_success.f9d1=触发成功 +i18n.start_pulling.57ab=开始拉取 +i18n.publish_task_execution_exception.c296=执行发布任务异常 +i18n.no_node_specified.fa3d=没有对应的节点: +i18n.no_corresponding_command.165e=没有对应对命令 +i18n.exclusion_success.7d46=剔除成功 +i18n.oauth2_not_enabled.c8b7=没有开启此 {} oauth2 +i18n.cannot_delete_online_cluster.11ad=不能删除在线的集群 +i18n.select_file.9feb=请选择文件 +i18n.no_corresponding_script_info_or_global_script_selected.765b=没有对应到脚本信息或者选择全局脚本 +i18n.project_has_build_items_cannot_delete.c2df=当前项目存在构建项,不能直接删除 +i18n.data_id_label.81b6=数据id +i18n.key_field_not_configured.7b22=没有配置 KEY 字段, +i18n.upgrade_duration_message.bab4=升级(重启)中大约需要30秒~2分钟左右 +i18n.git_submodule_update_failed_status_code.2218=git submodule update 失败状态码\: +i18n.project_id_not_found.b87e=没有项目id +i18n.operation_type.de9c=操作类型 +i18n.project_has_logs_cannot_migrate.2e0e=当前项目存在日志阅读,不能直接迁移 +i18n.delete_failure_with_colon.b429=删除失败\: +i18n.tag_cannot_contain_colon.f9ae=标签不能包含 : +i18n.distribute_id_already_exists_globally.6478=分发id已经存在啦,分发id需要全局唯一 +i18n.import_exception.04b6=导入第 {} 条数据异常\:{} +i18n.script_template_log2.6b2c=脚本模版日志 +i18n.id_is_empty.3bbf=id 为空 +i18n.mfa_incorrect_code.8783=\ mfa 验证码不正确 +i18n.parameter_parsing_exception.0056=参数解析异常\:{} +i18n.file_upload_failure_due_to_missing_chunks.1865=文件上传失败,存在分片丢失的情况, {} \!\= {} +i18n.root_path.1396=根路径 +i18n.node_not_exist.0027=不存在对应的节点 +i18n.public_key_or_private_key_does_not_exist.dc0d=公钥或者私钥不存在 +i18n.no_user_info.0355=没有对应的用户信息 +i18n.correct_verification_code2_required.df13=请输入正确验证码 +i18n.please_fill_in_correct_python3_version.abb1=请填入正确的 python3 版本号 +i18n.incorrect_account_credentials_or_unsupported_auth.1ef9=账号密码不正确或者不支持的身份验证, +i18n.file_already_exists.d60c=当前文件已经存在啦,请勿重复上传 +i18n.start_executing_pre_release_command.6c7e=开始执行 {} 发布前命令 +i18n.publish_to_ssh_directory_required.56a6=请输入发布到ssh中的目录 +i18n.config_path_exceeds_length_limit.f684=配置路径超过{}长度限制\:{} +i18n.cluster_response_incorrect.c08a=集群响应信息不正确,请确认集群地址是正确的服务端地址 +i18n.project_soft_linked_by.8556=项目被{}软链中 +i18n.monitor_docker_timeout.b03b=监控 docker[{}] 超时 {} +i18n.log_recorder_error_message.ee3e=日志记录器被关闭/或者未启用 +i18n.distribution_project_required.2560=请选择分发项目 +i18n.test_result.8441=测试结果:{} {} +i18n.environment_variable.3867=环境变量 +i18n.unsupported_mode_with_colon.c6de=不支持的模式: +i18n.message_conversion_exception.cce8=消息转换异常 +i18n.oauth2_login_failure.3841=OAuth 2 登录失败,平台账号不符合本系统要求 +i18n.node_did_not_pull_anything.8af5=节点没有拉取到任何 +i18n.project_has_monitoring_items_cannot_delete.c9a3=当前项目存在监控项,不能直接删除 +i18n.compression_type_not_supported.9dea=不支持的压缩类型, +i18n.cache_plugin_path_required.2093=cache 插件 path 不能为空 +i18n.data_file_content_error.e86f=数据文件内容错误,请检查文件是否被非法修改: +i18n.script_template_not_exist.e05f=脚本模版不存在\: +i18n.parameter_error_path_error.f482=参数错误path错误 +i18n.execute_event_script_error.7c69=执行事件脚本错误 +i18n.associated_group2_required.bd05=请选择关联的分组 +i18n.gradle_plugin_depends_on_java.2bb3=gradle 插件依赖 java , 使用 gradle 插件必须优先引入 java 插件 +i18n.no_server_management_permission.ee19=您没有服务端管理权限\:-2 +i18n.no_static_directory_configured.d3c0=当前没有配置静态目录,自动取消定时任务 +i18n.distribute_log.c612=分发日志 +i18n.trigger_result.364e=[{}]-{}触发器结果:{} +i18n.no_parameters_added_with_minus_two.a7cf=没有添加任何参数\:-2 +i18n.distribute_id_requirements.9c63=分发id 不能为空并且长度在2-20(英文字母 、数字和下划线) +i18n.ssh_rename_failed_exception.94aa=ssh重命名失败异常 +i18n.repository_authorization_error.4f50=仓库授权信息错误 +i18n.associated_data_and_exist_in_workspace.5fa7=当前工作空间下还存在关联:{} 和 {} 数据 +i18n.delete_log_file_failure.bf0b=删除日志文件失败 +i18n.content_format_error_with_detail.c846=内容格式错误,请检查修正\: +i18n.project_log_storage_path_required.d0bb=请填写的项目日志存储路径,或者还没有配置授权 +i18n.forbidden_operation_range.247f=【禁止操作】{} {} 至 {} +i18n.please_do_not_delete_this_file.0a7f=请勿删除此文件,删除后关联 id 将失效 +i18n.strict_execution_mode_event_script_error.c82a=严格执行模式,事件脚本返回状态码异常, +i18n.file_missing_cannot_publish.3818=当前文件丢失不能执行发布任务 +i18n.log_file_does_not_exist_or_error.a0e7=日志文件不存在或者错误 +i18n.build_task_waiting.e303=构建任务继续等待\:{} {} +i18n.no_manager_node_found.5934=当前集群未找到任何管理节点 +i18n.file_transfer_exception.bda6=转发文件异常 +i18n.upload_progress_with_units.44ad=上传文件进度\:{} {}/{} {} +i18n.select_node.f8a6=请选择节点 +i18n.ssh_terminal_execution_log.58f1=ssh 终端执行日志 +i18n.push_image_interrupted.6377=push image 被中断\: +i18n.project_monitor.d2ff=项目监控 +i18n.default_value.1aa9={} [默认] +i18n.read_additional_variables.5eb0=读取附加变量:{} {} +i18n.node_transfer_info_encoding_exception.12c8=节点传输信息编码异常\: +i18n.user_operation_log.2233=用户操作日志 +i18n.ssh_terminal_log.775f=SSH终端日志 +i18n.query_data_error.45e7=查询数据错误 +i18n.handle_node_deletion_script_failure.071b=处理 {} 节点删除脚本失败 {} +i18n.build_image_call_container_exception.7e13=构建镜像调用容器异常 +i18n.machines_docker_data_fixed.af8a=成功修复 {} 条机器 DOCKER 数据 +i18n.project_name.31ec=项目 +i18n.load_success.154e=加载成功 +i18n.start_checking_backup_project_files.baa7=开始检查备份项目文件:{} {} +i18n.no_script_template_specified.7d14=没有对应脚本模板\: +i18n.push_image_container_exception.2090=推送镜像调用容器异常 +i18n.ssh_with_build_items_message.0f6d=当前ssh存在构建项,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.ssh_item_distribution_required.2884=请选择分发SSH项 +i18n.file_name_already_exists.0d4e=文件名已经存在拉 +i18n.waiting_to_start.b267=等待开始\: +i18n.no_corresponding_data.4703=没有对应的数据 +i18n.info_to_retrieve_not_found.96d7=没有要获取的信息 +i18n.http_proxy_address_unavailable.b3f2=HTTP代理地址不可用\: +i18n.maven_plugin_depends_on_java.23f8=maven 插件依赖 java , 使用 maven 插件必须优先引入 java 插件 +i18n.join_beta_program.5c1f=是否加入 beta 计划 +i18n.cluster_does_not_exist.97a4=当前集群不存在 +i18n.publish_command_contains_forbidden_command.097d=发布命令中包含禁止执行的命令 +i18n.close_exception.5b86=关闭异常 +i18n.no_project_info_found.725a=没有找到对应的项目信息 +i18n.upgrade_database_process.e604=升级数据库流程: +i18n.no_type.9153=没有对应的类型 +i18n.user_operation_alarm.15b9=用户操作报警 +i18n.start_executing_build_task.a5ac=开始执行构建任务,任务等待时间:{} +i18n.event_type_not_supported.e9c3=不支持的事件类型:{} +i18n.user_directory_not_found_private_key_info.6ce4=用户目录没有找到私钥信息 +i18n.upload_progress_message_format.b91c=上传文件进度:{}[{}/{}] {}/{} {} +i18n.download_failed_generic.be4f=下载文件失败 +i18n.oshi_network_card_monitoring_exception.6d41=oshi 网卡资源监控异常 +i18n.connection_successful.b331=连接成功 +i18n.incomplete_upload_info_now_slice.34aa=上传信息不完成:nowSlice +i18n.account_locked_cannot_change_password.d6ab=当前账号被锁定中,不能修改密码 +i18n.no_publish_distribution_related_data_id.a077=没有发布分发对应关联数据ID +i18n.build_failed.a79a=构建失败\: +i18n.delete_ssh_temp_file_failure.6e5f=删除 ssh 临时文件失败 +i18n.branch_required.5095=请选择分支 +i18n.trigger_token.abe6=触发器 token +i18n.user_binding_warning.16b0=当前权限组还绑定用户,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.account_already_bound_to_mfa.5122=当前账号已经绑定 mfa 啦 +i18n.compare_files_result.bec4=对比文件结果,产物文件 {} 个、需要上传 {} 个 +i18n.content_format_error.ce15=内容格式错误,请检查修正 +i18n.login_name_cannot_be_empty.9a99=登录名不能为空 +i18n.correct_host_required.8c49=请填写正确的 host +i18n.select_correct_project_path_or_no_auth_configured.366a=请选择正确的项目路径,或者还没有配置授权 +i18n.need_initialize_system.fb62=需要初始化系统 +i18n.not_logged_in.6605=没有登录 +i18n.super_admin_cannot_reset_password_this_way.0761=超级管理员不能通过此方式重置密码 +i18n.old_and_new_passwords_match.55b4=新旧密码一致 +i18n.protocol_required.b4f8=请选择协议 +i18n.incorrect_ip_address.b872=ip 地址信息不正确 +i18n.main_class_not_found.8a12=没有找到运行的主类 +i18n.data_creation_time_format_incorrect.7772=数据创建时间格式不正确 {} {} +i18n.delete_backup_data_file_failure.2ebf=删除备份数据文件失败 +i18n.user_custom_workspace.ef93=用户自定义工作空间 +i18n.editable_suffixes_not_configured.5b41=没有配置可允许编辑的后缀 +i18n.invalid_repository_info.b4ad=无效的仓库信息 +i18n.login_JPOM.0de6=登录JPOM +i18n.ssh_does_not_exist_with_message.de6c=对应的 ssh 不存在 +i18n.not_running.4f8a=未运行 +i18n.no_get_id_method.2a65=没有 getId 方法 +i18n.project_id_keyword_occupied.1cae=项目id {} 关键词被系统占用 +i18n.delete_log_file_failure_with_colon.d867=删除日志文件失败\: +i18n.manual_cancel.8464=手动取消 +i18n.super_admin_mfa_verification_disabled.b97d=成功关闭超级管理员账号 mfa 验证:{} +i18n.prepare_to_build.1830=准备构建 +i18n.unknown_exception_on_pull_code.2b2e=拉取代码发生未知异常建议清除构建重新操作: +i18n.close_connection_exception.c855=关闭连接异常 +i18n.delete_action.2f4a=删除 +i18n.restart_self_exception.85b7=重启自身异常 +i18n.configure_monitoring_interval.9741=请配置监控周期 +i18n.build_finished_duration.7f7c=构建结束-累计耗时\:{} +i18n.handle_node_sync_script_failure.e99f=处理 {} 节点同步脚本失败 {} +i18n.project_disabled.f8b3=当前项目被禁用 +i18n.certificate_already_exists.adf9=当前证书已经存在啦(系统全局范围内) +i18n.oshi_file_system_monitoring_exception.dc24=oshi 文件系统资源监控异常 +i18n.image_cannot_be_empty.1600=镜像不能为空 +i18n.no_corresponding_node_info.cd24=没有对应到节点信息 +i18n.node_plugin_version_required.2318=node 插件 version 不能为空 +i18n.docker_exec_terminal_process_ended.c734=[{}] docker exec 终端进程结束 +i18n.node_migration_project_failure.d5ff=节点迁移项目失败 +i18n.cannot_cancel_super_admin_permissions.99b5=不能取消超级管理员的权限 +i18n.distribute_node_configuration_failure.8146=分发 {} 节点配置失败 {} +i18n.file_upload_failed.462e=上传文件失败: +i18n.node_service_resumed_normal_operation.2cbd=【{}】节点的【{}】项目{}已经恢复正常运行 +i18n.file_type_no_restart.0977=file 类型项目没有 restart +i18n.current_distribution_data_lost.f9f8=当前分发数据丢失 +i18n.pull_repository_code.3f51=拉取仓库代码 +i18n.deletion_success_message.4359=删除成功! +i18n.database_mode_config_missing.ae5d=数据库Mode配置缺失 +i18n.rsa_private_key_file_error.b687=第 {} 行 rsa 私钥文件不存在或者有误 +i18n.protocol_not_supported.b906=不支持的 protocol +i18n.force_unbind_succeeded.5bfd=强制解绑成功 +i18n.unexpected_exception.2b52=发生异常 +i18n.check_email_error.636c=检查邮箱信息错误:{} +i18n.initialization_success.4725=初始化成功 +i18n.running_status.d679=运行中 +i18n.update_success.55aa=更新成功 +i18n.error_message.483d=未找到脚本库信息\:{},请检查引用标记是否正确或者脚本是否被删除 +i18n.check_git_client_exception.42a3=检查 git 客户端异常 +i18n.initialize_database_failure.2ef9=初始化数据库失败 {} +i18n.docker_cluster_associated_workspaces_message.5520=当前 docker 还关联{}个 工作空间 docker 集群,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.at_least_one_node_required.a290=请至少选择一个节点 +i18n.reset_failed.5281=重置失败: +i18n.no_current_data_permission.17d7=没有当前数据权限,需要管理员或者数据创建人才操作该数据 +i18n.database_not_initialized.e5e7=还没有初始化数据库 +i18n.project_path_occupied.cddd=当前项目路径已经被【{}】占用,请检查 +i18n.configuration_modification_not_supported.5872=当前环境下不支持在线修改配置文件 +i18n.non_dsl_project_unsupported_operation.5c09=非 DSL 项目不支持此操作 +i18n.contact_does_not_exist.3369=联系人不存在 +i18n.check_cluster_info_exception.7b0c=检查集群信息异常 +i18n.docker_cluster_info.a2eb=docker 集群信息 +i18n.line_number_error.c65d=行号错误 +i18n.node_machine_table_exists_no_need_to_fix.2625=节点机器表已经存在 {} 条数据,不需要修复机器数据 +i18n.execution_exception_message.ef79=执行异常\: +i18n.auto_detect_local_docker_and_add.af72=自动探测到本地 docker 并且自动添加: +i18n.database_event_execution_ended.690b=数据库 {} 事件执行结束,:{} +i18n.build_history.a05c=构建历史 +i18n.machine_installation_id.d0b9=本机安装 ID 为:{} +i18n.no_available_docker_server.6aaa={} 没有可用的 docker server +i18n.workspace_label.98d6=工作空间 +i18n.credential_cannot_be_empty.d055=凭证不能为空 +i18n.file_modification_event.5bc2=文件修改事件:{} {} +i18n.no_corresponding_build_record_ignore_deletion.86a0=没有对应构建记录,忽略删除 +i18n.cannot_configure_root_path.d86e=不能配置根路径: +i18n.completed_and_successful_count_insufficient.92fa=完成并成功的个数不足 {}/{} +i18n.get_project_info_failure.ddff=获取项目信息失败\: +i18n.name_required.856d=请填写名称 +i18n.build_source.2ef9=构建来源, +i18n.joined_beta_program.c4e2=成功加入 beta 计划 +i18n.recover_abnormal_data.9adf={} 恢复 {} 条异常数据 +i18n.file_name_error_message.7a25=文件名不能包含/ +i18n.config_file_not_exist.09dd=配置文件不存在 +i18n.parent_task_not_found.bac1=没有找到父级任务 +i18n.static_file_scanning_disabled.2b2b=未开启静态文件扫描 +i18n.no_resource_found.dc22=没有找到对应的资源 +i18n.container_name_cannot_be_empty.14b1=容器名称不能为空 +i18n.token_parse_failed.cadf=token 解析失败: +i18n.distribute_exception.da82=分发异常 +i18n.script_content_cannot_be_empty.49be=脚本内容不能为空 +i18n.need_execute_pre_events.b848=需要执行 {} 个前置事件 +i18n.cluster_binding_success.eb7e=集群绑定成功 +i18n.port_field_required_or_incorrect.8426=第 {} 行 port 字段不能位空或者不正确 +i18n.delete_failure.acf0=删除失败 +i18n.prepare_to_migrate_data.f251=准备迁移数据 +i18n.ignore_log_record.48f5=忽略记录日志 {} +i18n.user_management.7d94=用户管理 +i18n.need_handle_build_micro_queue_count.3010=需要处理构建微队列数:{} +i18n.temporary_result_file_does_not_exist.1c7e=临时结果文件不存在\: {} +i18n.file_directory_too_long.c101=文件目录长度超过 500 ,自动忽略此类文件:{} +i18n.command_content_required.6005=请输入命令内容 +i18n.system_parameters.c7b0=系统参数 +i18n.node_address_not_found.f955=没有节点地址,不能继续操作 +i18n.verification_code_incorrect_retry.d88d=验证码不正确,请重新输入 +i18n.git_installation_location.7984=git安装位置:{} +i18n.scheduled_task_exception.f077=定时任务异常 {} +i18n.monitor_docker_exception.e326=监控 docker[{}] 异常 +i18n.restart_result.253f=重启结果: +i18n.start_building_with_thread_execution.83cd=开始构建,构建线程执行 +i18n.task_ended_successfully.e176=任务正常结束 +i18n.non_existent_build_product.1df4={} 不存在,处理构建产物失败 +i18n.current_not_supported.78b7=当前不支持: +i18n.wait_for_seconds.ff7b=执行等待 {} 秒 +i18n.no_user.3b69=没有对应的用户 +i18n.unknown_script_template_or_workspace.27f1=脚本模板或者工作空间未知 +i18n.send_message_failure_prefix.6f8c=发送消息失败\: +i18n.select_correct_ssh.aa93=请选择正确的ssh +i18n.synchronization_failed.d610=同步失败 +i18n.client_secret_not_configured.6923=没有配置 clientSecret +i18n.workspace_not_exist.a6fd=不存在对应的工作空间 +i18n.no_user_specified.6650=没有对应的用户: +i18n.backup_database.9524=备份数据库 +i18n.delete_project_file_failure.f007=删除项目文件失败\: +i18n.incorrect_certificate_info.aee1=填写的证书信息错误 +i18n.build_command_not_empty.2e37=构建命令不能为空 +i18n.auto_migrate_exist_backup_logs.dc33=自动迁移存在备份日志 {} -> {} +i18n.close_session_exception.3491=关闭会话异常 +i18n.login_failure_O_auth2_message.3e91=登录失败(OAuth2),请联系管理员! +i18n.log_retention_days.99d1=统计日志保留天数 {} +i18n.env_must_be_map_type.f8ad=env 必须是 map 类型 +i18n.upgrade_failure.4ae2=升级失败 +i18n.batch_trigger_script_exception.8fb4=服务端脚本批量触发异常 +i18n.no_branch_or_tag_message.8ae3=没有 {} 分支/标签 +i18n.general_error_message.728a=啊哦,好像哪里出错了,请稍候再试试吧~ +i18n.service_info_incomplete_with_code2.e9ca=服务信息不完整不能操作:-2 +i18n.ignored_operation.edee=忽略的操作:{} +i18n.monitor_name_cannot_be_empty.514a=监控名称不能为空 +i18n.get_project_pid_failure.17b0=获取项目pid 失败 +i18n.please_fill_in_steps.229d=请填写 steps +i18n.distribute_success.c689=分发成功 +i18n.unable_to_get_container_execution_result_file.7b2c=无法获取容器执行结果文件 +i18n.import_data.8ef8=导入数据 +i18n.unsupported_mode_with_script_log.6a7a=不支持的模式,script log +i18n.ssh_connection_failed.74ab=ssh连接失败,请检查用户名、密码、host、端口等填写是否正确,超时时间是否合理: +i18n.node_name_required.5bdf=请填写节点名称 +i18n.client_terminated_connection.6886=客户端终止连接:{} +i18n.close_thread_pool.4cd9=关闭 {} 线程池 +i18n.no_corresponding_folder.621f=没有对应文件夹 +i18n.no_online_manager_node_found.05d7=当前集群未找到在线的管理节点 +i18n.command_execution_failed.90ef=执行命令失败 +i18n.monitor_ssh_timeout.59fd=监控 ssh[{}] 超时 {} +i18n.execution_completed.24a1=执行完毕\: +i18n.close_project_failure.a1d2=关闭项目失败: +i18n.select_folder_to_compress.915f=请选择文件夹进行压缩 +i18n.config_file_not_found.fc87=均未找到配置文件 +i18n.update_ssh_machine_id_failed.bd24=更新 SSH 表机器id 失败: +i18n.select_pull_code_protocol.fc24=请选择拉取代码的协议 +i18n.auth_directory_cannot_be_empty.21ba=授权目录不能为空 +i18n.status_not_in_progress.f410=当前状态不在进行中, +i18n.image_tag_required.92cf=请填写镜像标签 +i18n.privacy_variable_cannot_trigger.dbc9=隐私变量不能生成触发器 +i18n.no_error_data_in_table.3092=当前表没有错误数据 +i18n.update_restore_data.1b0b=更新还原数据:{} +i18n.script_template.1f77=脚本模版 +i18n.certificate_info_incorrect.a950=证书信息不正确,证书压缩包里面必须包含:ca.pem、key.pem、cert.pem +i18n.trim_completed_with_recovered_space.0463=修剪完成,总回收空间: +i18n.default_workspace_cannot_delete.18b4=系统默认的工作空间,不能删除 +i18n.synchronization_node_failure_with_details.8660=同步节点{}失败\:{} +i18n.email_verification_failed.5863=验证邮箱信息失败,请检查配置的邮箱信息。端口号、授权码等。 +i18n.permission_group.ea59=权限分组 +i18n.node_exception_null_pointer.d408=节点异常,空指针 +i18n.node_system_logs.3ac9=节点系统日志 +i18n.parameter_value_required.3a29=请填写参数值 +i18n.node_connection_lost.b6c7=节点连接丢失或者还没有连接上 +i18n.associated_data_lost_error.becb=ERROR\:关联数据丢失 +i18n.ssh_modify_permission_error.0cd3=ssh修改文件权限异常...\: {} {} +i18n.please_fill_in_password.455f=请填写pass +i18n.fuzzy_match_files.139d={} 模糊匹配到 {} 个文件 +i18n.no_distribution_id_found.8df2=没有找到对应的分发id +i18n.command_script_not_found_in_service.25ac=当前服务中没有命令脚本:{}.{} +i18n.search_project.7e9b=搜索项目 +i18n.machine_asset_management.36ea=机器资产管理 +i18n.no_matching_data_found.fe9d=未找到匹配的数据 +i18n.project_console.3a94=项目控制台 +i18n.script_template_not_exist.1d5b=对应的脚本模版已经不存在拉 +i18n.subnet_mask_incorrect.6c27=子掩码不正确: +i18n.no_implemented_publish_distribution.fcf8=没有实现的发布分发:{} +i18n.h2_connection_successful.11f3=成功连接 H2 ,开始尝试自动备份 +i18n.container_build_exception.a98f=容器构建异常:{} -> {} +i18n.import_project_template_csv.c6f1=项目导入模板.csv +i18n.handle_message_exception_with_colon.56f0=处理消息异常: +i18n.node_already_exists_in_workspace.9499=对应工作空间已经存在该节点啦\: +i18n.plugin_not_initialized.3ea9=对应的插件端还没有被初始化 +i18n.no_node_info.6366=没有任何节点信息 +i18n.associated_ssh_node_contains_nonexistent_node.c7f5=关联 SSH 节点包含不存在的节点 +i18n.node_statistics.b4e1=节点统计 +i18n.user_workspace_relation_table.851e=用户(权限组)工作空间关系表 +i18n.dsl_info_not_configured.3487=未配置 dsl 信息(项目信息错误) +i18n.migration_success_message.e546=项目迁移成功:{} | {} +i18n.node_connection_failed.8497={} 节点连接失败 {} +i18n.ssh_management.9e0f=SSH管理 +i18n.synchronization_script_exception.9c70=同步脚本异常 +i18n.previous_node_distribution_failure.d556=前一个节点分发失败,取消分发 +i18n.cleanup_token_exception.760e=执行清理 token[{}] 异常 +i18n.incorrect_node_info_node_does_not_exist.2fd8=节点信息不正确,对应对节点不存在 +i18n.plugin_not_found.a6e5=对应找到对应到插件: +i18n.file_storage_center.6acf=文件存储中心 +i18n.system_administrator.181f=系统管理员 +i18n.parameter.3d0a=参数 +i18n.ignore_execution_event_script.8872=忽略执行事件脚本 {} {} {} +i18n.login_info_expired_please_re_login.fbbc=登录信息已经过期请重新登录 +i18n.clear_build_product_failed.edd4=清除构建产物失败 +i18n.prepare_to_delete_current_database_file.1e6a=准备删除当前数据库文件 +i18n.distribution_in_progress.c3ae=当前还在分发中,请等待分发结束 +i18n.initialization_failure.19e9=初始化失败\: +i18n.project_exists.f4e0=该节点下还存在项目,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.modify_service_success.bd75=修改服务成功 +i18n.database_connection_not_configured.c80e=没有配置数据库连接 +i18n.clear_temp_file_failed_manually.0dad=清除临时文件失败,请手动清理: +i18n.reset_log_failure.b3d3=重置日志失败 +i18n.type_field_required.7637=第 {} 行 type 字段不能位空 +i18n.migration_target_workspace_node_mismatch.d9cf=要迁移到的目标工作空间和节点不一致 +i18n.start_deleting_files.210c=开始删除 {} 文件 {} +i18n.running_project_cannot_change_path.5888=正在运行的项目不能修改路径 +i18n.download_success.5094=下载成功 +i18n.please_fill_in_correct_gradle_version.6e19=请填入正确的 gradle 版本号 +i18n.get_node_monitoring_info_failure.595a=获取节点监控信息失败 +i18n.docker_authorization_failed.8ede=docker 授权失败\:{} +i18n.supported_java_plugin_versions.bd70=目前java 插件支持的版本\: %s +i18n.delete_success_with_cleanup.6155=删除成功,并且清理历史构建产物成功 +i18n.cluster_info.32e0=集群信息 +i18n.file_type_not_supported.ae5d=不支持的文件类型\: +i18n.verification_code_incorrect.d8c0=验证码不正确 +i18n.main_class_not_found.b4b7=中没有找到对应的MainClass\: +i18n.node_not_exist.760e=对应的节点不存在 +i18n.start_distribution_exclamation.9fc2=开始分发\! +i18n.failure_prefix.115a=失败\: +i18n.connection_closed.6d4e=连接关闭 {} {} +i18n.no_corresponding_workspace_permission.8402=没有对应的工作空间权限 +i18n.post_distribution_action_required.8cc8=请选择分发后的操作 +i18n.unsupported_item.bcf4=不支持的: +i18n.no_available_new_version_upgrade.d8f2=没有可用的新版本升级\:-1 +i18n.docker_associated_workspaces_message.de78=当前 docker 还关联{}个 工作空间 docker 不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.static_directory_not_configured.9bd6=还未配置静态目录 +i18n.server_jps_command_exception.e380=当前服务器 jps 命令异常,请检查 jdk 是否完整,以及 java 环境变量是否配置正确 +i18n.data_download_failed.9499=数据下载失败 +i18n.delete_data.40f8=删除数据 +i18n.image_name_required.ab44=请填写镜名称 +i18n.listen_log_changes.9081=监听日志变化 +i18n.configure_correct_auth_url.22e7=请配置正确的授权 url +i18n.current_upload_chunk_info_incorrect.900e=当前上传的分片信息错误 +i18n.listen_log_success_currently_sessions_viewing.a74a=监听{}日志成功,目前共有{}个会话正在查看 +i18n.node_script_template.be6a=节点脚本模板 +i18n.post_packaging_action_required.bf66=请选择打包后的操作 +i18n.multiple_clusters_exist.196b=系统中存在多个集群,不需要自动绑定数据 +i18n.variable_name_already_exists.70f2=对应的变量名称已经存在啦 +i18n.node_management.b26d=节点管理 +i18n.account_mfa_not_enabled.fd39=当前账号没有开启两步验证 +i18n.exit_successful.8150=退出成功 +i18n.main_class_attribute_not_found.24c9=清单文件中没有找到对应的MainClass属性 +i18n.code_pull_conflict.6e8e=拉取代码发生冲突,可以尝试清除构建或者解决仓库里面的冲突后重新操作。: +i18n.cannot_delete_super_admin.68e2=不能删除超级管理员 +i18n.node_running_status_abnormal.3160=【{}】节点的运行状态异常 +i18n.pull_code_failed.70d6=拉取代码失败:{} +i18n.execution_succeeded.f56c=执行成功 +i18n.docker_already_exists.d9a5=对应的 docker 已经存在啦 +i18n.build_task_queue_waiting.5f06=构建任务开始进入队列等待.... +i18n.system_process_monitoring_error.fe1d=系统进程监控异常: +i18n.pull_log_exception.cc3e=拉取日志异常 +i18n.create_file_watch_failure.bc1a=创建文件监听失败 +i18n.no_project_found.ef5e=没有找到对应的项目 +i18n.delete_project_file_failure_with_full_stop.85b8=删除项目文件失败: +i18n.create_plugin_endpoint_connection_failure.30f8=创建插件端连接失败 {} +i18n.manual_cache_refresh_exception.9d91=手动刷新缓存异常 +i18n.no_script_template_found.0498=没有找到对应的脚本模板 +i18n.user_field_required.8732=第 {} 行 user 字段不能位空 +i18n.cannot_delete_running_project.e56b=不能删除正在运行的项目 +i18n.incorrect_parameter.02ce=参数存在不正确 +i18n.update_node_machine_id_failed.51d9=更新节点表机器 id 失败: +i18n.compare_files_result_with_delete.033d=对比文件结果,产物文件 {} 个、需要上传 {} 个、需要删除 {} 个 +i18n.build_trigger_queue_result.a1fe=构建触发器队列执行结果:{} +i18n.unsupported_type_with_colon.1050=不支持的类型\: +i18n.no_machine_found.c16c=没有找到对应的机器 +i18n.admin_email_not_configured.ecb8=管理员还没有配置系统邮箱,请联系管理配置发件信息 +i18n.node_response_error.efc6=\ 节点响应异常,状态码错误: +i18n.target_database_info_not_specified.2ff6=未指定目标数据库信息 +i18n.file_type_no_stop.00ff=file 类型项目没有 stop +i18n.monitor_ssh_exception.e9ce=监控 ssh[{}] 异常 +i18n.running_node_cannot_be_empty.ffc6=运行节点不能为空 +i18n.push_registration_to_server_failed.5949=向服务端推送注册失败 {} +i18n.delete_script_template_execution_error.8bc5=删除脚本模版执行数据错误\:{} +i18n.no_menus_contact_admin.cfec=没有任何菜单,请联系管理员 +i18n.check_passed.dce8=检查通过 +i18n.upload_progress_with_colon.dd5b=上传文件进度:{} {}/{} {} +i18n.machine_node_info.6a75=机器节点信息 +i18n.ssh_command_log.7fd1=SSH命令日志 +i18n.upgrade_failure_with_colon.59f1=升级失败\: +i18n.script_tag_modification_not_allowed.cb75=脚本标记不能修改 +i18n.auth_exception.27be=授权异常 +i18n.unsupported_plugin_message.2889=当前还不支持 {} 插件 +i18n.operation_succeeded_refresh_backup.54a9=操作成功,请稍后刷新查看备份状态 +i18n.no_data.55a2=没有任何数据 +i18n.node_not_enabled.a14d=节点未启用 +i18n.no_build_id.a0b8=没有buildId +i18n.user_not_select_permission_group.1091=用户未选择权限组 +i18n.project_has_logs_cannot_delete.1d2a=当前项目存在日志阅读,不能直接删除 +i18n.unsupported_encoding_with_placeholder.3bd9=不支持的编码方式:{} +i18n.export_image_exception.cb1c=导出镜像异常 +i18n.backup_data_not_exist.f88c=备份数据不存在 +i18n.illegal_character_encoding_format.af7a=配置的字符编码格式不合法: +i18n.select_file_to_delete.33d6=请选择要删除的文件 +i18n.online_upgrade_cannot_downgrade.d419=在线升级不能降级操作 +i18n.incorrect_cluster_address.893f=填写的集群地址不正确 +i18n.check_docker_dependency_error.60f7=检查 docker 依赖错误\:{} +i18n.program_already_running.96e1=当前程序正在运行中,不能重复启动,PID\: +i18n.password_change_success.8013=修改密码成功! +i18n.build_resource_cleanup_failed.c4cf=清理构建资源失败 +i18n.incorrect_publish_method.e095=发布方法不正确 +i18n.import_low_version_data_to_new_version.247b=2. 将导出的低版本数据( sql 文件) 导入到新版本中【启动程序参数里面添加 --replace-import-h2-sql\=/xxxx.sql (路径需要替换为第一步控制台输出的 sql 文件保存路径)】 +i18n.incorrect_line_number.5877=行号不正确 +i18n.file_deletion_event_with_details.7537=文件删除事件:{} {} +i18n.configure_correct_token_url.7bba=请配置正确的令牌 url +i18n.maven_plugin_version_required.71f1=maven 插件 version 不能为空 +i18n.trigger_token_error_or_expired_with_code.393b=触发token错误,或者已经失效\:-1 +i18n.invalid_webhooks_address.d836=WebHooks 地址不合法 +i18n.project_path_auth_not_under_jpom.0e18=项目路径授权不能位于Jpom目录下 +i18n.reconnect_failure.7c01=重连失败 +i18n.project_operations.03d9=项目运维 +i18n.image_not_exist.ee17=镜像不存在 +i18n.no_data.1ac0=没有数据 +i18n.load_file_failure.86cc=加载文件失败\: +i18n.no_corresponding_ssh_script_info.1c12=没有对应的ssh脚本信息 +i18n.log_file_error.473b=日志文件错误 +i18n.compare_id_not_exist.43be=比较 id 不存在 +i18n.private_file_not_found.ee45=没有隐私文件 +i18n.detect_local_docker_exception_with_details.7cc9=探测本地 docker 异常: +i18n.prepare_restart.8251=开始准备项目重启:{} {} +i18n.save_node_data_failed.f314=保存节点数据失败\: +i18n.range_format_not_supported.d69e=不支持的 range 格式 +i18n.select_at_least_one_node_project.637c=至少选择1个节点项目 +i18n.close_beta_plan_success.5a94=关闭 beta 计划成功 +i18n.start_rolling_back_execution.a019=开始回滚执行 +i18n.get_container_log_interrupted.041d=获取容器日志操作被中断\: +i18n.node_has_no_workspace.69c0=节点没有工作空间 +i18n.correct_enterprise_wechat_address_required.5f2d=请输入正确企业微信地址 +i18n.unknown_jsch_log_level_with_details.1f9a=未知的 jsch 日志级别:{} {} +i18n.file_not_found.d952=文件不存在 +i18n.node_communication_failure_signal.5aae=节点通讯失败,远程地址和端口时发生错误的信号。通常,由于中间的防火墙或中间路由器已关闭,无法访问远程主机。 +i18n.multiple_node_data_exists_merge_config.043f=节点地址 {} 存在多个数据,将自动合并使用 {} 节点的配置信息 +i18n.unknown_data_loss.5a24=未知(数据丢失) +i18n.command_execution_failed_details.77ed=执行命令失败,详情如下: +i18n.unknown_parameter.96dd=未知参数 +i18n.publish_file_second_level_directory_required.2f65=请填写发布文件的二级目录 +i18n.node_id_required_and_format.5926=节点id不能为空并且2-50(英文字母 、数字和下划线) +i18n.restore_backup_data_failed.58af=还原备份数据失败 +i18n.system_task_execution_exception.d559=执行系统任务异常 +i18n.command_management.621f=命令管理 +i18n.no_project_specified2.a7f5=没有对应项目: +i18n.certificate_info_error_issuer_or_subject_DN_not_found.805d=证书信息出现错误,未找到 issuerDN 或者 subjectDN +i18n.exported_ssh_data.2896=导出的 ssh 数据 +i18n.refreshing_cache.c969=正在刷新缓存中,请勿重复刷新 +i18n.start_executing_database_event.fc57=开始执行数据库事件:{} +i18n.static_file_storage.35f6=静态文件存储 +i18n.project_log_is_existing_folder.a80a=项目log是一个已经存在的文件夹 +i18n.project_data_workspace_id_inconsistency.7ed6=项目数据工作空间ID[{}]查询出节点ID不一致, 旧数据\: {}, 新数据\: {} +i18n.query_workspace_error.6a0d=查询错误的工作空间失败 +i18n.unknown_database_mode.f9e5=当前数据库模式未知 +i18n.secondary_directory_match.0aec={} 二级目录模糊匹配到 {} 个文件, 当前文件保留方式 {} +i18n.ssh_server_alive_interval_config_error.1f11=配置 ssh serverAliveInterval 错误 +i18n.reset_success.faa3=重置成功 +i18n.please_fill_in_repository_name.9f0d=请填写仓库名称 +i18n.parameter_error_ssh_name_cannot_be_empty.ff4f=参数错误ssh名称不能为空 +i18n.current_docker_cluster_still_associated_with_workspaces.b301=当前 docker 集群还关联 {} 个工作空间集群,不能退出集群 +i18n.current_distribution_has_only_one_project.cd59=当前分发只有一个项目啦,删除整个分发即可 +i18n.no_corresponding_execution_log.9545=没有对应的执行日志 +i18n.import_success_with_details.a4a0=导入成功(编码格式:{}),添加 {} 条数据,修改 {} 条数据 +i18n.current_distribution_has_build_items_cannot_unbind.a8e9=当前分发存在构建项,不能解绑 +i18n.listen_file_failed_may_not_exist.fd56=监听文件失败,可能文件不存在 +i18n.log_file_cleanup_failed.3a3b=清理日志文件失败 +i18n.parse_csv_exception.885e=解析 csv 异常 +i18n.no_build_record_found.76f4=还没有对应的构建记录 +i18n.login_info_required.973b=请输入登录信息 +i18n.h2_database_backup_success.a099=H2 数据库备份成功:{} +i18n.uses_only_supports_string_type.ac54=uses 只支持 String 类型 +i18n.link_id_required.5dc7=Link 模式 LinkId必填 +i18n.oshi_hard_disk_monitoring_exception.c642=oshi 硬盘资源监控异常 +i18n.get_docker_cluster_info_failure.c80d=获取 docker 集群信息失败 +i18n.exit_code.3b54=本次执行退出码\: {} +i18n.compare_project_failure.e6ab=对比项目文件失败: +i18n.parse_file_exception.374d=解析文件异常, +i18n.certificate_info_table.fff8=证书信息表 +i18n.online_upgrade.da8c=在线升级 +i18n.no_log_file.bacf=还没有日志文件 +i18n.listen_task_lost_or_not_found.347f=监听任务丢失或者未找到:{} +i18n.parse_project_csv_exception.ece1=解析项目 csv 异常 +i18n.ssh_file_deletion_exception.5ba5=ssh删除文件异常 +i18n.token_invalid_or_expired.cb96=token错误,或者已经失效\:-1 +i18n.list_and_query.c783=列表、查询 +i18n.migration_docker_cert_error.a5ea=迁移 docker[{}] 证书发生异常 +i18n.file_downloading.7a8f=文件下载中 +i18n.mark_must_contain_letters_numbers_underscores.667d=标记只能包含字母、数字、下划线 +i18n.please_fill_in_runs_on.c2ff=请填写runsOn。 +i18n.create_build_task_exception.06f1=创建构建任务异常 +i18n.execution_interrupted.1bb6=执行被中断 +i18n.event_loss_or_execution_error.7b14=事件丢失或者执行错误:{} {} +i18n.public_key_and_private_key_mismatch.4aa2=公钥和私钥不匹配 +i18n.restore_success.4c7f=还原成功 +i18n.monitoring_notifications.de94=监控通知 +i18n.machines_node_data_fixed.7744=成功修复 {} 条机器节点数据 +i18n.upload_success.a769=上传成功 +i18n.send_email_failure.1ab3=发送邮件失败: +i18n.email_configuration.b3f7=邮箱配置 +i18n.server_script_not_exist.de24=不存在对应的服务端脚本,请重新选择 +i18n.java_plugin_version_required.de39=java 插件 version 不能为空 +i18n.soft_link_project_mode_error.ffa0=被软链的项目不能是File或Link模式 +i18n.node_null_pointer_exception.76fe={}节点,程序空指针异常 +i18n.parse_certificate_exception.3b6c=解析证书异常 +i18n.get_build_status_exception.914e=获取构建状态异常 +i18n.node_has_distribution_projects_cannot_delete.3987=该节点存在分发项目,不能 +i18n.selected_user_status_abnormal.efcf=选择的用户状态异常 +i18n.target_workspace_consistency.e04c=目标工作空间与当前工作空间一致并且目标节点与当前节点一致 +i18n.nickname_length_limit.6312=昵称长度只能是2-10 +i18n.please_pass_parameter.3182=请传入参数 +i18n.node_no_data_pulled.0dae={} 节点没有拉取到任何 {},但是删除了数据:{} +i18n.node_info_not_found.2c8c=没有查询到节点信息: +i18n.please_fill_in_repository_address.0cf8=请填写仓库地址 +i18n.table_without_primary_key.7392=表没有主键 +i18n.invalid_shard_id.46fd=不合法的分片id +i18n.execution_ended.b793=执行结束\:{} +i18n.user_trigger_unavailable.9866=当前用户触发器不可用 +i18n.task_ended.b341=任务结束\: +i18n.session_closed_reason.103a=会话[{}]关闭原因:{} +i18n.static_directory_auth_cannot_be_empty.2cb2=静态目录授权不能为空 +i18n.file_publish_task_record.edc4=文件发布任务记录 +i18n.no_files_in_zip.1af6=压缩包里没有任何文件 +i18n.unable_to_connect_to_repository.52df=无法连接此仓库, +i18n.parent_task_not_exist.ca1b=父任务不存在 +i18n.distribute_exception_with_detail.28fe=分发异常 {} +i18n.unknown_database_dialect_type.951b=未知的数据库方言类型\: +i18n.fill_download_address.763c=填写下载地址 +i18n.verification_code_is.5af5=验证码是:{} +i18n.pull_exception.b38d=拉取异常 +i18n.incomplete_upload_info_total_slice.7e85=上传信息不完成:totalSlice +i18n.database_corrupted.944e=数据库异常,可能数据库文件已经损坏(可能丢失部分数据),需要重新初始化。可以尝试在启动参数里面添加 --recover\:h2db 来自动恢复,: +i18n.handle_node_synchronization_script_library_failure.14e4=处理 {} 节点同步脚本库失败 {} +i18n.cluster_name_required.5ca6=请填写集群名称 +i18n.build_method_incorrect.5319=构建方式不正确 +i18n.data_does_not_exist.b201=数据不存在 +i18n.operation_time.7e95=操作时间 +i18n.communication_ip_cannot_be_empty.ae35=通信 IP 不能为空 +i18n.build_info_not_exist.4470=不存在对应的构建信息 +i18n.protocol_field_required.7cc2=第 {} 行 protocol 字段不能位空 +i18n.incorrect_mode_for_migration.caef=当前模式不正确,不能直接迁移到 {} +i18n.socket_session_establishment_failed.4924=socket 会话建立失败,授权信息错误 +i18n.handle_message_exception.0bdc=处理消息异常 +i18n.index_field_not_configured.96d9=索引未配置字段 +i18n.file_name_not_found.b0ed=没有文件名 +i18n.file_deletion_event.a51c=文件删除事件:{} +i18n.no_asset_management_permission.739e=您没有资产管理权限 +i18n.current_docker_already_in_other_cluster.e629=当前 docker 已经加入到其他集群啦 +i18n.addition_succeeded.3fda=添加成功 +i18n.node_upgrade.3bf3=节点升级 +i18n.server_system_config.3181=服务端系统配置 +i18n.empty_file_cannot_upload.88df=空文件不能上传 +i18n.config_file_already_exists.c5fe=对应的配置文件已经存在啦 +i18n.login_password_required.9605=请填写登录密码 +i18n.current_repository_associated_with_build.4b6e=当前仓库被构建关联,不能直接删除 +i18n.unzip_exception.92cc=解压异常 {} by InputStream {} +i18n.read_global_script_file_error.0d4c=读取全局脚本文件失败 +i18n.build_product_sync_success.f7d1=构建产物文件成功同步到文件管理中心,{} +i18n.decrypt_parameter_failure.d10a=解密参数失败 +i18n.no_record.ff41=没有对应的记录 +i18n.workspace_error_or_no_permission.7c8b=工作空间错误,或者没有权限编辑此数据 +i18n.upload_failed.b019=上传失败\: +i18n.no_permission.e343=您没有对应权限 +i18n.listener_key_not_found.6d3a=没有找到监听 key +i18n.download_failed_retry.c113=下载失败。请刷新页面后重试 +i18n.please_fill_in_correct_go_version.44ed=请填入正确的 go 版本号 +i18n.forbidden_operation_time_range.92bf=【禁止操作】当前时段禁止执行 {} 至 {} +i18n.no_matching_files.b7a6={} 没有匹配到任何文件 +i18n.multi_download_not_supported.94b9=不支持分片多端下载 +i18n.parameter_error_port_error.810d=参数错误port错误 +i18n.incomplete_node_info_missing_machine_id.1c9a=节点信息不完整,缺少机器id +i18n.node_network_connection_exception_or_timeout.5904=节点网络连接异常或超时,请优先检查插件端运行状态再检查 IP 地址、 +i18n.ssh_command_management.c40a=SSH命令管理 +i18n.variable_name_rules.480a=变量名称 1-50 英文字母 、数字和下划线 +i18n.parameter_error_user_cannot_be_empty.9239=参数错误user不能为空 +i18n.no_database_config_header_found.9ee3=没有找到数据库配置标识头 +i18n.week_day_range_format.ebec=周{} 的 {} 至 {} +i18n.no_node_info_no_need_to_fix_machine_data.562e=没有任何节点信息,不需要修复机器数据 +i18n.send_message_exception.7817=发消息异常 +i18n.static_file_task_load_failure.b995=静态文件任务加载失败 +i18n.server_exception_occurred.9eb4=服务端发生异常 +i18n.ssh_unauthorized_directory.df78=此ssh未授权操作此目录 +i18n.event_script_does_not_exist.e726=事件脚本不存在\:{} {} +i18n.async_refresh_in_progress.5550=异步刷新中请稍后刷新页面查看 +i18n.build_status_message.42a7=当前构建中任务数:{},队列中任务数:{} 构建任务等待超时或者超出最大等待数量,当前运行中的任务数:{}/{},取消执行当前构建 +i18n.download_action.f26e=下载 +i18n.project_is_not_node_distribution_project_cannot_delete.2a5a=该项目不是节点分发项目,不能在此次删除 +i18n.node_has_build_items_cannot_delete.a952=该节点存在构建项,不能 +i18n.encoding_error.b685=编码异常 +i18n.node_distribution.ae68=节点分发 +i18n.two_step_verification_code_required.7e86=请输入两步验证码 +i18n.close_docker_exec_terminal.fec3=关闭[{}] docker exec 终端:{} +i18n.ssh_error_string.6bdb=ssh 错误:{} +i18n.project_id_does_not_exist.6b9b=项目id不存在 +i18n.cluster_not_bound_to_group_for_docker_monitoring.3926=当前集群还未绑定分组,不能监控 Docker 资产信息 +i18n.no_content_to_execute.66aa=没有需要执行的内容 +i18n.online_build.6f7a=在线构建 +i18n.export_low_version_data.f1aa=1. 导出低版本数据 【启动程序参数里面添加 --backup-h2】 +i18n.docker_asset_imported.0ab4=docker[{}] 资产导入 +i18n.associated_group_required.5889=请选择关联分组 +i18n.backup_directory_conflict.c13e=备份目录冲突: +i18n.delete_service_success.4d73=删除服务成功 +i18n.select_monitoring_operation.3057=请选择监控的操作 +i18n.config_file_database_config_not_parsed.47b2=未解析出配置文件中的数据库配置信息 +i18n.data_already_exists.0397=导入的数据已经存在啦 +i18n.please_check_in_time.3b4f=请及时检查 +i18n.installation_success.811f=安装成功 +i18n.cluster_management.74ea=集群管理 +i18n.type_field_value_error.14cf=第 {} 行 type 字段值错误(Git/Svn) +i18n.migrate_data.f556=迁移数据 +i18n.no_script.93c4=没有对应的脚本 +i18n.node_does_not_exist.4ce4=节点不存在 +i18n.not_super_admin.962e=您不是超级管理员没有权限\:-2 +i18n.ssh_does_not_exist.5bec=对应的 SSH 不存在 +i18n.file_too_large.9994=上传文件太大了,请重新选择一个较小的文件上传吧 +i18n.backup_product.53c0=备份产物 {} {} +i18n.command_error.d0b4=执行命令错误 +i18n.select_alarm_contact.d02a=请选择报警联系人 +i18n.file_signature_info_not_found.83bf=没有文件签名信息 +i18n.associated_nodes_warning.64d8=当前机器还关联{}个节点,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.build_unknown_error.dad6=构建发生未知错误 +i18n.new_version_exists_download_unavailable.4ba7=存在新版本,下载地址不可用 +i18n.compare_backup_failure.303e=对比清空项目文件备份失败 +i18n.upload_success_and_restart.7bc3=上传成功并重启 +i18n.no_deletion_condition.19d0=没有删除条件 +i18n.get_docker_cluster_failure_with_placeholder.06cb=获取 {} docker 集群失败 {} +i18n.synchronize_project_files_failed.6aa4=同步项目文件失败: +i18n.parse_system_start_time_error.112c=\ 解析系统启动时间错误: +i18n.service_info_incomplete_with_code1.30f4=服务信息不完整不能操作:-1 +i18n.at_least_one_project_required.2bbd=请至少选择一个项目 +i18n.cluster_id_conflict.45b7={} 集群ID冲突:{} {} +i18n.modified_value_is_empty.e4fa=修改的值为空 +i18n.cannot_delete_root_dir.fcdc=不能删除根目录 +i18n.manual_cancel_task.e592=手动取消任务 +i18n.unzip_exception.453e={} 解压异常 {} {} +i18n.start_executing_event_script.377e=开始执行事件脚本: {} +i18n.unlock_success.4cea=解锁成功 +i18n.login_name_email_format_length_range.25f3=登录名如果为邮箱格式,长度必须 {}-{} +i18n.ssh_data_repair_not_needed.203f=机器 SSH 表已经存在 {} 条数据,不需要修复机器 SSH 数据 +i18n.new_package_same_as_running_package.e25a=新包和正在运行的包一致 +i18n.config_file_not_exist_with_message.6a40=配置文件不存在 {} +i18n.no_workspace_selected.33d5=没有选择任何工作空间 +i18n.associated_data_exists_in_workspace.8827=当前工作空间下还存在关联数据: +i18n.script_not_bound_to_ssh_node.3459=当前脚本未绑定 SSH 节点,不能使用触发器执行 +i18n.start_executing_post_release_command.fd06=开始执行 {} 发布后命令 +i18n.alert_content_and_status.6ed1=报警内容:{} 状态消息:{} +i18n.incorrect_project_id.5f70=项目id 不正确 +i18n.certificate_management.4001=证书管理 +i18n.download_exception.e616=下载文件异常 +i18n.process_file_deletion_exception.1c6e=处理文件删除异常 +i18n.trigger_auto_execute_command_template_exception.4e01=触发自动执行命令模版异常 +i18n.no_build.d163=没有对应的构建 +i18n.start_executing_publishing_with_file_size.5039=开始执行发布,需要发布的文件大小:{} +i18n.command_execution_exception.4ccd=执行命令异常 +i18n.operation_ip.cbd4=操作IP +i18n.auth_directory_cannot_contain_hierarchy.d6ca=授权目录中不能存在包含关系: +i18n.ip_authorization_interception_exception.8130=IP授权拦截异常,请检查配置是否正确 +i18n.refresh_token_timeout.3291=刷新token超时 +i18n.missing_script_library_message.be9a=对应的脚本库不存在: +i18n.operation_file_permission_exception.5a41=操作文件权限异常,请手动处理: +i18n.monitoring_user_actions.f2d5=监控用户操作 +i18n.please_fill_in_user.5f52=请填写user +i18n.command_template_execution_link_exception.51cf=命令模版执行链接异常 +i18n.cleanup_succeeded.02ea=清理成功 +i18n.distribute_node_authorization_failure.bb92=分发 {} 节点授权失败 {} +i18n.monitoring_item_not_exist.32c8=不存在监控项啦 +i18n.workspace_id_required.c967=工作空间ID不能为空 +i18n.please_fill_in_ipv4_address.d23a=请填写 ipv4 地址: +i18n.publish_product.5925=发布产物 +i18n.no_workspace_info_contact_admin_for_authorization.825f=没有任何工作空间信息,请联系管理授权 +i18n.cannot_disable_super_admin.6429=不能禁用超级管理员 +i18n.oshi_system_process_monitoring_exception.a4da=oshi 系统进程监控异常 +i18n.start_async_download.78cc=开始异步下载 +i18n.install_id_does_not_exist.6aee=数据错误,安装 ID 不存在 +i18n.file_format_not_supported.eac4=不支持的文件格式 +i18n.restore_backup_data_success.253a=还原备份数据成功 +i18n.build_info_missing.0ab0=构建信息缺失 +i18n.file_not_exist.5091=对应的文件不存在 +i18n.max_concurrent_shard_ids.f89c=分片id最大同时使用 100 个 +i18n.handle_node_deletion_script_library_failure.4205=处理 {} 节点删除脚本库失败 {} +i18n.data_id_not_found.1b0a=没有数据id +i18n.yml_config_format_error_tab.f629=yml 配置内容格式有误请检查后重新操作(不要使用 \\\\t(TAB) 缩进): +i18n.build_in_progress.4d33=当前构建还在进行中 +i18n.execute_script_exit_code.64a8=执行 {} 类型脚本的退出码是:{} +i18n.user_does_not_exist.8363=\ 用户不存在请联系管理创建 +i18n.no_node_found.6f85=没有找到对应的节点 +i18n.current_docker_not_in_cluster.f70c=当前 docker {} 不在集群中 +i18n.no_available_docker_server.9fc6=\ 没有可用的 docker server +i18n.active_clearance.5870=主动清除 +i18n.login_info_expired_re_login.6bc4=登录信息已失效,重新登录 +i18n.release_node_project_failed.764e=释放节点项目失败: +i18n.no_corresponding_task.3be5=没有对应的任务 +i18n.account_does_not_exist.8402=账号不存在 +i18n.scan_succeeded.7975=扫描成功 +i18n.websocket_error.2bb4=websocket出现错误:{} +i18n.no_management_permission2.35d4=您没有对应管理权限\:-3 +i18n.file_in_use_stop_project_first.a2c3=文件被占用,请先停止项目 +i18n.product_file_does_not_exist.ee13=产物文件不存在 +i18n.default_setting.18c6=默认 +i18n.address_field_required.3bc8=第 {} 行 address 字段不能位空 +i18n.get_repository_branch_failure.37cc=获取仓库分支失败 +i18n.file_type_project_no_running_status.32a2=file 类型项目没有运行状态 +i18n.project_info.6674=项目信息 +i18n.no_changes_in_repository_code_with_details.fd9f=仓库代码没有任何变动终止本次构建:{} {} +i18n.import_save_project_exception.cdbe=导入保存项目异常 +i18n.in_progress.b851=进行中\: +i18n.current_distribution_data_lost_record_id_not_exist.ca07=当前分发数据丢失,记录id 不存在 +i18n.oauth2_binding_warning.d8f0=当前权限组被 oauth2[{}] 绑定,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.node_status_code_abnormal.4d22=【{}】节点的状态码异常:{} +i18n.data_does_not_exist_with_details.d9b5=数据不存在\: +i18n.data_type_not_supported.fd03=不支持的数据类型\: +i18n.refresh_token_failure.de7f=刷新token失败 +i18n.no_docker_details.3343=没有对应到docker信息 +i18n.no_permission_for_function.b63d=您没有对应功能【{}】管理权限 +i18n.auth_config.3d48=授权配置 +i18n.no_info.e59e=没有任何信息 +i18n.repository_info.22cd=仓库信息 +i18n.data_id_cannot_be_empty.403b=数据 id 不能为空 +i18n.ssh_does_not_exist.88d7=ssh 不存在 +i18n.select_operation_type.63c6=请选择操作类型 +i18n.clear_success.2685=清空成功 +i18n.illegal_access.c365=非法访问 +i18n.certificate_has_no_aliases.3a2f=证书没有任何:aliases +i18n.docker_asset_management.96d9=DOCKER资产管理 +i18n.docker_tag_incorrect.8b62=docker tag 填写不正确,没有找到任何docker +i18n.release_successful.f2ca=释放成功 +i18n.result_dir_file_required.5f02=resultDirFile 不能为空 +i18n.status_not_distributing.6298=当前状态不是分发中 +i18n.build_record_not_exist.8186=构建记录不存在 +i18n.unable_to_connect_to_docker.2bb3=无法连接 docker 请检查 host 或者 TLS 证书 以及仓库信息配置是否正确。 +i18n.system_configuration_directory.0f82=系统配置目录 +i18n.node_authorized_config.f934=节点授权配置 +i18n.cannot_operate_current_directory.aa3d=不能操作当前目录 +i18n.script_template_exists.3f86=该节点下还存在脚本模版,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.do_not_reopen.f86a=不要重复打开 +i18n.asset_ssh_not_exist.cd43=不存在对应的资产SSH +i18n.package_missing_info.e277=此包没有版本号、打包时间、最小兼容版本 +i18n.too_many_attempts.d88d=尝试次数太多,请稍后再来 +i18n.incorrect_account_credentials.b2c5=账号密码不正确 +i18n.plugin_system_log.955c=插件端系统日志 +i18n.cluster_created_successfully.6bf3=集群创建成功 +i18n.data_associated_id_inconsistent.59f7=数据关联的id 不一致 +i18n.script_exit_code.716e=执行脚本的退出码是:{} +i18n.ssh_connections_warning.1ddb=当前机器SSH还关联{}个ssh,不能直接删除(需要提前解绑或者删除关联数据后才能删除) +i18n.oauth2_not_configured.9c85=未配置 oauth2 +i18n.disallowed_file_extension.eb05=不允许编辑的文件后缀 +i18n.no_environment_variables_found.46ad=没有找到任何环境变量 +i18n.remote_download_url.011f={} 远程下载 url:{} +i18n.correction_data_failure.dac6=修正数据失败: +i18n.no_corresponding_ssh_item.2deb=没有对应的ssh项 +i18n.private_key_file_not_exist.49ed=配置的私钥文件不存在 +i18n.check_docker_exception.a6d1=检查 docker 异常 +i18n.only_git_repositories_have_branch_info.d7f7=只有 GIT 仓库才有分支信息 +i18n.select_monitoring_person.0756=请选择监控人员 +i18n.associated_data_name_not_exist_error.583e=ERROR\:关联数据名称不存在 +i18n.file_not_exist.ea6a=不存在对应的文件 +i18n.demo_account_not_support_delete.f9a6=演示账号不支持删除 +i18n.no_command_to_execute.340b=没有需要执行的命令 +i18n.no_files_in_project_directory.108e=项目目录没有任何文件,请先到项目文件管理中上传文件 +i18n.build_record_lost.f6a2=构建记录丢失,无法继续构建 +i18n.no_node.2e83=没有对应的节点 +i18n.reconnect_plugin_failure.cc6c=重连插件端失败 +i18n.installation_success_with_machine_id.1cd6=安装成功,本机安装 ID 为:{} +i18n.query_success.d72b=查询成功 +i18n.create_success.04a6=创建成功 +i18n.send_message_failure.9621=发送消息失败 +i18n.project_has_node_distribution_cannot_migrate.cc0e=当前项目存在节点分发,不能直接迁移 +i18n.please_fill_in_node_address.e77e=请填写 节点地址 +i18n.login_name_length_range.fe8d=登录名长度范围 3-50 +i18n.file_already_exists.983d=文件已经存在啦 +i18n.repository_key_file_does_not_exist_or_is_abnormal.1d78=仓库密钥文件不存在或者异常,请检查后操作 +i18n.no_corresponding_docker_info.c47a=没有对应的 docker 信息 +i18n.type_not_exist_error.09de=ERROR\:类型不存在 +i18n.configure_announcement_title_or_content.7894=请配置公告标题或者内容 +i18n.no_any_branch.d042=没有任何分支 +i18n.connection_successful.0515=成功连接 {} {} +i18n.docker_does_not_exist_with_code.689b=对应的 docker 不存在\:-1 +i18n.multiple_worker_nodes_exist.7110=还存在多个工作节点,不能退出最后一个管理节点 +i18n.operation_log.cda8=操作日志 +i18n.close_resource_failure.dc66=关闭资源失败 +i18n.free_script.7760=自由脚本 +i18n.project_id_length_range.7064=项目id 长度范围2-20(英文字母 、数字和下划线) +i18n.system_cancel.3df2=系统取消 +i18n.configure_correct_user_info_url.1276=请配置正确的用户信息 url diff --git a/modules/common/src/main/resources/i18n/messages_zh_HK.properties b/modules/common/src/main/resources/i18n/messages_zh_HK.properties new file mode 100644 index 0000000000..9c9a26cd43 --- /dev/null +++ b/modules/common/src/main/resources/i18n/messages_zh_HK.properties @@ -0,0 +1,1616 @@ +#i18n zh_HK +#Sun Jun 16 22:24:30 CST 2024 +i18n.ssh_info_does_not_exist.5ed0=ssh 信息不存在啦 +i18n.incompatible_program_versions.5291=當前程序版本 {} 新版程序最低兼容 {} 不能直接升級 +i18n.no_projects_configured.e873=沒有配置任何項目 +i18n.docker_log_thread_ended.8230=docker log 線程結束:{} {} +i18n.machine_ssh_info.8dbb=機器SSH信息 +i18n.machine_info_not_exist.3468=對應的機器信息不存在 +i18n.unsupported_method.a1de=不支持的方式 +i18n.service_name_in_cluster_required.5446=請填寫集羣中的服務名 +i18n.cluster_manager_node_not_found.1cd0=沒有找到集羣管理節點 +i18n.distribute_id_already_exists.2168=分發id已經存在啦 +i18n.node_authorized_distribution.c5d7=節點授權分發 +i18n.operation_user.4c89=操作用户 +i18n.no_uploaded_file.07ef=沒有上傳文件 +i18n.physical_node_pull.874e={} 物理節點拉取到 {} 個{},當前工作空間邏輯節點已經緩存 {} 個{},更新 {} 個{},刪除 {} 個緩存 +i18n.request_type_not_supported_for_decoding.ea2e=當前請求類型不支持解碼:{} +i18n.account_login_failed_too_many_times_locked.23b2=該賬户登錄失敗次數過多,已被鎖定{},請不要再次嘗試 +i18n.no_corresponding_ssh_info.d864=沒有對應的ssh信息 +i18n.no_tag_name.40ff=沒有 tag name +i18n.ssh_console_connection_timeout.8eb3=ssh 控制枱連接超時 +i18n.publish_task_execution_failed.b075=執行發佈任務失敗 +i18n.migration_completed.7a30=遷移完成,累計遷移 {} 條數據,耗時:{} +i18n.container_command_execution_exception.a14a=執行容器命令異常 +i18n.plugin_connection_failed.02a1=插件端連接失敗 +i18n.auto_migrate_exist_logs.c169=自動遷移存在日誌 {} -> {} +i18n.demo_account_password_change_not_supported.91f4=當前賬户為演示賬號,不支持修改密碼 +i18n.file_type_not_supported3.f551=不支持的文件類型:{} +i18n.trigger_project_reload_event.a7dc=觸發項目 reload 事件:{} +i18n.alert_contact_exception_message.1072=報警聯繫人異常\: +i18n.chunk_upload_exception.87c1=分片上傳異常:{} {} +i18n.start_waiting_for_data_migration.e76f=開始等待數據遷移 +i18n.empty_file_or_folder_for_publish.cae8=發佈的文件或者文件夾為空,不能繼續發佈 +i18n.demo_account_cannot_use_feature.a1a1=演示賬號不能使用該功能 +i18n.repository_type_required.9414=請選擇倉庫類型 +i18n.async_resource_expired.2ddc=異步資源過期,需要主動關閉,{} {} +i18n.mark_cannot_be_empty.1927=標記不能為空 +i18n.get_success.fb55=獲取成功 +i18n.execution_interrupted_reason.e3d7=執行中斷 {} 流程,原因事件腳本中斷 +i18n.forbidden_operation_time_period.86a3=【禁止操作】當前時間不在可執行的時間段內,限制時間段\: +i18n.host_cannot_be_empty.644a=參數錯誤host不能為空 +i18n.pull_code_exception_with_cleanup.a887=拉取代碼異常,已經主動清理本地倉庫緩存內容,請手動重試。 +i18n.correct_retention_days_required.d542=請填寫正確的保留天數 +i18n.external_config_not_exist_or_not_configured.f24e=外置配置不存在或者未配置:{},使用默認配置 +i18n.cannot_read_tar_archive_entry.85d7=不能讀取tarArchiveEntry {} +i18n.cluster_not_exist.4098=對應的集羣不存在 +i18n.upload_failed_no_matching_project.b219=上傳失敗,沒有找到對應的分發項目 +i18n.no_branches_or_tags_in_repository.76b6=倉庫沒有任何分支或者標籤 +i18n.download_file_description.10cb=下載文件 {} {} {} +i18n.delay_build.7d62=延遲 {} 秒後開始構建 +i18n.default_cluster.38cf=默認集羣 +i18n.node_and_check_project_failed.ac4b=節點與檢查項目失敗 +i18n.load_oauth2_config.da42=加載 oauth2 配置 :{} {} +i18n.cluster_not_bound_to_group_for_node_monitoring.1586=當前集羣還未綁定分組,不能監控集羣節點資產信息 +i18n.cannot_execute_error.4c29=不能執行:error +i18n.no_environment_variable.c79f=沒有對應的環境變量 +i18n.restart_completed.42b8=重啟完成 +i18n.node_return_info_exception.0961=節點返回信息異常,請檢查節點地址是否配置正確或者代理配置是否正確 +i18n.project_associated_with_other_workspaces_message.299c=當前項目已經被其他工作空間關聯,請檢查確認後再操作或者使用孤獨數據修正 +i18n.start_executing_distribution_package.a2cc=開始執行分發包啦,請到分發中查看詳情狀態 +i18n.clear_old_version_package_failed.021c=清空舊版本重新包失敗 +i18n.node_online_upgrade.f144=節點在線升級 +i18n.execution_ended_with_detail.8f93=執行結束\: {} {} +i18n.ssh_file_upload_exception.5c1c=ssh上傳文件異常 +i18n.authorization_exception.acc0={} 授權異常 {} +i18n.invalid_or_expired_token.bc43=token錯誤,或者已經失效 +i18n.no_docker_info.d685=沒有對應 docker +i18n.invalid_http_proxy_address.1da1=HTTP代理地址格式不正確 +i18n.execute_dsl_script_exception.0882=執行 DSL 腳本異常:{} +i18n.package_product.bfbb=打包產物 +i18n.cancel_success.285f=取消成功 +i18n.node_name_required.ac0f=節點名稱 不能為空 +i18n.class_path_and_java_ext_dirs_cp_required.7557=ClassPath、JavaExtDirsCp 模式 MainClass必填 +i18n.parameter_validation_failed.f0a1=參數驗證失敗 +i18n.asset_cluster_and_node_mismatch.8964=資產集羣和節點不匹配 +i18n.script_template.54f2=腳本模板 +i18n.no_project_specified.0076=沒有對應的項目: +i18n.connection_successful_with_message.5cf2=連接成功: +i18n.old_password_incorrect.9cf6=舊密碼不正確! +i18n.script_template_execution_record.374b=腳本模版執行記錄 +i18n.ssh_authorization_directory_cannot_be_root.8125=ssh 授權目錄不能是根目錄 +i18n.node_connection_failure_message.aacc=節點連接失敗: +i18n.email_service_not_configured.3180=未配置郵箱服務不能發送郵件:{} {} +i18n.node_service_stopped_abnormal_restart.a5c0=【{}】節點的【{}】項目{}已經停止,重啟操作異常 +i18n.same_distribution_project_exists.ff41=已經存在相同的分發項目\: +i18n.unbind_success.1c43=解綁成功 +i18n.project_log.2926=項目日誌 +i18n.private_key_file_not_found.4ad9=私鑰文件不存在: +i18n.unsupported_prefix.4f8c=當前還不支持: +i18n.file_download_failed.7983=文件下載失敗: +i18n.no_corresponding_distribution_project.6dcd=沒有對應的分發項目 +i18n.build_log.7c0e=構建日誌 +i18n.no_current_static_directory_permission.ed70=沒有當前靜態目錄權限 +i18n.select_workspace_error.426e=選擇工作空間錯誤 +i18n.task_already_exists.f59a=任務已經存在啦 +i18n.start_distribution_with_count.cdc7=開始分發,需要分發 {} 個項目 +i18n.get_port_error.0698=獲取端口錯誤 +i18n.node_communication_failure.00fb=節點通訊失敗,請優先檢查限制上傳大小配置是否合理,或者網絡連接是否被代理終端、防火牆終端等。 +i18n.static_file_management.6ac2=靜態文件管理 +i18n.error_info.99ed=,錯誤信息: +i18n.no_corresponding_docker_asset.6f06=沒有對應的 docker 資產 +i18n.build_data_not_exist.0225=構建數據不存在:{},任務自動丟棄\:{} +i18n.project_does_not_exist.3029=項目不存在 +i18n.corresponding_node_does_not_exist.72cb=當前對應的節點不存在 +i18n.upload_success_and_distribute.f446=上傳成功,開始分發\! +i18n.data_table_not_supported_for_grouping.6678=當前數據表不支持分組 +i18n.node_sync_project_failed.a2a7=節點同步項目失敗 +i18n.get_container_execution_result_interrupted.4a48=獲取容器執行結果操作被中斷\: +i18n.no_ssh_entry_found.d0e1=沒有找到對應的ssh項:{} +i18n.build_runs_on_image_interrupted.00fd=構建 runsOn 鏡像被中斷 +i18n.incorrect_parameter_format.9efb=傳入的參數格式不正確 +i18n.multiple_certificate_files_found.bee3=找到 2 個以上的證書文件 +i18n.invalid_runs_on_image_name.4b96=runsOn 鏡像名稱不合法 +i18n.dockerfile_path_required.69ac=請填寫要執行的 Dockerfile 路徑 +i18n.file_upload_mode_not_configured.b3b2=沒有配置文件上傳模式 +i18n.file_full_path.16cc=文件全路徑:{} +i18n.container_startup_failure.532e=容器啟動失敗\: +i18n.environment_variables_not_found.dbd4=沒有環境變量 +i18n.workspace_name_already_exists.0f82=對應的工作空間名稱已經存在啦 +i18n.alarm_contact_or_webhook_required.6c24=請選擇一位報警聯繫人或者填寫webhook +i18n.upload_exception.cd6c=上傳異常: +i18n.user_not_exist.5387=不存在對應的用户 +i18n.no_corresponding_file_colon.8970=沒有對應文件\: +i18n.start_executing_upload_post_command.1c1b=開始執行上傳後命令 +i18n.data_type_not_configured_correctly.bf16=未正確配置數據類型 +i18n.monitoring_logs.2217=監控日誌 +i18n.ssh_script_batch_trigger_exception.70e1=SSH 腳本批量觸發異常 +i18n.select_cluster.f8c3=請選擇集羣 +i18n.no_build_history.39f7=沒有對應的構建歷史 +i18n.docker_does_not_exist.bb41=對應的 docker 不存在 +i18n.trigger_exception.d624=觸發異常 +i18n.remote_addresses_not_configured.275e=還沒有配置允許的遠程地址 +i18n.table_error_workspace_data.9021=表 {}[{}] 存在 {} 條錯誤工作空間數據 -> {} +i18n.directory_cannot_skip_levels.179e=目錄不能越級: +i18n.normal_end.3bfe=正常結束 +i18n.start_building.1039=開始構建中 +i18n.empty_execution_result.9fe8=執行結果為空, +i18n.database_exception_due_to_resources.dbf1=數據庫異常,可能因為服務器資源不足(內存、硬盤)等原因造成數據異常關閉。需要手動重啟服務端來恢復,: +i18n.prepare_rollback.dba6=開始準備回滾:{} -> {} +i18n.monitor_node_exception.6ff1=監控 {} 節點異常 {} +i18n.add_new_success.431a=新增成功! +i18n.no_parameters_added_with_minus_one.e47d=沒有添加任何參數\:-1 +i18n.welcome_join_session.1c16=歡迎加入\:{} 會話id\:{} +i18n.backup_old_package_failure_due_to_new_package_absence.b90c=備份舊程序包失敗:{},因為新程序包不存在:{} +i18n.completed_count_insufficient.02e9=完成的個數不足 {}/{} +i18n.cluster_info_incomplete_with_code.246b=集羣信息不完整,不能加入該集羣\:-1 +i18n.log_recorder_not_enabled.5a4e=日誌記錄器未啟用 +i18n.exit_code.ea65=執行退出碼:{} +i18n.start_executing.f0b9=開始執行\: {} +i18n.authentication_config.964c=認證配置 +i18n.please_fill_in_information_and_check_validity.771a=請填寫信息,並檢查是否填寫合法 +i18n.publish_exception.cf0b=執行發佈異常 +i18n.detect_local_docker_exception.ccfc=探測本地 docker 異常 +i18n.run_status_not_configured.e959=沒有配置 run.status +i18n.build_log_recorder_closed.1cc7=構建日誌記錄器已關閉,可能手動取消停止構建,流程\:{} +i18n.no_data_found.4ffb=沒有對應數據 +i18n.supported_comparison_operators_message.6d7a=表達式目前僅支持 \=\= 和 \!\= 比較 +i18n.get_container_log_failure.915d=獲取容器日誌失敗 +i18n.content_is_empty.3122=內容為空 +i18n.invalid_jar_file.e80a=jar 包文件不合法 +i18n.alert_contact_exception.2cec=報警聯繫人異常 +i18n.publish_command_length_limit.66b0=發佈命令長度限制在4000字符 +i18n.unbind.6633=解綁 +i18n.asset_monitoring_thread_pool_rejected_task.222e=資產監控線程池拒絕了任務:{} +i18n.container_cluster.a5b4=容器集羣 +i18n.workspace_ssh_already_exists.ccc0=對應的工作空間已經存在當前 SSH 啦 +i18n.non_plaintext_variable_cannot_view.50ca=非明文變量不能查看 +i18n.connect_plugin_failed.e492=連接插件端失敗:{} {} {} +i18n.restore_project_failed.7f7c=還原項目失敗 +i18n.no_nodes.17b4=沒有任何節點 +i18n.scheduled_backup_log_failure.a0d7=定時備份日誌失敗 +i18n.folder_download_not_supported.c3b7=暫不支持下載文件夾 +i18n.user_not_exist_trigger_invalid.f375=對應的用户不存在,觸發器已失效 +i18n.download_remote_file_exception.3ee0=下載遠程文件異常 +i18n.only_tar_files_supported.dcc4=只支持tar文件 +i18n.file_management_center.0f5f=文件管理中心 +i18n.no_file_found.7d40=沒有對應到文件 +i18n.server_captcha_generation_exception.54d0=當前服務器生成驗證碼異常,自動禁用驗證碼 +i18n.node_script_template_execution_record.704a=節點腳本模版執行記錄 +i18n.response_exception_status_code.cbca={} 響應異常 狀態碼錯誤:{} {} +i18n.current_project_associated_with_online_build_and_repository.96c5=當前【項目】關聯的【在線構建】關聯的【倉庫({})】被其他 {} 個不同發佈方式的【在線構建】綁定暫不支持遷移 +i18n.current_address_no_repository.db31=當前地址不存在倉庫: +i18n.dsl_not_configured.8a57=DSL 未配置運行管理或者未配置 {} 流程 +i18n.file_write_success.804a=文件寫入成功 +i18n.node_address_required.71f1=節點地址不能為空 +i18n.cannot_edit_corresponding_config_file.8d10=不能編輯對應的配置文件 +i18n.auth_directory_cannot_be_under_jpom.bb67=授權目錄不能位於Jpom目錄下 +i18n.docker_data_repair_not_needed.0fb9=機器 DOCKER 表已經存在 {} 條數據,不需要修復機器 DOCKER 數據 +i18n.invalid_project_path.04f7=項目路徑不能為空,不能為頂級目錄,不能包含中文 +i18n.file_type_not_supported2.d497=不支持的文件類型: +i18n.docker_management.e7e5=Docker管理 +i18n.no_project_id_found.0f21=沒有找到對應的項目id\: +i18n.agent_response_empty.cc8e=agent 端響應內容為空 +i18n.no_available_maven_versions.dffe=maven 鏡像庫中沒有找到任何可用的 maven 版本 +i18n.static_directory_cannot_contain_relation.1a90=靜態目錄中不能存在包含關係: +i18n.cluster_info_incomplete.84a1=集羣信息不完整,不能加入該集羣 +i18n.import_success.b6d1=導入成功 +i18n.distribute_thread_exception.9725=分發線程異常 +i18n.need_handle_build_queue_count.c01e=需要處理的 {} 構建隊列數:{} +i18n.config_file_not_found.310e=未找到配置文件\: +i18n.correct_verification_code_required.ff0d=請輸入正確的驗證碼 +i18n.no_corresponding_ssh.aa68=沒有對應的ssh +i18n.chunk_upload_file_exception.0dc3=分片上傳文件異常 +i18n.no_execution_id.68dc=沒有執行ID +i18n.command_execution_record.56d5=命令執行記錄 +i18n.task_not_exist.47e9=不存在對應的任務 +i18n.configure_dsl_content.42e3=請配置 dsl 內容 +i18n.select_correct_node.1b4e=請選擇正確的節點 +i18n.select_node_error.dc0f=選擇節點錯誤 +i18n.docker_certificate_file_missing.ad46=docker 證書文件丟失 +i18n.select_correct_post_publish_script.49d2=請選擇正確的發佈後腳本 +i18n.distribute_management.3a2d=分發管理 +i18n.please_fill_in_correct_node_version.8483=請填入正確的 node 版本號 +i18n.sql_statement_too_long.38d6=sql 語句太長啦 +i18n.strict_mode_image_build_failure.ecea=嚴格模式下鏡像構建失敗,終止任務 +i18n.synchronization_failed.091a={} 同步失敗 {} +i18n.security_warning_h2_console.4669=【安全警吿】數據庫賬號密碼使用默認的情況下不建議開啟 h2 數據 web 控制枱 +i18n.start_queuing_for_execution.7417=開始排隊等待執行 +i18n.disallowed_download.06a3=不允許下載當前地址的文件 +i18n.cluster_id_changed.6e49=集羣ID 發生變化:{} -> {} +i18n.import_success_message.2df3=導入成功(編碼格式:{}),更新 {} 條數據,因為節點分發/項目副本忽略 {} 條數據 +i18n.close_success.8a31=關閉成功 +i18n.no_config_file_found.9720=沒有找到對應配置文件: +i18n.server_captcha_available.5570=當前服務器驗證碼可用 +i18n.download_remote_file.ae84=下載遠程文件 +i18n.backup_old_package.a7fc=備份舊程序包:{} +i18n.certificate_file_missing.c663=證書文件丟失 +i18n.incorrect_range_information.a41c=range 傳入的信息不正確 +i18n.user_directory_not_found.cfe3=用户目錄沒有找到 +i18n.main_class_attribute_not_found.93e8=中沒有找到對應的MainClass屬性 +i18n.file_system_monitoring_exception.d4c0=文件系統監控異常: +i18n.account_name_nickname_required.b757=請輸入賬户暱稱 +i18n.docker_info_not_found.4f64=當前集羣未找到 docker 信息 +i18n.distribution_exception_saving.8285={} {} 分發異常保存 +i18n.no_main_class_found.b001=沒有找到對應的MainClass\: +i18n.save_succeeded.3b10=保存成功 +i18n.select_node_to_modify.6617=請選擇要修改的節 +i18n.correct_dingtalk_address_required.2b4a=請輸入正確釘釘地址 +i18n.query_folder_sftp_failed.9d35=查詢文件夾 SFTP 失敗, +i18n.name_field_required.e0c5=第 {} 行 name 字段不能位空 +i18n.plugin_end_log_connection_successful.9035=連接成功:插件端日誌 +i18n.start_building_with_number_and_path.c41c=開始構建 \#{} 構建執行路徑 \: {} +i18n.start_executing_upload_pre_command.fb5c=開始執行上傳前命令 +i18n.delete_container_exception.9ad8=刪除容器異常 +i18n.query_folder_failed.3f0e=查詢文件夾失敗, +i18n.script_library.aed1=腳本庫 +i18n.container_build_product_path_cannot_use_ant_pattern.ddc7=容器構建的產物路徑不能使用 ant 模式 +i18n.get_folder_failure.0fda=獲取文件夾失敗 +i18n.system_already_initialized.743c=系統已經初始化過啦,請勿重複初始化 +i18n.trigger_auto_execute_server_script_exception.8e84=觸發自動執行服務器腳本異常 +i18n.cleaned_data.0e9d={} 清理了 {}條數據 +i18n.no_implemented_feature.af80=沒有實現該功能 +i18n.no_corresponding_repository.dde9=沒有對應的倉庫 +i18n.get_docker_cluster_info_failure_with_code.fa77=獲取 docker 集羣信息失敗\:-1 +i18n.get_container_execution_result_failure.1828=獲取容器執行結果失敗 +i18n.ssh_already_exists_with_message.d284=對應的SSH已經存在啦 +i18n.build_finished.7f38=構建結束 +i18n.not_jpom_install_package.2cca=此文件不是 jpom 安裝包 +i18n.republishing.131d=重新發布中 +i18n.node_id_not_found.2f9e=沒有節點id +i18n.no_description.c231=\ 沒有描述 +i18n.certificate_in_use_by_docker.dd63=當前證書被 docker 關聯中,不能直接刪除 +i18n.no_corresponding_build_record.b3b2=沒有對應構建記錄. +i18n.auto_delete_data.ca62=\ 自動刪除 {} 表中數據 {} 條數據 +i18n.active_clearance_colon.96a6=主動清除: +i18n.correct_encoding_format_required.1f7f=請填寫正確的編碼格式, +i18n.notice_script_invocation_error.9002=noticeScript 調用錯誤 +i18n.go_plugin_version_required.ccf6=go 插件 version 不能為空 +i18n.select_correct_log_path_or_no_auth_configured.9a9b=請選擇正確的日誌路徑,或者還沒有配置授權 +i18n.service_info_incomplete.968d=服務信息不完整不能操作 +i18n.session_already_closed.8dcc=會話已經關閉啦,不能發送消息:{} +i18n.modify_or_add_data.e1f0=修改、添加數據 +i18n.alias_code_validation.8b99=別名碼只能是英文、數字 +i18n.configure_run_path_property.356c=請配置運行路徑屬性【jpom.path】 +i18n.node_script_template_log.85e3=節點腳本模板日誌 +i18n.compression_success.80b3=壓縮成功 +i18n.initialize_workspace.bc97=初始化{}工作空間 +i18n.login_name_cannot_contain_chinese_and_special_characters.48a8=登錄名不能包含漢字並且不能包含特殊字符 +i18n.cannot_join_cluster_as_role.01d4=不能以 {} 角色加入集羣 +i18n.not_an_enumeration.8244=不是枚舉 +i18n.script_not_exist.b180=對應腳本已經不存在啦 +i18n.already_offline.d3b5=已經離線啦 +i18n.rebuild_success.5938=重建成功 +i18n.global_workspace_variable_edit_in_system_management.58d2=全局工作空間變量請到系統管理修改 +i18n.project_management.4363=項目管理 +i18n.node_service_not_running.ad89=【{}】節點的【{}】項目{}已經沒有運行 +i18n.node_script_template_title.4e74=節點腳本模版 +i18n.no_build_record.66a2=沒有對應的構建記錄 +i18n.node_exception.bca7=節點異常: +i18n.no_distribution_project.d4d1=沒有分發項目 +i18n.missing_package_in_root_dir.8bab=一級目錄沒有%s包,請先到文件管理中上傳程序的%s +i18n.exported_project_data.fd1f=導出的項目數據 +i18n.permission_distribution_config_error.e7fb=權限分發配置錯誤:{} {} +i18n.node_info.2dcf=節點信息 +i18n.delete_success_with_colon.d44a=刪除成功\: +i18n.delete_table_data.c813=刪除表 {} 中 {} 條工作空間id為:{} 的數據 +i18n.socket_exception.d836=socket 異常 +i18n.file_does_not_exist_for_download.8dd6=文件不存在,無法下載 +i18n.jpom_project_maintenance_system.7f8e=Jpom項目運維繫統 +i18n.parameter_error_id_cannot_be_empty.86cc=參數錯誤id不能為空 +i18n.prepare_backup.7970=開始準備備份項目文件:{} {} +i18n.login_log.3fb2=登錄日誌 +i18n.hard_drive_monitoring_error.43e7=硬盤資源監控異常: +i18n.dockerfile_not_found_in_repository.4168=倉庫目錄下沒有找到 Dockerfile 文件\: {} +i18n.login_name_format_incorrect.f789=登錄名格式不正確(英文字母 、數字和下劃線),並且長度必須 {}-{} +i18n.no_asset_machine.c77c=沒有對應的資產機器 +i18n.reconnect_plugin_failure_after_upgrade.73e3=升級後重連插件端失敗\: +i18n.current_system_is_windows.91d1=當前系統為:windows +i18n.execute.1a6a=執行 +i18n.unknown_error.84d3=未知: +i18n.monitor_docker_exception_detail.e334=監控 docker[{}] 異常 {} +i18n.database_username_not_configured.a048=未配置(未解析到)數據庫用户名 +i18n.upload_progress_template.ac3f=上傳文件進度\:{}/{} {} +i18n.mark_already_exists.0ccc=標記已存在 +i18n.docker_not_found.2a2e=\ 沒有找到任何 docker。可能docker tag 填寫不正確,需要為 docker 配置標籤 +i18n.download_remote_file_failed.fcc3=下載遠程文件失敗\: +i18n.no_file_found.6f1b=沒有找到 {} 文件 +i18n.distribute_result.a230=分發結果:{} +i18n.multiple_ssh_addresses_found.b3f7=SSH 地址 {} 存在多個數據,將自動合併使用 {} SSH的配置信息 +i18n.no_management_permission.fd25=您沒有對應管理權限\:-2 +i18n.workspace_env_vars.f7e8=工作空間環境變量 +i18n.unsupported_mode.a3d3=暫不支持的模式: +i18n.select_node_and_project.6021=請選擇節點和項目 +i18n.operation_succeeded.3313=操作成功 +i18n.url_length_exceeded.ca1c=url 長度不能超過 200 +i18n.waiting_to_close_process.3634=等待關閉[Process]進程:{} +i18n.node_id.c90a=節點id +i18n.search_result_display.d2c3=在 {} 行中搜索到並顯示 {} 行 +i18n.reset_super_admin_password_success.50c6=重置超級管理員賬號密碼成功,登錄賬號為:{} 新密碼為:{} +i18n.current_address_may_not_be_git.41c6=當前地址可能不是 git 倉庫地址: +i18n.no_trigger_type_specified.5628=沒有對應的觸發器類型: +i18n.user_not_exist.4892=用户不存在 +i18n.cleanup_history_build_failed_retrying.088e=清理歷史構建產物失敗,已經重新嘗試 +i18n.no_jpom_type_config_found.aa57=沒有找到 Jpom 類型配置 +i18n.repository_info_does_not_exist.4142=倉庫信息不存在 +i18n.no_run_found_in_steps.a141=steps 中沒有發現任何 run , run 用於執行命令 +i18n.start_migrating_h2_data_to.f478=開始遷移 h2 數據到 {} +i18n.no_distribution_project_found.90b0=沒有找到對應的分發項目 +i18n.no_branch_name.1879=沒有 branch name +i18n.message_send_failed.4dbe=消息發送失敗,自動移除此會話\:{} +i18n.send_failed.9ca6=發送失敗 +i18n.select_run_mode.5a5d=請選擇運行模式 +i18n.database_backup_label.62d8=數據庫備份 +i18n.auto_start_project_failed.c7b5=自動啟動項目失敗:{} {} +i18n.incorrect_type_passed.d42e=傳入的類型錯誤:{} +i18n.build_trigger_batch_exception.47d5=構建觸發批量觸發異常 +i18n.id_cannot_be_empty.8f2c=id 不能為空 +i18n.webhooks_invocation_error.9792=WebHooks 調用錯誤 +i18n.data_modification_time_format_incorrect.7ffe=數據修改時間格式不正確 {} {} +i18n.cannot_delete_recent_logs.ee19=不能刪除近一天相關的日誌(文件修改時間) +i18n.agent_jar_not_exist.28ac=Agent JAR包不存在 +i18n.parse_certificate_unknown_error.c43c=解析證書發生未知錯誤: +i18n.node_info_incomplete.3b69=對應的節點信息不完整不能繼續 +i18n.ssh_import_template_csv.14fa=ssh導入模板.csv +i18n.build_info.224a=構建信息 +i18n.rename_failed.0c76=重命名失敗\: +i18n.execution_command_required.1cf3=請選擇執行的命令 +i18n.auto_migrate_associated_build_and_repo.0b3f=自動遷移關聯的構建:{} 和 倉庫:{} +i18n.system_logs.84aa=系統日誌 +i18n.machines_ssh_data_fixed.1387=成功修復 {} 條機器 SSH 數據 +i18n.docker_label_required.b690=請填要執行 docker 標籤 +i18n.no_matching_permission.09cf=未匹配到合適的權限不足 +i18n.data_id_does_not_exist.a566=數據id 不存在 +i18n.no_ssh_info.a8ec=沒有對應 SSH 信息 +i18n.distribution_with_build_items_message.45f5=當前分發存在構建項,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.please_pass_body_parameter.4e5c=請傳入 body 參數 +i18n.scanning_in_progress.7444=當前正在掃描中 +i18n.get_decrypt_distribution_failure.4feb=獲取解密分發失敗 +i18n.retention_days.3c7d=,保留天數:{} +i18n.no_log_info.d551=還沒有日誌信息 +i18n.authorized_cannot_be_reloaded.6ece=authorized 不能重複加載 +i18n.private_key_not_found_in_zip.e103=壓縮包裏沒有找到私鑰文件 +i18n.restore_authorization_data_exception.015a=恢復授權數據異常或者沒有選擇授權目錄 +i18n.select_correct_script.ff2d=請選擇正確的腳本 +i18n.get_decrypt_implementation_failure.e77a=獲取解密實現失敗 +i18n.no_machine.89ed=沒有對應的機器 +i18n.modify_success.69be=修改成功 +i18n.publish_project_package_success.b0ce=發佈項目包成功:{} +i18n.service_info_incomplete_with_code3.8612=服務信息不完整不能操作:-3 +i18n.repository_info_error.5b0a=第 {} 行 倉庫信息有誤 +i18n.node_not_enabled.10ef={} 節點未啟用 +i18n.client_id_not_configured.ab8e=沒有配置 clientId +i18n.build_product_dir_not_empty.ba06=構建產物目錄不能為空,長度1-200 +i18n.trigger_token_error_or_expired.8976=觸發token錯誤,或者已經失效 +i18n.auth_info_error.c184=授權信息錯誤 +i18n.download_file_error.5bcd=下載文件異常\: +i18n.rollback_ended.fb1d=執行回滾結束:{} +i18n.reload_project_exception.b566=重載項目異常 +i18n.machine_name_required.e8cf=請填寫機器名稱 +i18n.cumulative_filter_files.448d={} 累積過濾:{} 個文件 +i18n.project_path_already_exists_as_file.a900=項目路徑是一個已經存在的文件 +i18n.decrypt_failure.ad83=解密失敗 +i18n.disallowed_file_format.d6e4=不允許的文件格式 +i18n.operation_monitoring.0cd5=操作監控 +i18n.cron_expression_format_error.6dcd=cron 表達式格式不正確 +i18n.id_already_exists.6208=id已經存在啦 +i18n.ssh_info.ebe6=SSH 信息 +i18n.current_status.81c0=\ 當前還在: +i18n.project_path_no_spaces.263c=項目路徑不能包含空格 +i18n.please_fill_in_address_of.9e02=請填寫 %s 的 地址 +i18n.rsa_private_key_file_invalid.5f12=rsa 私鑰文件不存在或者有誤 +i18n.ssh_node_required.4566=請選擇 ssh 節點 +i18n.folder_or_file_exists.c687=文件夾或者文件已存在 +i18n.parse_error.da6d=\ 解析錯誤\: +i18n.no_corresponding_docker.733e=沒有對應的 docker +i18n.file_published.d1d9=文件發佈 +i18n.java_ext_dirs_cp_required.1f4a=JavaExtDirsCp 模式 javaExtDirsCp必填 +i18n.correct_url_required.67a3=請填寫正確的 url +i18n.pagination_error.6759=篩選的分頁有問題,當前頁碼查詢不到任何數據 +i18n.correction_success.38bc=修正成功 +i18n.project_id_in_use.1adb=當前項目id已經被正在運行的程序佔用 +i18n.import_success_with_count.22b9=導入成功,添加 {} 條數據,修改 {} 條數據 +i18n.file_upload_exception.a5f6=發生文件上傳異常:{} {} +i18n.please_fill_in_correct_maven_version.468c=請填入正確的 maven 版本號,可用的版本如下: +i18n.node_has_log_search_projects_cannot_delete.a388=該節點存在日誌搜索(閲讀)項目,不能 +i18n.copy_success.20a4=複製成功 +i18n.selected_node_required.d65a=至少選擇一個節點 +i18n.docker_already_exists_in_workspace.a0de=對應工作空間已經存在對應的 docker 啦 +i18n.upload_exception_mismatch.0b25=上傳異常,完成數量不匹配 +i18n.multiple_docker_addresses_found.0f82=DOCKER 地址 {} 存在多個數據,將自動合併使用 {} DOCKER 的配置信息 +i18n.download_progress.898a=當前進度:{} ,文件總大小:{},已經下載:{} +i18n.data_not_exist.41f9=對應數據不存在 +i18n.create_folder_failure.b632=創建文件夾失敗(文件夾名可能已經存在啦)\: +i18n.gradle_plugin_version_required.b983=gradle 插件 version 不能為空 +i18n.node_account_required.2d90=請填寫節點賬號 +i18n.publish_project_package_failed.9514=發佈項目包失敗: +i18n.online_agent_close_not_supported.d81d=不支持在線關閉 Agent 進程 +i18n.port_configuration_check.d888=端口號是否配置正確,防火牆規則, +i18n.local_docker_exists.ec31=已經存在本地 docker 信息啦,不要重複添加: +i18n.close_client_session_exception.530a=關閉客户端回話異常 +i18n.start_execution.00d7=開始執行 +i18n.current_operation_not_supported.3aec=不支持當前操作: +i18n.repository_info_cannot_be_empty.67d2=倉庫信息不能為空 +i18n.permission_distribution_config_error_class_not_found.ca67=權限分發配置錯誤:{} {} class not find +i18n.invalid_email_format.7526=郵箱格式不正確 +i18n.load_plugin.1f64=加載:{} 插件 +i18n.host_field_required.5c36=第 {} 行 host 字段不能位空 +i18n.container_build_interrupted.a17b=容器 build 被中斷\: +i18n.upload_action.d5a7=上傳 +i18n.delete_file_failure.041f=刪除文件失敗,請檢查 +i18n.no_cluster_info_found.fb40=沒有找到對應的集羣信息 +i18n.script_template_log.30cb=腳本模板日誌 +i18n.file_name_not_configured.39fa=沒有配置fileName +i18n.ssh_asset_management.3b6c=SSH資產管理 +i18n.operation_succeeded_with_details.c773=操作成功\: +i18n.no_cache_info.fba1=沒有對應的緩存信息 +i18n.save_distribution_project_failed.ceec=保存分發項目失敗 +i18n.download_success_and_distribute.ae94=下載成功,開始分發\! +i18n.start_executing_process.9cb8=開始執行 {}流程 +i18n.select_monitoring_function.c6e4=請選擇監控的功能 +i18n.project_path_auth_required.9e58=項目路徑授權不能為空 +i18n.second_level_directory_cannot_skip_levels.c9fb=二級目錄不能越級: +i18n.ssh_error_or_folder_not_configured.c087=ssh error 或者 沒有配置此文件夾 +i18n.static_directory_auth_cannot_be_under_jpom.8879=靜態目錄授權不能位於Jpom目錄下 +i18n.file_size_exceeds_limit.8272=上傳文件大小超出限制 +i18n.please_fill_in_from.7268=請填寫from +i18n.unsupported_system_type_with_placeholder.d5cc=不支持的系統類型:{} +i18n.configure_correct_cluster_id.5a78=請配置正確的集羣Id,【jpom.clusterId】 +i18n.ssh_not_exist.2e40=不存在對應ssh +i18n.ssl_connection_failed.e26c=SSL 無法連接(請檢查證書信任的地址和配置的 docker host 是否一致)\: +i18n.soft_link_project_does_not_exist.8ad2=被軟鏈的項目已經不存在啦, +i18n.command_name_required.49fa=請輸入命令名稱 +i18n.service_exception.3821=服務異常: +i18n.jpom_log_not_configured.3153=沒有配置 JPOM_LOG +i18n.log_reading.a4c8=日誌閲讀 +i18n.no_corresponding_data_or_permission.1291=沒有對應的數據或者沒有此數據權限 +i18n.no_log_info_or_log_file_error.2c25=還沒有日誌信息或者日誌文件錯誤 +i18n.disable_monitoring.4615=禁用監控 +i18n.account_not_bound_to_any_workspace.fd61=當前賬號沒有綁定任何工作空間,請聯繫管理員處理 +i18n.initialize_user_failure.fe27=初始化用户失敗 +i18n.container_cli_interrupted.b67f=容器cli被中斷\: +i18n.project_data_lost.2ae3=項目數據丟失 +i18n.permission_function_not_configured_correctly.84dd=權限功能沒有配置正確 {} +i18n.execution_exception_with_detail.c142=執行異常:{} +i18n.download_failed.65e2=下載失敗 +i18n.remote_download_host_cannot_be_empty.cdf5=運行遠程下載的 host 不能配置為空 +i18n.backup_old_package_failure_due_to_old_package_absence.53aa=備份舊程序包失敗:{},因為舊程序包不存在 +i18n.no_workspace_found_for_data.ac0f=沒有找到數據對應的工作空間,不能進行操作 +i18n.container_build_host_config_field_not_exist.6f61=容器構建 hostConfig 字段【{}】不存在 +i18n.clear_script_file_failed.f595=清理腳本文件失敗 +i18n.import_save_failure.001a=導入第 {} 條數據保存失敗\:{} +i18n.not_connected.fa55=還沒有連接上 +i18n.unsupported_request_method.45d7=不被支持的請求方式 +i18n.incomplete_data_not_supported.b5d3=數據不完整,暫不支持操作 +i18n.node_delete_project_failed.534c=節點刪除項目失敗 +i18n.wrong_id.ab4d=錯誤的ID +i18n.soft_link_project_does_not_exist.4e4f=軟鏈項目已經不存在啦 +i18n.invalid_zip_file.3092=上傳的壓縮包不是 Jpom [{}] 包 +i18n.publish_command_non_zero_exit_code.ea80=執行發佈命令退出碼非0,{} +i18n.project_path_promotion_issue.2250=項目路徑存在提升目錄問題 +i18n.handle_node_deletion_script_failure_duplicate.821e=處理 {} 節點刪除腳本失敗{} +i18n.no_matching_process_type.b468=未匹配到合適的處理類型 +i18n.unsupported_type.7495=不支持的類型 +i18n.submit_task_queue_success.5f5b=提交任務隊列成功,當前隊列數: +i18n.login_failed_please_enter_correct_password_and_account.03b2=登錄失敗,請輸入正確的密碼和賬號,多次失敗將鎖定賬號 +i18n.no_docker_info_no_need_to_fix_machine_data.f45e=沒有任何 DOCKER 信息,不需要修復機器 DOCKER 數據 +i18n.cluster_info_incomplete_for_operation.ad96=集羣信息不完整,不能操作 +i18n.yml_configuration_content_error.08f8=yml 配置內容錯誤 +i18n.password_cannot_be_empty.89b5=密碼不能為空 +i18n.ssh_not_exist.08a2=SSH不存在 +i18n.file_merge_exception_details.e9d0=文件合併異常 {}\:{} -> {} +i18n.configure_correct_self_hosted_gitlab_address.ad50=請配置正確的自建 gitlab 地址 +i18n.unknown_jsch_log_level.6a5c=未知的 jsch 日誌級別:{} +i18n.no_oauth2_found.ea74=沒有找到對應的 oauth2, +i18n.no_workspace_info.75ae=沒有任何工作空間信息 +i18n.do_not_reinitialize_database.9bb5=不要重複初始化數據庫 +i18n.update_container_service_exception.2249=更新容器服務調用容器異常 +i18n.file_cleanup_failed.511e=清理文件失敗 +i18n.cluster_not_bound_to_group_for_ssh_monitoring.c894=當前集羣還未綁定分組,不能監控 SSH 資產信息 +i18n.container_log_fetch_exception.591a=拉取 容器日誌異常 +i18n.project_path_conflict.8c6f=項目路徑和【{}】項目衝突\:{} +i18n.cannot_delete_default_workspace.0c06=不能刪除默認工作空間 +i18n.operation_failed_with_details.7280=操作失敗\: +i18n.project_type_not_supported_for_startup.7bd1=當前項目類型不支持啟動 +i18n.no_changes_in_repository_code.b1aa=倉庫代碼沒有任何變動終止本次構建:{} +i18n.content_cannot_be_empty.9f0d=內容不能為空 +i18n.remote_repository_does_not_exist.7009=當前地址遠程不存在倉庫: +i18n.please_fill_in_host.7922=請填寫host +i18n.no_type_specified.8c65=沒有對應類型: +i18n.distribute_name_cannot_be_empty.0637=分發名稱不能為空 +i18n.repository_password_cannot_be_empty.20b3=倉庫密碼不能為空 +i18n.delete_file_failure_with_full_stop.6c96=刪除文件失敗: +i18n.distribution_not_exist.cf8a=對應的分發不存在 +i18n.user_or_group_bindings_exist_in_workspace.d57b=當前工作空間下還綁定着用户(權限組)信息 +i18n.user_account.cbf7=用户賬號 +i18n.not_logged_in.c89f=當前未登錄不能操作此數據 +i18n.current_system_is_mac.0139=當前系統為:mac +i18n.docker_certificate_migrated.b3d3=docker[{}] 證書成功遷移到證書管理中 +i18n.certificate_type_not_found.6706=沒有證書類型 +i18n.no_permission_to_execute_command.04d4=沒有執行相關命令權限 +i18n.type_error.395f=類型錯誤 +i18n.node_service_stopped_successful_restart.603b=【{}】節點的【{}】項目{}已經停止,已經執行重啟操作,結果成功 +i18n.git_reset_hard_failed_status_code.d818=git reset --hard失敗狀態碼\: +i18n.no_certificate_files_found.ff6d=沒有找到任何證書文件 +i18n.introducing_script_content.a55b=引入腳本內容:{}[{}] +i18n.script_cannot_be_empty.f566=腳本不能為空 +i18n.user_permission_group.52a4=用户權限組 +i18n.python3_plugin_version_required.a0ce=python3 插件 version 不能為空 +i18n.update_condition_not_found.0870=沒有更新條件 +i18n.repository_id_cannot_be_empty.a42c=倉庫ID不能為空 +i18n.workspace_required.b3bd=請選擇工作空間 +i18n.get_container_log_interrupted_message.83a5=獲取容器日誌被中斷\: +i18n.trigger_auto_execute_ssh_command_template_exception.7451=觸發自動執行SSH命令模版異常 +i18n.start_syncing_to_file_management_center.0a03=開始同步到文件管理中心{} +i18n.delete_success.0007=刪除成功 +i18n.select_correct_pre_publish_script.d230=請選擇正確的發佈前腳本 +i18n.greeting.5ecd=您好,Jpom +i18n.ssh_connection_failed.4719=ssh連接失敗 +i18n.oauth2_redirect_failed.6dcd=跳轉 oauth2 失敗,{} {} +i18n.data_workspace_mismatch.ae1d=數據工作空間和操作工作空間不一致 +i18n.process_file_event_exception.e8e6=處理文件事件異常 +i18n.current_docker_cluster_has_no_management_nodes_online.56cd=當前 {} docker 集羣沒有管理節點在線 +i18n.machine_docker_info.9914=機器DOCKER信息 +i18n.manual_cancel_distribution.7bf6=手動取消分發 +i18n.correct_information_required.5e12=請輸入正確的信息 +i18n.cluster_status_code_exception.9d89=集羣狀態碼異常:{} {} +i18n.restart_operation.5e3a=執行重啟操作 +i18n.no_h2_data_info_for_migration.5799=沒有 h2 數據信息不用遷移 +i18n.publish_success.2fff=發佈成功 +i18n.system_cache.c4a8=系統緩存 +i18n.distribution_machine_required.5921=請選擇分發的機器 +i18n.build_call_container_exception.6e04=構建調用容器異常 +i18n.process_killed_successfully.a4c3=成功kill +i18n.build_name_not_empty.4154=構建名稱不能為空 +i18n.auto_clear_data_errors.112f=自動清除數據錯誤 {} {} +i18n.publish_directory_is_empty.79c6=發佈目錄為空 +i18n.file_or_directory_not_found.f03e=文件不存在或者是目錄\: +i18n.clear_file_cache_failed.5cd1=清空文件緩存失敗 +i18n.file_downloading_status.c995=文件下載中: +i18n.system_IP_authorization.9c08=系統配置IP授權 +i18n.node_failed.20d5=節點失敗: +i18n.project_has_node_distribution_cannot_delete.41b0=當前項目存在節點分發,不能直接刪除 +i18n.oshi_system_monitoring_exception.5c1c=oshi 系統監控異常 +i18n.empty_folder_cannot_be_packed.5a75=文件夾為空,不能打包 \# +i18n.backup_file_not_exist.9628=備份文件不存在 +i18n.auto_migrate_associated_build.a060=自動遷移關聯的構建:{} +i18n.node_upgrade_failed.4493=節點升級失敗: +i18n.current_docker_offline.a509=當前 {} docker 不在線 +i18n.protocol_field_value_error.2b41=第 {} 行 protocol 字段值錯誤(http/http/ssh) +i18n.invalid_file_type.7246=上傳的文件不是 zip +i18n.database_exception.4894=數據庫異常 +i18n.clear_success_message.51f4=清除成功 +i18n.data_not_supported_for_sorting.5431=當前數據不支持排序 +i18n.command_non_zero_exit_code.a6e1=執行命令退出碼非0,{} +i18n.table_info_configuration_error.b050=表信息配置錯誤 +i18n.close_session_exception_with_detail.85f0=關閉會話異常:{} +i18n.auto_delete_expired_build_history_files.723b=自動刪除過期的構建歷史相關文件:{} {} +i18n.build_product_file_sync_failed.0e64=構建產物文件同步到文件管理中心失敗,當前文件已經存文件管理中心存在啦 +i18n.manager_node_not_found.df04=當前集羣未找到管理節點 +i18n.system_restart_cancel_download.444e=系統重啟取消下載任務 +i18n.yml_config_format_error_illegal_field.16ea=yml 配置內容格式有誤請檢查後重新操作(請檢查是否有非法字段): +i18n.delete_failure_with_colon_and_full_stop.bc42=刪除失敗: +i18n.product_directory_cannot_skip_levels.3ad4=產物目錄不能越級: +i18n.fix_null_workspace_data.4d0b=修復工作空間為 null 的數據 {} {} +i18n.soft_link_project_department_exists.fa97=軟鏈的項目部存在 +i18n.docker_info.00d2=docker 信息 +i18n.log_file_does_not_exist.f6c6=日誌文件不存在 +i18n.system_admin_not_found.6f6c=沒有找到系統管理員 +i18n.no_file_info.db01=沒有對應的文件信息 +i18n.record_operation_log_exception.8012=記錄操作日誌異常 +i18n.corresponding_file_required.57b3=請選擇對應到文件 +i18n.build_command_no_delete.df52=構建命令不能包含刪除命令 +i18n.file_merge_error.f32f=文件合併後異常,文件不完成可能被損壞 +i18n.script_info_not_found.bd8d=找不到對應的腳本信息 +i18n.cannot_modify_own_info.4036=不能修改自己的信息 +i18n.modify_db_password_must_restart.d08d=修改數據庫密碼必須重啟 +i18n.ssh_already_bound_to_other_node.2d4e=對應的SSH已經被其他節點綁定啦 +i18n.not_jpom_package.ea3e=此包不是Jpom【{}】包 +i18n.auth_failed.2765=授權失敗\: +i18n.node_name.b178=節點名稱 +i18n.current_system_is_linux.e377=當前系統為:linux +i18n.project_id_cannot_contain_spaces.251d=項目Id不能包含空格 +i18n.start_publishing_file.a14e=開始發佈文件 +i18n.checkout_version.a586=把版本:%s check out +i18n.check_docker_url_exception.4302=檢查 docker url 異常 {} +i18n.execution_frequency.d014={} 秒執行一次 +i18n.parse_jar.a26e=解析jar +i18n.jpom_verification_code.5b5b=Jpom 驗證碼 +i18n.please_fill_in_personal_token.970a=請填寫個人令牌 +i18n.parent_table_info_config_error.2f52=父級表信息配置錯誤, +i18n.physical_node_pull_records.99df={} 物理節點拉取到 {} 個執行記錄,更新 {} 個執行記錄 +i18n.login_success.71fa=登錄成功 +i18n.clear_temp_file_failed_check_directory.7340=清除臨時文件失敗,請檢查目錄: +i18n.incorrect_repository_credentials.f1c8=倉庫賬號或者密碼錯誤: +i18n.request_failed_message.9c71=請求失敗\: status\: %s body\: %s headers\: %s +i18n.send_alert_error.cd38=發送報警信息錯誤 +i18n.binding_success.1974=綁定成功 +i18n.docker_not_exist.7ed8=不存在對應的 docker +i18n.monitored_directory_does_not_exist.fa4e=被監控的目錄不存在忽略創建監聽器:{} +i18n.no_distribution_exists.4425=不存在分發 +i18n.old_version_project_logs_exist_while_running.75ab=存在舊版項目日誌但項目在運行中需要停止運行後手動遷移:{} {} +i18n.data_backup.9e26=數據備份 +i18n.ssh_monitor_execution_error.2d3c={} ssh 監控執行存在異常信息:{} +i18n.download_file_size.d4de=下載成功文件大小: +i18n.user_login_log.0c00=用户登錄日誌 +i18n.cluster_address_check_exception.cd92=填寫的集羣地址檢查異常,請確認集羣地址是正確的服務端地址, +i18n.no_shard_id_info.30f8=沒有分片 id 信息 +i18n.socket_error.18c1=socket 錯誤 +i18n.synchronization_node_failure.8a2c=同步節點 {} 失敗 {} +i18n.build_thread_pool_rejected_task.3bad=構建線程池拒絕了未知任務:{} +i18n.need_configure_absolute_path.f2e6=需要配置絕對路徑: +i18n.auto_start_timed_task_message.9637={} 定時任務已經自動啟動\:{} +i18n.need_execute_callbacks.b708=需要執行 {} 個回調 +i18n.protocol_type_not_supported2.e519=不支持的協議類型 +i18n.parameter_error_id_error.58ce=參數錯誤id error +i18n.unsupported_type_with_placeholder.71a2=不支持的類型:{} +i18n.publish_method_format.4622=發佈的方式:{} +i18n.ssh_bound_to_node_message.7b64=當前ssh被節點綁定,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.send_alert_notification_exception.6788=發送報警通知異常 +i18n.container_build_host_config_conversion_failure.27aa=容器構建 hostConfig 參數 {} 轉換失敗:{} +i18n.build_task_count_and_queue_count.f0b6=當前構建中任務數:{},隊列中任務數:{} {} +i18n.node_cache.d68c=節點緩存 +i18n.cluster_node_not_in_system.0645=當前集羣對應的節點,不在本系統中無法退出集羣 +i18n.file_search_failed.231b=文件搜索失敗 +i18n.execution_ended_with_duration.a59b=執行結束 {}流程,耗時:{} +i18n.ssh_monitor_info_result.a660={} ssh 監控信息結果:{} {} +i18n.certificate_serial_number_not_found.c8d1=沒有證書序列號 +i18n.configure_table_name.f6fd=請配置 table Name +i18n.project_has_monitoring_items_cannot_migrate.c7f6=當前項目存在監控項,不能直接遷移 +i18n.date_format_error.3d1c=日期格式錯誤\: +i18n.unexpected_exception_with_details.247d=發生異常:{} {} +i18n.static_directory_not_configured.acbc=未配置靜態目錄 +i18n.exported_repo_data.bac5=導出的 倉庫信息 數據 +i18n.current_cluster_is_bound_to_workspace_cannot_be_deleted_directly.94c2=當前集羣還被工作空間綁定,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.repository_import_template.5e2d=倉庫信息導入模板.csv +i18n.execution_node_required.d747=請選擇執行節點 +i18n.program_error_null_pointer.12e1=程序錯誤,空指針 +i18n.delete_build_cache.c7f3=刪除構建緩存 +i18n.system_interruption.e37c=系統中斷異常 +i18n.stop_running.1d4e=停止運行 +i18n.selected_weekday_incorrect.4cd4=選擇的周幾不正確 +i18n.node_has_monitoring_items_cannot_delete.0304=該節點存在監控項,不能 +i18n.existing_project_cannot_be_soft_link.aa5a=已經存在的項目不能修改為軟鏈項目 +i18n.git_fetch_failed_status_code.5187=git fetch失敗狀態碼\: +i18n.ssh_folder_creation_exception.6ed2=ssh創建文件夾異常 +i18n.ssh_batch_command_execution_exception.029a=ssh 批量執行命令異常 +i18n.content_type_not_supported.81a9=不支持的 contentType +i18n.cloud_server_network_issues.a865=雲服務器的安全組配置等網絡相關問題排查定位。 +i18n.configure_correct_redirect_url.058e=請配置正確的重定向 url +i18n.no_cache_info_with_minus_one.52f2=沒有對應的緩存信息:-1 +i18n.no_node_entry_found.b1ef=沒有找到對應的節點項:{} +i18n.execution_interrupted_message.2597=執行被中斷:{} +i18n.build_not_exist.c2ac=不存在對應的構建 +i18n.demo_account_not_support_reset_password.a595=演示賬號不支持重置密碼 +i18n.build_command_execution.a55c=執行構建命令 +i18n.no_docker_info_found.6d38=沒有找到對應的 docker 信息 +i18n.update_docker_machine_id_failed.063d=更新 DOCKER 表機器id 失敗: +i18n.build_status_abnormal.8ca1=構建狀態異常或者被取消 +i18n.node_connection_failure.896d=節點連接失敗,請檢查節點是否在線 +i18n.login_name_already_exists.2511=登錄名已經存在 +i18n.port_error.312e=端口錯誤 +i18n.account_disabled.9361=賬號已經被禁用,不能使用 +i18n.file_does_not_exist_anymore.2fab=文件已經不存在啦 +i18n.system_error.9417=系統錯誤\! +i18n.file_type_not_supported_with_placeholder.db22=不支持的文件類型\:{} +i18n.suffix_cannot_be_empty.ec72=允許編輯的文件後綴不能為空 +i18n.read_system_parameter_exception.ee72=讀取系統參數異常 +i18n.check_docker_cert_exception.8042=檢查 docker 證書異常 {} +i18n.ssh_terminal.ec50=SSH終端 +i18n.batch_trigger_project_exception.3c28=項目批量觸發異常 +i18n.data_name_label.5a14=數據名稱 +i18n.cannot_create_config_file_in_environment.55bb=當前環境不能創建配置文件 +i18n.ssh_file_manager.1482=SSH文件管理 +i18n.i18n_node_already_exists.632d=對應的節點已經存在拉 +i18n.address_not_configured.f2eb=未配置地址 +i18n.migration_success.b20d={} 遷移成功 {} 條數據 +i18n.no_corresponding_file.97b5=沒有對應文件 +i18n.please_fill_in_name.52f3=請填寫 名稱 +i18n.unable_to_access_node_network.4e09=無法訪問節點網絡(未知的名稱或服務),請檢查主機名或者 DNS 是否可用。 +i18n.ssh_already_exists_in_workspace.569e=對應工作空間已經存在該 ssh 啦\: +i18n.update_operation_log_failed.d348=更新操作日誌失敗 +i18n.cron_expression_incorrect.b41a=cron 表達式不正確, +i18n.script_template_id_required.f339=請填寫腳本模板id +i18n.start_rolling_back.f020=開始回滾:{} +i18n.execution_exception.b0d5=執行異常 +i18n.corresponding_function.5bb5=對應功能【{}-{}】 +i18n.plugin_parameter_incorrect.a355=插件端使用參數不正確 +i18n.operation_failed.3d94=操作失敗 +i18n.start_publishing.c0b9=開始發佈中 +i18n.monitor_info.f299=監控信息 +i18n.node_already_exists.28ea=對應的節點已經存在啦 +i18n.request_needs_decoding.d4d7=當前請求需要解碼:{} +i18n.no_parameters_added.1721=沒有添加任何參數 +i18n.repo_already_exists.38a3=已經存在對應的倉庫信息啦 +i18n.publish_script_exit_code.0f69=執行發佈腳本的退出碼是:{} +i18n.method_not_supported.90c4=當前方法不被支持,暫時不能使用 +i18n.general_execution_exception.62e9=執行異常: +i18n.distribute_info_error_no_projects.e75f=分發信息錯誤,沒有任何項目 +i18n.unknown_prune_type.0931=pruneType 未知 +i18n.error_sql.15ff=錯誤 sql\:{} +i18n.verification_method_not_configured.7358={}未配置驗證方法:{} +i18n.local_git_certificate_not_supported.b395=暫時不支持本地 git 指定證書拉取代碼 +i18n.unsupported_method_with_colon.eae8=不支持的方式: +i18n.current_docker_has_no_cluster_info.0b52=當前 docker 沒有集羣信息 +i18n.cannot_delete_self.fec9=不能刪除自己 +i18n.operation_monitoring_error.8036=執行操作監控錯誤 +i18n.alias_or_token_error.d5c6=別名或者token錯誤,或者已經失效 +i18n.delete_old_package.ca95=刪除舊程序包:{} +i18n.table_info_configuration_error_message.6452=表信息配置錯誤, +i18n.login_name_already_taken.5b46=當前登錄名已經被系統佔用 +i18n.auto_backup_h2_database.2ed0=自動備份 h2 數據庫文件,備份文件位於:{} +i18n.verification_code_disabled.349b=驗證碼已禁用 +i18n.unsupported_type_with_colon2.7de2=不支持的類型: +i18n.node_service_stopped_failed_restart.4307=【{}】節點的【{}】項目{}已經停止,已經執行重啟操作,結果失敗 +i18n.associated_workspace.885b=所屬工作空間 +i18n.auto_clear_machine_node_stats_logs.5279=自動清理 {} 條機器節點統計日誌 +i18n.unsupported_mode.501d=不支持的模式 +i18n.missing_script_message.af89=找不到對應的腳本 +i18n.start_migrating.20d6=開始遷移 {} {} +i18n.incompatible_database_version.8f7b=數據庫版本不兼容,需要處理跨版本升級。 +i18n.admin_account_required.31e0=系統中的系統管理員賬號數量必須存在一個以上 +i18n.process_does_not_exist.4e39=流程不存在 +i18n.no_ssh_commands_to_execute_after_publish.89ba=沒有需要執行發佈後的ssh命令 +i18n.correct_remote_address_required.0ce1=請輸入正確的遠程地址 +i18n.comparison_data_not_found.413e=沒有要對比的數據 +i18n.invalid_remote_address_format.7f32=配置的遠程地址不規範,請重新填寫: +i18n.docker_console_connection_timeout.b2c7=docker 控制枱連接超時 +i18n.send_success.9db9=發送成功 +i18n.start_distribution.bce5=開始分發 +i18n.heartbeat_message_forwarding_failed.89cc=心跳消息轉發失敗 {} {} +i18n.asset_machine_node_statistics.4a03=資產機器節點統計 +i18n.repository_does_not_exist.3cdb=倉庫不存在 +i18n.select_correct_build_method.84c4=請選擇正確的構建方式 +i18n.project_file_manager.c8cb=項目文件管理 +i18n.no_ssh_info_no_need_to_fix_machine_data.0946=沒有任何ssh信息,不需要修復機器 SSH 數據 +i18n.data_id_already_exists.28b6=數據Id已經存在啦:{} \: {} +i18n.event_script_interrupted.8c79=事件腳本中斷: +i18n.configure_user_notification.250d=請配置用户通知 +i18n.operation_status_code.8231=操作狀態碼 +i18n.agent_jar_damaged.74a8=Agent JAR 損壞請重新上傳, +i18n.read_error.7fa5=讀取錯誤 +i18n.network_resource_monitoring_error.4ede=網卡資源監控異常: +i18n.select_workspace_to_modify.ac87=請選擇要修改的工作空間 +i18n.decode_failure.822e=解碼失敗 +i18n.restart_failed.f92a=重啟失敗 +i18n.login_name_already_taken_message.b01f=當前登錄名已經被系統佔用啦 +i18n.forbidden_operation_time.d83d=【禁止操作】當前時段禁止執行 +i18n.log_file_not_found.7f2e=沒有日誌文件\: +i18n.protocol_type_not_supported.7a66=不支持到協議類型 +i18n.execution_result_file_not_found_in_container.cf18=容器中沒有找到執行結果文件\: {} +i18n.execution_exception_with_flow.6d4b=執行異常[{}]流程:{} +i18n.trigger_success.f9d1=觸發成功 +i18n.start_pulling.57ab=開始拉取 +i18n.publish_task_execution_exception.c296=執行發佈任務異常 +i18n.no_node_specified.fa3d=沒有對應的節點: +i18n.no_corresponding_command.165e=沒有對應對命令 +i18n.exclusion_success.7d46=剔除成功 +i18n.oauth2_not_enabled.c8b7=沒有開啟此 {} oauth2 +i18n.cannot_delete_online_cluster.11ad=不能刪除在線的集羣 +i18n.no_corresponding_script_info_or_global_script_selected.765b=沒有對應到腳本信息或者選擇全局腳本 +i18n.select_file.9feb=請選擇文件 +i18n.project_has_build_items_cannot_delete.c2df=當前項目存在構建項,不能直接刪除 +i18n.data_id_label.81b6=數據id +i18n.key_field_not_configured.7b22=沒有配置 KEY 字段, +i18n.upgrade_duration_message.bab4=升級(重啟)中大約需要30秒~2分鐘左右 +i18n.git_submodule_update_failed_status_code.2218=git submodule update 失敗狀態碼\: +i18n.project_id_not_found.b87e=沒有項目id +i18n.operation_type.de9c=操作類型 +i18n.project_has_logs_cannot_migrate.2e0e=當前項目存在日誌閲讀,不能直接遷移 +i18n.delete_failure_with_colon.b429=刪除失敗\: +i18n.tag_cannot_contain_colon.f9ae=標籤不能包含 : +i18n.distribute_id_already_exists_globally.6478=分發id已經存在啦,分發id需要全局唯一 +i18n.import_exception.04b6=導入第 {} 條數據異常\:{} +i18n.script_template_log2.6b2c=腳本模版日誌 +i18n.id_is_empty.3bbf=id 為空 +i18n.mfa_incorrect_code.8783=\ mfa 驗證碼不正確 +i18n.parameter_parsing_exception.0056=參數解析異常\:{} +i18n.file_upload_failure_due_to_missing_chunks.1865=文件上傳失敗,存在分片丟失的情況, {} \!\= {} +i18n.root_path.1396=根路徑 +i18n.node_not_exist.0027=不存在對應的節點 +i18n.public_key_or_private_key_does_not_exist.dc0d=公鑰或者私鑰不存在 +i18n.no_user_info.0355=沒有對應的用户信息 +i18n.correct_verification_code2_required.df13=請輸入正確驗證碼 +i18n.please_fill_in_correct_python3_version.abb1=請填入正確的 python3 版本號 +i18n.incorrect_account_credentials_or_unsupported_auth.1ef9=賬號密碼不正確或者不支持的身份驗證, +i18n.file_already_exists.d60c=當前文件已經存在啦,請勿重複上傳 +i18n.start_executing_pre_release_command.6c7e=開始執行 {} 發佈前命令 +i18n.publish_to_ssh_directory_required.56a6=請輸入發佈到ssh中的目錄 +i18n.config_path_exceeds_length_limit.f684=配置路徑超過{}長度限制\:{} +i18n.cluster_response_incorrect.c08a=集羣響應信息不正確,請確認集羣地址是正確的服務端地址 +i18n.monitor_docker_timeout.b03b=監控 docker[{}] 超時 {} +i18n.project_soft_linked_by.8556=項目被{}軟鏈中 +i18n.log_recorder_error_message.ee3e=日誌記錄器被關閉/或者未啟用 +i18n.distribution_project_required.2560=請選擇分發項目 +i18n.test_result.8441=測試結果:{} {} +i18n.environment_variable.3867=環境變量 +i18n.unsupported_mode_with_colon.c6de=不支持的模式: +i18n.message_conversion_exception.cce8=消息轉換異常 +i18n.oauth2_login_failure.3841=OAuth 2 登錄失敗,平台賬號不符合本系統要求 +i18n.node_did_not_pull_anything.8af5=節點沒有拉取到任何 +i18n.project_has_monitoring_items_cannot_delete.c9a3=當前項目存在監控項,不能直接刪除 +i18n.compression_type_not_supported.9dea=不支持的壓縮類型, +i18n.cache_plugin_path_required.2093=cache 插件 path 不能為空 +i18n.data_file_content_error.e86f=數據文件內容錯誤,請檢查文件是否被非法修改: +i18n.parameter_error_path_error.f482=參數錯誤path錯誤 +i18n.script_template_not_exist.e05f=腳本模版不存在\: +i18n.execute_event_script_error.7c69=執行事件腳本錯誤 +i18n.associated_group2_required.bd05=請選擇關聯的分組 +i18n.gradle_plugin_depends_on_java.2bb3=gradle 插件依賴 java , 使用 gradle 插件必須優先引入 java 插件 +i18n.no_server_management_permission.ee19=您沒有服務端管理權限\:-2 +i18n.no_static_directory_configured.d3c0=當前沒有配置靜態目錄,自動取消定時任務 +i18n.distribute_log.c612=分發日誌 +i18n.trigger_result.364e=[{}]-{}觸發器結果:{} +i18n.no_parameters_added_with_minus_two.a7cf=沒有添加任何參數\:-2 +i18n.distribute_id_requirements.9c63=分發id 不能為空並且長度在2-20(英文字母 、數字和下劃線) +i18n.repository_authorization_error.4f50=倉庫授權信息錯誤 +i18n.ssh_rename_failed_exception.94aa=ssh重命名失敗異常 +i18n.associated_data_and_exist_in_workspace.5fa7=當前工作空間下還存在關聯:{} 和 {} 數據 +i18n.delete_log_file_failure.bf0b=刪除日誌文件失敗 +i18n.content_format_error_with_detail.c846=內容格式錯誤,請檢查修正\: +i18n.project_log_storage_path_required.d0bb=請填寫的項目日誌存儲路徑,或者還沒有配置授權 +i18n.forbidden_operation_range.247f=【禁止操作】{} {} 至 {} +i18n.please_do_not_delete_this_file.0a7f=請勿刪除此文件,刪除後關聯 id 將失效 +i18n.strict_execution_mode_event_script_error.c82a=嚴格執行模式,事件腳本返回狀態碼異常, +i18n.file_missing_cannot_publish.3818=當前文件丟失不能執行發佈任務 +i18n.log_file_does_not_exist_or_error.a0e7=日誌文件不存在或者錯誤 +i18n.build_task_waiting.e303=構建任務繼續等待\:{} {} +i18n.no_manager_node_found.5934=當前集羣未找到任何管理節點 +i18n.file_transfer_exception.bda6=轉發文件異常 +i18n.select_node.f8a6=請選擇節點 +i18n.upload_progress_with_units.44ad=上傳文件進度\:{} {}/{} {} +i18n.ssh_terminal_execution_log.58f1=ssh 終端執行日誌 +i18n.project_monitor.d2ff=項目監控 +i18n.push_image_interrupted.6377=push image 被中斷\: +i18n.default_value.1aa9={} [默認] +i18n.read_additional_variables.5eb0=讀取附加變量:{} {} +i18n.node_transfer_info_encoding_exception.12c8=節點傳輸信息編碼異常\: +i18n.user_operation_log.2233=用户操作日誌 +i18n.handle_node_deletion_script_failure.071b=處理 {} 節點刪除腳本失敗 {} +i18n.ssh_terminal_log.775f=SSH終端日誌 +i18n.query_data_error.45e7=查詢數據錯誤 +i18n.build_image_call_container_exception.7e13=構建鏡像調用容器異常 +i18n.machines_docker_data_fixed.af8a=成功修復 {} 條機器 DOCKER 數據 +i18n.project_name.31ec=項目 +i18n.load_success.154e=加載成功 +i18n.start_checking_backup_project_files.baa7=開始檢查備份項目文件:{} {} +i18n.no_script_template_specified.7d14=沒有對應腳本模板\: +i18n.push_image_container_exception.2090=推送鏡像調用容器異常 +i18n.ssh_with_build_items_message.0f6d=當前ssh存在構建項,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.ssh_item_distribution_required.2884=請選擇分發SSH項 +i18n.file_name_already_exists.0d4e=文件名已經存在拉 +i18n.waiting_to_start.b267=等待開始\: +i18n.no_corresponding_data.4703=沒有對應的數據 +i18n.info_to_retrieve_not_found.96d7=沒有要獲取的信息 +i18n.http_proxy_address_unavailable.b3f2=HTTP代理地址不可用\: +i18n.maven_plugin_depends_on_java.23f8=maven 插件依賴 java , 使用 maven 插件必須優先引入 java 插件 +i18n.join_beta_program.5c1f=是否加入 beta 計劃 +i18n.cluster_does_not_exist.97a4=當前集羣不存在 +i18n.publish_command_contains_forbidden_command.097d=發佈命令中包含禁止執行的命令 +i18n.close_exception.5b86=關閉異常 +i18n.no_project_info_found.725a=沒有找到對應的項目信息 +i18n.upgrade_database_process.e604=升級數據庫流程: +i18n.no_type.9153=沒有對應的類型 +i18n.user_operation_alarm.15b9=用户操作報警 +i18n.start_executing_build_task.a5ac=開始執行構建任務,任務等待時間:{} +i18n.event_type_not_supported.e9c3=不支持的事件類型:{} +i18n.user_directory_not_found_private_key_info.6ce4=用户目錄沒有找到私鑰信息 +i18n.upload_progress_message_format.b91c=上傳文件進度:{}[{}/{}] {}/{} {} +i18n.download_failed_generic.be4f=下載文件失敗 +i18n.oshi_network_card_monitoring_exception.6d41=oshi 網卡資源監控異常 +i18n.connection_successful.b331=連接成功 +i18n.incomplete_upload_info_now_slice.34aa=上傳信息不完成:nowSlice +i18n.account_locked_cannot_change_password.d6ab=當前賬號被鎖定中,不能修改密碼 +i18n.no_publish_distribution_related_data_id.a077=沒有發佈分發對應關聯數據ID +i18n.build_failed.a79a=構建失敗\: +i18n.delete_ssh_temp_file_failure.6e5f=刪除 ssh 臨時文件失敗 +i18n.branch_required.5095=請選擇分支 +i18n.trigger_token.abe6=觸發器 token +i18n.user_binding_warning.16b0=當前權限組還綁定用户,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.account_already_bound_to_mfa.5122=當前賬號已經綁定 mfa 啦 +i18n.compare_files_result.bec4=對比文件結果,產物文件 {} 個、需要上傳 {} 個 +i18n.content_format_error.ce15=內容格式錯誤,請檢查修正 +i18n.login_name_cannot_be_empty.9a99=登錄名不能為空 +i18n.correct_host_required.8c49=請填寫正確的 host +i18n.select_correct_project_path_or_no_auth_configured.366a=請選擇正確的項目路徑,或者還沒有配置授權 +i18n.need_initialize_system.fb62=需要初始化系統 +i18n.not_logged_in.6605=沒有登錄 +i18n.old_and_new_passwords_match.55b4=新舊密碼一致 +i18n.super_admin_cannot_reset_password_this_way.0761=超級管理員不能通過此方式重置密碼 +i18n.protocol_required.b4f8=請選擇協議 +i18n.incorrect_ip_address.b872=ip 地址信息不正確 +i18n.main_class_not_found.8a12=沒有找到運行的主類 +i18n.data_creation_time_format_incorrect.7772=數據創建時間格式不正確 {} {} +i18n.delete_backup_data_file_failure.2ebf=刪除備份數據文件失敗 +i18n.user_custom_workspace.ef93=用户自定義工作空間 +i18n.editable_suffixes_not_configured.5b41=沒有配置可允許編輯的後綴 +i18n.invalid_repository_info.b4ad=無效的倉庫信息 +i18n.login_JPOM.0de6=登錄JPOM +i18n.ssh_does_not_exist_with_message.de6c=對應的 ssh 不存在 +i18n.not_running.4f8a=未運行 +i18n.no_get_id_method.2a65=沒有 getId 方法 +i18n.project_id_keyword_occupied.1cae=項目id {} 關鍵詞被系統佔用 +i18n.delete_log_file_failure_with_colon.d867=刪除日誌文件失敗\: +i18n.manual_cancel.8464=手動取消 +i18n.super_admin_mfa_verification_disabled.b97d=成功關閉超級管理員賬號 mfa 驗證:{} +i18n.prepare_to_build.1830=準備構建 +i18n.unknown_exception_on_pull_code.2b2e=拉取代碼發生未知異常建議清除構建重新操作: +i18n.close_connection_exception.c855=關閉連接異常 +i18n.delete_action.2f4a=刪除 +i18n.configure_monitoring_interval.9741=請配置監控週期 +i18n.restart_self_exception.85b7=重啟自身異常 +i18n.build_finished_duration.7f7c=構建結束-累計耗時\:{} +i18n.handle_node_sync_script_failure.e99f=處理 {} 節點同步腳本失敗 {} +i18n.project_disabled.f8b3=當前項目被禁用 +i18n.certificate_already_exists.adf9=當前證書已經存在啦(系統全局範圍內) +i18n.oshi_file_system_monitoring_exception.dc24=oshi 文件系統資源監控異常 +i18n.image_cannot_be_empty.1600=鏡像不能為空 +i18n.no_corresponding_node_info.cd24=沒有對應到節點信息 +i18n.node_plugin_version_required.2318=node 插件 version 不能為空 +i18n.docker_exec_terminal_process_ended.c734=[{}] docker exec 終端進程結束 +i18n.node_migration_project_failure.d5ff=節點遷移項目失敗 +i18n.cannot_cancel_super_admin_permissions.99b5=不能取消超級管理員的權限 +i18n.distribute_node_configuration_failure.8146=分發 {} 節點配置失敗 {} +i18n.file_upload_failed.462e=上傳文件失敗: +i18n.node_service_resumed_normal_operation.2cbd=【{}】節點的【{}】項目{}已經恢復正常運行 +i18n.file_type_no_restart.0977=file 類型項目沒有 restart +i18n.current_distribution_data_lost.f9f8=當前分發數據丟失 +i18n.deletion_success_message.4359=刪除成功! +i18n.pull_repository_code.3f51=拉取倉庫代碼 +i18n.database_mode_config_missing.ae5d=數據庫Mode配置缺失 +i18n.rsa_private_key_file_error.b687=第 {} 行 rsa 私鑰文件不存在或者有誤 +i18n.protocol_not_supported.b906=不支持的 protocol +i18n.force_unbind_succeeded.5bfd=強制解綁成功 +i18n.unexpected_exception.2b52=發生異常 +i18n.check_email_error.636c=檢查郵箱信息錯誤:{} +i18n.initialization_success.4725=初始化成功 +i18n.running_status.d679=運行中 +i18n.error_message.483d=未找到腳本庫信息\:{},請檢查引用標記是否正確或者腳本是否被刪除 +i18n.check_git_client_exception.42a3=檢查 git 客户端異常 +i18n.update_success.55aa=更新成功 +i18n.initialize_database_failure.2ef9=初始化數據庫失敗 {} +i18n.docker_cluster_associated_workspaces_message.5520=當前 docker 還關聯{}個 工作空間 docker 集羣,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.at_least_one_node_required.a290=請至少選擇一個節點 +i18n.reset_failed.5281=重置失敗: +i18n.no_current_data_permission.17d7=沒有當前數據權限,需要管理員或者數據創建人才操作該數據 +i18n.database_not_initialized.e5e7=還沒有初始化數據庫 +i18n.project_path_occupied.cddd=當前項目路徑已經被【{}】佔用,請檢查 +i18n.configuration_modification_not_supported.5872=當前環境下不支持在線修改配置文件 +i18n.non_dsl_project_unsupported_operation.5c09=非 DSL 項目不支持此操作 +i18n.contact_does_not_exist.3369=聯繫人不存在 +i18n.check_cluster_info_exception.7b0c=檢查集羣信息異常 +i18n.docker_cluster_info.a2eb=docker 集羣信息 +i18n.line_number_error.c65d=行號錯誤 +i18n.node_machine_table_exists_no_need_to_fix.2625=節點機器表已經存在 {} 條數據,不需要修復機器數據 +i18n.execution_exception_message.ef79=執行異常\: +i18n.auto_detect_local_docker_and_add.af72=自動探測到本地 docker 並且自動添加: +i18n.database_event_execution_ended.690b=數據庫 {} 事件執行結束,:{} +i18n.build_history.a05c=構建歷史 +i18n.machine_installation_id.d0b9=本機安裝 ID 為:{} +i18n.no_available_docker_server.6aaa={} 沒有可用的 docker server +i18n.credential_cannot_be_empty.d055=憑證不能為空 +i18n.workspace_label.98d6=工作空間 +i18n.file_modification_event.5bc2=文件修改事件:{} {} +i18n.no_corresponding_build_record_ignore_deletion.86a0=沒有對應構建記錄,忽略刪除 +i18n.cannot_configure_root_path.d86e=不能配置根路徑: +i18n.completed_and_successful_count_insufficient.92fa=完成併成功的個數不足 {}/{} +i18n.get_project_info_failure.ddff=獲取項目信息失敗\: +i18n.name_required.856d=請填寫名稱 +i18n.build_source.2ef9=構建來源, +i18n.joined_beta_program.c4e2=成功加入 beta 計劃 +i18n.recover_abnormal_data.9adf={} 恢復 {} 條異常數據 +i18n.file_name_error_message.7a25=文件名不能包含/ +i18n.config_file_not_exist.09dd=配置文件不存在 +i18n.parent_task_not_found.bac1=沒有找到父級任務 +i18n.static_file_scanning_disabled.2b2b=未開啟靜態文件掃描 +i18n.no_resource_found.dc22=沒有找到對應的資源 +i18n.container_name_cannot_be_empty.14b1=容器名稱不能為空 +i18n.token_parse_failed.cadf=token 解析失敗: +i18n.distribute_exception.da82=分發異常 +i18n.script_content_cannot_be_empty.49be=腳本內容不能為空 +i18n.need_execute_pre_events.b848=需要執行 {} 個前置事件 +i18n.cluster_binding_success.eb7e=集羣綁定成功 +i18n.port_field_required_or_incorrect.8426=第 {} 行 port 字段不能位空或者不正確 +i18n.delete_failure.acf0=刪除失敗 +i18n.prepare_to_migrate_data.f251=準備遷移數據 +i18n.ignore_log_record.48f5=忽略記錄日誌 {} +i18n.need_handle_build_micro_queue_count.3010=需要處理構建微隊列數:{} +i18n.user_management.7d94=用户管理 +i18n.file_directory_too_long.c101=文件目錄長度超過 500 ,自動忽略此類文件:{} +i18n.temporary_result_file_does_not_exist.1c7e=臨時結果文件不存在\: {} +i18n.command_content_required.6005=請輸入命令內容 +i18n.system_parameters.c7b0=系統參數 +i18n.node_address_not_found.f955=沒有節點地址,不能繼續操作 +i18n.verification_code_incorrect_retry.d88d=驗證碼不正確,請重新輸入 +i18n.git_installation_location.7984=git安裝位置:{} +i18n.scheduled_task_exception.f077=定時任務異常 {} +i18n.monitor_docker_exception.e326=監控 docker[{}] 異常 +i18n.restart_result.253f=重啟結果: +i18n.start_building_with_thread_execution.83cd=開始構建,構建線程執行 +i18n.task_ended_successfully.e176=任務正常結束 +i18n.non_existent_build_product.1df4={} 不存在,處理構建產物失敗 +i18n.current_not_supported.78b7=當前不支持: +i18n.no_user.3b69=沒有對應的用户 +i18n.wait_for_seconds.ff7b=執行等待 {} 秒 +i18n.unknown_script_template_or_workspace.27f1=腳本模板或者工作空間未知 +i18n.send_message_failure_prefix.6f8c=發送消息失敗\: +i18n.select_correct_ssh.aa93=請選擇正確的ssh +i18n.synchronization_failed.d610=同步失敗 +i18n.client_secret_not_configured.6923=沒有配置 clientSecret +i18n.workspace_not_exist.a6fd=不存在對應的工作空間 +i18n.no_user_specified.6650=沒有對應的用户: +i18n.backup_database.9524=備份數據庫 +i18n.delete_project_file_failure.f007=刪除項目文件失敗\: +i18n.incorrect_certificate_info.aee1=填寫的證書信息錯誤 +i18n.build_command_not_empty.2e37=構建命令不能為空 +i18n.auto_migrate_exist_backup_logs.dc33=自動遷移存在備份日誌 {} -> {} +i18n.close_session_exception.3491=關閉會話異常 +i18n.login_failure_O_auth2_message.3e91=登錄失敗(OAuth2),請聯繫管理員! +i18n.log_retention_days.99d1=統計日誌保留天數 {} +i18n.env_must_be_map_type.f8ad=env 必須是 map 類型 +i18n.batch_trigger_script_exception.8fb4=服務端腳本批量觸發異常 +i18n.upgrade_failure.4ae2=升級失敗 +i18n.no_branch_or_tag_message.8ae3=沒有 {} 分支/標籤 +i18n.general_error_message.728a=啊哦,好像哪裏出錯了,請稍候再試試吧~ +i18n.service_info_incomplete_with_code2.e9ca=服務信息不完整不能操作:-2 +i18n.ignored_operation.edee=忽略的操作:{} +i18n.monitor_name_cannot_be_empty.514a=監控名稱不能為空 +i18n.get_project_pid_failure.17b0=獲取項目pid 失敗 +i18n.please_fill_in_steps.229d=請填寫 steps +i18n.distribute_success.c689=分發成功 +i18n.unable_to_get_container_execution_result_file.7b2c=無法獲取容器執行結果文件 +i18n.import_data.8ef8=導入數據 +i18n.unsupported_mode_with_script_log.6a7a=不支持的模式,script log +i18n.ssh_connection_failed.74ab=ssh連接失敗,請檢查用户名、密碼、host、端口等填寫是否正確,超時時間是否合理: +i18n.node_name_required.5bdf=請填寫節點名稱 +i18n.client_terminated_connection.6886=客户端終止連接:{} +i18n.close_thread_pool.4cd9=關閉 {} 線程池 +i18n.no_corresponding_folder.621f=沒有對應文件夾 +i18n.no_online_manager_node_found.05d7=當前集羣未找到在線的管理節點 +i18n.command_execution_failed.90ef=執行命令失敗 +i18n.monitor_ssh_timeout.59fd=監控 ssh[{}] 超時 {} +i18n.execution_completed.24a1=執行完畢\: +i18n.close_project_failure.a1d2=關閉項目失敗: +i18n.select_folder_to_compress.915f=請選擇文件夾進行壓縮 +i18n.config_file_not_found.fc87=均未找到配置文件 +i18n.update_ssh_machine_id_failed.bd24=更新 SSH 表機器id 失敗: +i18n.select_pull_code_protocol.fc24=請選擇拉取代碼的協議 +i18n.auth_directory_cannot_be_empty.21ba=授權目錄不能為空 +i18n.status_not_in_progress.f410=當前狀態不在進行中, +i18n.image_tag_required.92cf=請填寫鏡像標籤 +i18n.privacy_variable_cannot_trigger.dbc9=隱私變量不能生成觸發器 +i18n.no_error_data_in_table.3092=當前表沒有錯誤數據 +i18n.update_restore_data.1b0b=更新還原數據:{} +i18n.script_template.1f77=腳本模版 +i18n.certificate_info_incorrect.a950=證書信息不正確,證書壓縮包裏面必須包含:ca.pem、key.pem、cert.pem +i18n.trim_completed_with_recovered_space.0463=修剪完成,總回收空間: +i18n.default_workspace_cannot_delete.18b4=系統默認的工作空間,不能刪除 +i18n.synchronization_node_failure_with_details.8660=同步節點{}失敗\:{} +i18n.email_verification_failed.5863=驗證郵箱信息失敗,請檢查配置的郵箱信息。端口號、授權碼等。 +i18n.permission_group.ea59=權限分組 +i18n.node_exception_null_pointer.d408=節點異常,空指針 +i18n.node_system_logs.3ac9=節點系統日誌 +i18n.parameter_value_required.3a29=請填寫參數值 +i18n.node_connection_lost.b6c7=節點連接丟失或者還沒有連接上 +i18n.associated_data_lost_error.becb=ERROR\:關聯數據丟失 +i18n.ssh_modify_permission_error.0cd3=ssh修改文件權限異常...\: {} {} +i18n.please_fill_in_password.455f=請填寫pass +i18n.fuzzy_match_files.139d={} 模糊匹配到 {} 個文件 +i18n.no_distribution_id_found.8df2=沒有找到對應的分發id +i18n.command_script_not_found_in_service.25ac=當前服務中沒有命令腳本:{}.{} +i18n.search_project.7e9b=搜索項目 +i18n.machine_asset_management.36ea=機器資產管理 +i18n.no_matching_data_found.fe9d=未找到匹配的數據 +i18n.project_console.3a94=項目控制枱 +i18n.script_template_not_exist.1d5b=對應的腳本模版已經不存在拉 +i18n.subnet_mask_incorrect.6c27=子掩碼不正確: +i18n.no_implemented_publish_distribution.fcf8=沒有實現的發佈分發:{} +i18n.h2_connection_successful.11f3=成功連接 H2 ,開始嘗試自動備份 +i18n.container_build_exception.a98f=容器構建異常:{} -> {} +i18n.import_project_template_csv.c6f1=項目導入模板.csv +i18n.handle_message_exception_with_colon.56f0=處理消息異常: +i18n.node_already_exists_in_workspace.9499=對應工作空間已經存在該節點啦\: +i18n.plugin_not_initialized.3ea9=對應的插件端還沒有被初始化 +i18n.no_node_info.6366=沒有任何節點信息 +i18n.associated_ssh_node_contains_nonexistent_node.c7f5=關聯 SSH 節點包含不存在的節點 +i18n.node_statistics.b4e1=節點統計 +i18n.user_workspace_relation_table.851e=用户(權限組)工作空間關係表 +i18n.dsl_info_not_configured.3487=未配置 dsl 信息(項目信息錯誤) +i18n.migration_success_message.e546=項目遷移成功:{} | {} +i18n.node_connection_failed.8497={} 節點連接失敗 {} +i18n.ssh_management.9e0f=SSH管理 +i18n.synchronization_script_exception.9c70=同步腳本異常 +i18n.previous_node_distribution_failure.d556=前一個節點分發失敗,取消分發 +i18n.cleanup_token_exception.760e=執行清理 token[{}] 異常 +i18n.incorrect_node_info_node_does_not_exist.2fd8=節點信息不正確,對應對節點不存在 +i18n.plugin_not_found.a6e5=對應找到對應到插件: +i18n.file_storage_center.6acf=文件存儲中心 +i18n.system_administrator.181f=系統管理員 +i18n.parameter.3d0a=參數 +i18n.ignore_execution_event_script.8872=忽略執行事件腳本 {} {} {} +i18n.login_info_expired_please_re_login.fbbc=登錄信息已經過期請重新登錄 +i18n.clear_build_product_failed.edd4=清除構建產物失敗 +i18n.prepare_to_delete_current_database_file.1e6a=準備刪除當前數據庫文件 +i18n.distribution_in_progress.c3ae=當前還在分發中,請等待分發結束 +i18n.initialization_failure.19e9=初始化失敗\: +i18n.project_exists.f4e0=該節點下還存在項目,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.modify_service_success.bd75=修改服務成功 +i18n.database_connection_not_configured.c80e=沒有配置數據庫連接 +i18n.clear_temp_file_failed_manually.0dad=清除臨時文件失敗,請手動清理: +i18n.reset_log_failure.b3d3=重置日誌失敗 +i18n.type_field_required.7637=第 {} 行 type 字段不能位空 +i18n.migration_target_workspace_node_mismatch.d9cf=要遷移到的目標工作空間和節點不一致 +i18n.start_deleting_files.210c=開始刪除 {} 文件 {} +i18n.download_success.5094=下載成功 +i18n.running_project_cannot_change_path.5888=正在運行的項目不能修改路徑 +i18n.please_fill_in_correct_gradle_version.6e19=請填入正確的 gradle 版本號 +i18n.get_node_monitoring_info_failure.595a=獲取節點監控信息失敗 +i18n.docker_authorization_failed.8ede=docker 授權失敗\:{} +i18n.supported_java_plugin_versions.bd70=目前java 插件支持的版本\: %s +i18n.delete_success_with_cleanup.6155=刪除成功,並且清理歷史構建產物成功 +i18n.cluster_info.32e0=集羣信息 +i18n.file_type_not_supported.ae5d=不支持的文件類型\: +i18n.verification_code_incorrect.d8c0=驗證碼不正確 +i18n.main_class_not_found.b4b7=中沒有找到對應的MainClass\: +i18n.node_not_exist.760e=對應的節點不存在 +i18n.start_distribution_exclamation.9fc2=開始分發\! +i18n.failure_prefix.115a=失敗\: +i18n.connection_closed.6d4e=連接關閉 {} {} +i18n.no_corresponding_workspace_permission.8402=沒有對應的工作空間權限 +i18n.post_distribution_action_required.8cc8=請選擇分發後的操作 +i18n.unsupported_item.bcf4=不支持的: +i18n.no_available_new_version_upgrade.d8f2=沒有可用的新版本升級\:-1 +i18n.docker_associated_workspaces_message.de78=當前 docker 還關聯{}個 工作空間 docker 不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.static_directory_not_configured.9bd6=還未配置靜態目錄 +i18n.server_jps_command_exception.e380=當前服務器 jps 命令異常,請檢查 jdk 是否完整,以及 java 環境變量是否配置正確 +i18n.data_download_failed.9499=數據下載失敗 +i18n.delete_data.40f8=刪除數據 +i18n.image_name_required.ab44=請填寫鏡名稱 +i18n.listen_log_changes.9081=監聽日誌變化 +i18n.configure_correct_auth_url.22e7=請配置正確的授權 url +i18n.current_upload_chunk_info_incorrect.900e=當前上傳的分片信息錯誤 +i18n.listen_log_success_currently_sessions_viewing.a74a=監聽{}日誌成功,目前共有{}個會話正在查看 +i18n.node_script_template.be6a=節點腳本模板 +i18n.post_packaging_action_required.bf66=請選擇打包後的操作 +i18n.multiple_clusters_exist.196b=系統中存在多個集羣,不需要自動綁定數據 +i18n.variable_name_already_exists.70f2=對應的變量名稱已經存在啦 +i18n.node_management.b26d=節點管理 +i18n.account_mfa_not_enabled.fd39=當前賬號沒有開啟兩步驗證 +i18n.exit_successful.8150=退出成功 +i18n.main_class_attribute_not_found.24c9=清單文件中沒有找到對應的MainClass屬性 +i18n.code_pull_conflict.6e8e=拉取代碼發生衝突,可以嘗試清除構建或者解決倉庫裏面的衝突後重新操作。: +i18n.cannot_delete_super_admin.68e2=不能刪除超級管理員 +i18n.node_running_status_abnormal.3160=【{}】節點的運行狀態異常 +i18n.pull_code_failed.70d6=拉取代碼失敗:{} +i18n.execution_succeeded.f56c=執行成功 +i18n.docker_already_exists.d9a5=對應的 docker 已經存在啦 +i18n.build_task_queue_waiting.5f06=構建任務開始進入隊列等待.... +i18n.system_process_monitoring_error.fe1d=系統進程監控異常: +i18n.pull_log_exception.cc3e=拉取日誌異常 +i18n.create_file_watch_failure.bc1a=創建文件監聽失敗 +i18n.no_project_found.ef5e=沒有找到對應的項目 +i18n.delete_project_file_failure_with_full_stop.85b8=刪除項目文件失敗: +i18n.create_plugin_endpoint_connection_failure.30f8=創建插件端連接失敗 {} +i18n.manual_cache_refresh_exception.9d91=手動刷新緩存異常 +i18n.no_script_template_found.0498=沒有找到對應的腳本模板 +i18n.user_field_required.8732=第 {} 行 user 字段不能位空 +i18n.cannot_delete_running_project.e56b=不能刪除正在運行的項目 +i18n.incorrect_parameter.02ce=參數存在不正確 +i18n.update_node_machine_id_failed.51d9=更新節點表機器 id 失敗: +i18n.compare_files_result_with_delete.033d=對比文件結果,產物文件 {} 個、需要上傳 {} 個、需要刪除 {} 個 +i18n.build_trigger_queue_result.a1fe=構建觸發器隊列執行結果:{} +i18n.unsupported_type_with_colon.1050=不支持的類型\: +i18n.no_machine_found.c16c=沒有找到對應的機器 +i18n.admin_email_not_configured.ecb8=管理員還沒有配置系統郵箱,請聯繫管理配置發件信息 +i18n.node_response_error.efc6=\ 節點響應異常,狀態碼錯誤: +i18n.target_database_info_not_specified.2ff6=未指定目標數據庫信息 +i18n.file_type_no_stop.00ff=file 類型項目沒有 stop +i18n.monitor_ssh_exception.e9ce=監控 ssh[{}] 異常 +i18n.running_node_cannot_be_empty.ffc6=運行節點不能為空 +i18n.push_registration_to_server_failed.5949=向服務端推送註冊失敗 {} +i18n.delete_script_template_execution_error.8bc5=刪除腳本模版執行數據錯誤\:{} +i18n.no_menus_contact_admin.cfec=沒有任何菜單,請聯繫管理員 +i18n.check_passed.dce8=檢查通過 +i18n.upload_progress_with_colon.dd5b=上傳文件進度:{} {}/{} {} +i18n.machine_node_info.6a75=機器節點信息 +i18n.ssh_command_log.7fd1=SSH命令日誌 +i18n.upgrade_failure_with_colon.59f1=升級失敗\: +i18n.script_tag_modification_not_allowed.cb75=腳本標記不能修改 +i18n.auth_exception.27be=授權異常 +i18n.unsupported_plugin_message.2889=當前還不支持 {} 插件 +i18n.operation_succeeded_refresh_backup.54a9=操作成功,請稍後刷新查看備份狀態 +i18n.no_data.55a2=沒有任何數據 +i18n.node_not_enabled.a14d=節點未啟用 +i18n.no_build_id.a0b8=沒有buildId +i18n.user_not_select_permission_group.1091=用户未選擇權限組 +i18n.project_has_logs_cannot_delete.1d2a=當前項目存在日誌閲讀,不能直接刪除 +i18n.unsupported_encoding_with_placeholder.3bd9=不支持的編碼方式:{} +i18n.export_image_exception.cb1c=導出鏡像異常 +i18n.backup_data_not_exist.f88c=備份數據不存在 +i18n.illegal_character_encoding_format.af7a=配置的字符編碼格式不合法: +i18n.select_file_to_delete.33d6=請選擇要刪除的文件 +i18n.online_upgrade_cannot_downgrade.d419=在線升級不能降級操作 +i18n.incorrect_cluster_address.893f=填寫的集羣地址不正確 +i18n.check_docker_dependency_error.60f7=檢查 docker 依賴錯誤\:{} +i18n.program_already_running.96e1=當前程序正在運行中,不能重複啟動,PID\: +i18n.password_change_success.8013=修改密碼成功! +i18n.build_resource_cleanup_failed.c4cf=清理構建資源失敗 +i18n.incorrect_publish_method.e095=發佈方法不正確 +i18n.import_low_version_data_to_new_version.247b=2. 將導出的低版本數據( sql 文件) 導入到新版本中【啟動程序參數裏面添加 --replace-import-h2-sql\=/xxxx.sql (路徑需要替換為第一步控制枱輸出的 sql 文件保存路徑)】 +i18n.incorrect_line_number.5877=行號不正確 +i18n.file_deletion_event_with_details.7537=文件刪除事件:{} {} +i18n.configure_correct_token_url.7bba=請配置正確的令牌 url +i18n.maven_plugin_version_required.71f1=maven 插件 version 不能為空 +i18n.trigger_token_error_or_expired_with_code.393b=觸發token錯誤,或者已經失效\:-1 +i18n.invalid_webhooks_address.d836=WebHooks 地址不合法 +i18n.project_path_auth_not_under_jpom.0e18=項目路徑授權不能位於Jpom目錄下 +i18n.reconnect_failure.7c01=重連失敗 +i18n.project_operations.03d9=項目運維 +i18n.image_not_exist.ee17=鏡像不存在 +i18n.no_data.1ac0=沒有數據 +i18n.load_file_failure.86cc=加載文件失敗\: +i18n.no_corresponding_ssh_script_info.1c12=沒有對應的ssh腳本信息 +i18n.log_file_error.473b=日誌文件錯誤 +i18n.compare_id_not_exist.43be=比較 id 不存在 +i18n.private_file_not_found.ee45=沒有隱私文件 +i18n.detect_local_docker_exception_with_details.7cc9=探測本地 docker 異常: +i18n.prepare_restart.8251=開始準備項目重啟:{} {} +i18n.save_node_data_failed.f314=保存節點數據失敗\: +i18n.range_format_not_supported.d69e=不支持的 range 格式 +i18n.select_at_least_one_node_project.637c=至少選擇1個節點項目 +i18n.close_beta_plan_success.5a94=關閉 beta 計劃成功 +i18n.start_rolling_back_execution.a019=開始回滾執行 +i18n.get_container_log_interrupted.041d=獲取容器日誌操作被中斷\: +i18n.node_has_no_workspace.69c0=節點沒有工作空間 +i18n.correct_enterprise_wechat_address_required.5f2d=請輸入正確企業微信地址 +i18n.unknown_jsch_log_level_with_details.1f9a=未知的 jsch 日誌級別:{} {} +i18n.file_not_found.d952=文件不存在 +i18n.node_communication_failure_signal.5aae=節點通訊失敗,遠程地址和端口時發生錯誤的信號。通常,由於中間的防火牆或中間路由器已關閉,無法訪問遠程主機。 +i18n.multiple_node_data_exists_merge_config.043f=節點地址 {} 存在多個數據,將自動合併使用 {} 節點的配置信息 +i18n.unknown_data_loss.5a24=未知(數據丟失) +i18n.command_execution_failed_details.77ed=執行命令失敗,詳情如下: +i18n.unknown_parameter.96dd=未知參數 +i18n.publish_file_second_level_directory_required.2f65=請填寫發佈文件的二級目錄 +i18n.node_id_required_and_format.5926=節點id不能為空並且2-50(英文字母 、數字和下劃線) +i18n.restore_backup_data_failed.58af=還原備份數據失敗 +i18n.system_task_execution_exception.d559=執行系統任務異常 +i18n.command_management.621f=命令管理 +i18n.no_project_specified2.a7f5=沒有對應項目: +i18n.certificate_info_error_issuer_or_subject_DN_not_found.805d=證書信息出現錯誤,未找到 issuerDN 或者 subjectDN +i18n.exported_ssh_data.2896=導出的 ssh 數據 +i18n.refreshing_cache.c969=正在刷新緩存中,請勿重複刷新 +i18n.start_executing_database_event.fc57=開始執行數據庫事件:{} +i18n.static_file_storage.35f6=靜態文件存儲 +i18n.project_log_is_existing_folder.a80a=項目log是一個已經存在的文件夾 +i18n.project_data_workspace_id_inconsistency.7ed6=項目數據工作空間ID[{}]查詢出節點ID不一致, 舊數據\: {}, 新數據\: {} +i18n.query_workspace_error.6a0d=查詢錯誤的工作空間失敗 +i18n.unknown_database_mode.f9e5=當前數據庫模式未知 +i18n.secondary_directory_match.0aec={} 二級目錄模糊匹配到 {} 個文件, 當前文件保留方式 {} +i18n.ssh_server_alive_interval_config_error.1f11=配置 ssh serverAliveInterval 錯誤 +i18n.reset_success.faa3=重置成功 +i18n.please_fill_in_repository_name.9f0d=請填寫倉庫名稱 +i18n.parameter_error_ssh_name_cannot_be_empty.ff4f=參數錯誤ssh名稱不能為空 +i18n.current_docker_cluster_still_associated_with_workspaces.b301=當前 docker 集羣還關聯 {} 個工作空間集羣,不能退出集羣 +i18n.current_distribution_has_only_one_project.cd59=當前分發只有一個項目啦,刪除整個分發即可 +i18n.no_corresponding_execution_log.9545=沒有對應的執行日誌 +i18n.import_success_with_details.a4a0=導入成功(編碼格式:{}),添加 {} 條數據,修改 {} 條數據 +i18n.current_distribution_has_build_items_cannot_unbind.a8e9=當前分發存在構建項,不能解綁 +i18n.listen_file_failed_may_not_exist.fd56=監聽文件失敗,可能文件不存在 +i18n.log_file_cleanup_failed.3a3b=清理日誌文件失敗 +i18n.parse_csv_exception.885e=解析 csv 異常 +i18n.no_build_record_found.76f4=還沒有對應的構建記錄 +i18n.login_info_required.973b=請輸入登錄信息 +i18n.h2_database_backup_success.a099=H2 數據庫備份成功:{} +i18n.uses_only_supports_string_type.ac54=uses 只支持 String 類型 +i18n.link_id_required.5dc7=Link 模式 LinkId必填 +i18n.oshi_hard_disk_monitoring_exception.c642=oshi 硬盤資源監控異常 +i18n.get_docker_cluster_info_failure.c80d=獲取 docker 集羣信息失敗 +i18n.exit_code.3b54=本次執行退出碼\: {} +i18n.compare_project_failure.e6ab=對比項目文件失敗: +i18n.parse_file_exception.374d=解析文件異常, +i18n.certificate_info_table.fff8=證書信息表 +i18n.online_upgrade.da8c=在線升級 +i18n.no_log_file.bacf=還沒有日誌文件 +i18n.listen_task_lost_or_not_found.347f=監聽任務丟失或者未找到:{} +i18n.parse_project_csv_exception.ece1=解析項目 csv 異常 +i18n.ssh_file_deletion_exception.5ba5=ssh刪除文件異常 +i18n.token_invalid_or_expired.cb96=token錯誤,或者已經失效\:-1 +i18n.list_and_query.c783=列表、查詢 +i18n.migration_docker_cert_error.a5ea=遷移 docker[{}] 證書發生異常 +i18n.file_downloading.7a8f=文件下載中 +i18n.mark_must_contain_letters_numbers_underscores.667d=標記只能包含字母、數字、下劃線 +i18n.please_fill_in_runs_on.c2ff=請填寫runsOn。 +i18n.create_build_task_exception.06f1=創建構建任務異常 +i18n.execution_interrupted.1bb6=執行被中斷 +i18n.event_loss_or_execution_error.7b14=事件丟失或者執行錯誤:{} {} +i18n.public_key_and_private_key_mismatch.4aa2=公鑰和私鑰不匹配 +i18n.restore_success.4c7f=還原成功 +i18n.monitoring_notifications.de94=監控通知 +i18n.machines_node_data_fixed.7744=成功修復 {} 條機器節點數據 +i18n.upload_success.a769=上傳成功 +i18n.send_email_failure.1ab3=發送郵件失敗: +i18n.email_configuration.b3f7=郵箱配置 +i18n.server_script_not_exist.de24=不存在對應的服務端腳本,請重新選擇 +i18n.java_plugin_version_required.de39=java 插件 version 不能為空 +i18n.soft_link_project_mode_error.ffa0=被軟鏈的項目不能是File或Link模式 +i18n.node_null_pointer_exception.76fe={}節點,程序空指針異常 +i18n.parse_certificate_exception.3b6c=解析證書異常 +i18n.get_build_status_exception.914e=獲取構建狀態異常 +i18n.node_has_distribution_projects_cannot_delete.3987=該節點存在分發項目,不能 +i18n.selected_user_status_abnormal.efcf=選擇的用户狀態異常 +i18n.target_workspace_consistency.e04c=目標工作空間與當前工作空間一致並且目標節點與當前節點一致 +i18n.nickname_length_limit.6312=暱稱長度只能是2-10 +i18n.please_pass_parameter.3182=請傳入參數 +i18n.node_no_data_pulled.0dae={} 節點沒有拉取到任何 {},但是刪除了數據:{} +i18n.node_info_not_found.2c8c=沒有查詢到節點信息: +i18n.please_fill_in_repository_address.0cf8=請填寫倉庫地址 +i18n.table_without_primary_key.7392=表沒有主鍵 +i18n.invalid_shard_id.46fd=不合法的分片id +i18n.execution_ended.b793=執行結束\:{} +i18n.user_trigger_unavailable.9866=當前用户觸發器不可用 +i18n.task_ended.b341=任務結束\: +i18n.session_closed_reason.103a=會話[{}]關閉原因:{} +i18n.static_directory_auth_cannot_be_empty.2cb2=靜態目錄授權不能為空 +i18n.file_publish_task_record.edc4=文件發佈任務記錄 +i18n.no_files_in_zip.1af6=壓縮包裏沒有任何文件 +i18n.unable_to_connect_to_repository.52df=無法連接此倉庫, +i18n.parent_task_not_exist.ca1b=父任務不存在 +i18n.distribute_exception_with_detail.28fe=分發異常 {} +i18n.unknown_database_dialect_type.951b=未知的數據庫方言類型\: +i18n.fill_download_address.763c=填寫下載地址 +i18n.verification_code_is.5af5=驗證碼是:{} +i18n.pull_exception.b38d=拉取異常 +i18n.incomplete_upload_info_total_slice.7e85=上傳信息不完成:totalSlice +i18n.database_corrupted.944e=數據庫異常,可能數據庫文件已經損壞(可能丟失部分數據),需要重新初始化。可以嘗試在啟動參數裏面添加 --recover\:h2db 來自動恢復,: +i18n.handle_node_synchronization_script_library_failure.14e4=處理 {} 節點同步腳本庫失敗 {} +i18n.cluster_name_required.5ca6=請填寫集羣名稱 +i18n.build_method_incorrect.5319=構建方式不正確 +i18n.data_does_not_exist.b201=數據不存在 +i18n.operation_time.7e95=操作時間 +i18n.communication_ip_cannot_be_empty.ae35=通信 IP 不能為空 +i18n.build_info_not_exist.4470=不存在對應的構建信息 +i18n.protocol_field_required.7cc2=第 {} 行 protocol 字段不能位空 +i18n.incorrect_mode_for_migration.caef=當前模式不正確,不能直接遷移到 {} +i18n.socket_session_establishment_failed.4924=socket 會話建立失敗,授權信息錯誤 +i18n.handle_message_exception.0bdc=處理消息異常 +i18n.index_field_not_configured.96d9=索引未配置字段 +i18n.file_name_not_found.b0ed=沒有文件名 +i18n.file_deletion_event.a51c=文件刪除事件:{} +i18n.no_asset_management_permission.739e=您沒有資產管理權限 +i18n.current_docker_already_in_other_cluster.e629=當前 docker 已經加入到其他集羣啦 +i18n.addition_succeeded.3fda=添加成功 +i18n.node_upgrade.3bf3=節點升級 +i18n.server_system_config.3181=服務端系統配置 +i18n.empty_file_cannot_upload.88df=空文件不能上傳 +i18n.config_file_already_exists.c5fe=對應的配置文件已經存在啦 +i18n.login_password_required.9605=請填寫登錄密碼 +i18n.current_repository_associated_with_build.4b6e=當前倉庫被構建關聯,不能直接刪除 +i18n.unzip_exception.92cc=解壓異常 {} by InputStream {} +i18n.read_global_script_file_error.0d4c=讀取全局腳本文件失敗 +i18n.build_product_sync_success.f7d1=構建產物文件成功同步到文件管理中心,{} +i18n.decrypt_parameter_failure.d10a=解密參數失敗 +i18n.no_record.ff41=沒有對應的記錄 +i18n.workspace_error_or_no_permission.7c8b=工作空間錯誤,或者沒有權限編輯此數據 +i18n.upload_failed.b019=上傳失敗\: +i18n.no_permission.e343=您沒有對應權限 +i18n.listener_key_not_found.6d3a=沒有找到監聽 key +i18n.download_failed_retry.c113=下載失敗。請刷新頁面後重試 +i18n.please_fill_in_correct_go_version.44ed=請填入正確的 go 版本號 +i18n.forbidden_operation_time_range.92bf=【禁止操作】當前時段禁止執行 {} 至 {} +i18n.no_matching_files.b7a6={} 沒有匹配到任何文件 +i18n.multi_download_not_supported.94b9=不支持分片多端下載 +i18n.parameter_error_port_error.810d=參數錯誤port錯誤 +i18n.incomplete_node_info_missing_machine_id.1c9a=節點信息不完整,缺少機器id +i18n.node_network_connection_exception_or_timeout.5904=節點網絡連接異常或超時,請優先檢查插件端運行狀態再檢查 IP 地址、 +i18n.ssh_command_management.c40a=SSH命令管理 +i18n.variable_name_rules.480a=變量名稱 1-50 英文字母 、數字和下劃線 +i18n.parameter_error_user_cannot_be_empty.9239=參數錯誤user不能為空 +i18n.no_database_config_header_found.9ee3=沒有找到數據庫配置標識頭 +i18n.week_day_range_format.ebec=周{} 的 {} 至 {} +i18n.no_node_info_no_need_to_fix_machine_data.562e=沒有任何節點信息,不需要修復機器數據 +i18n.send_message_exception.7817=發消息異常 +i18n.static_file_task_load_failure.b995=靜態文件任務加載失敗 +i18n.server_exception_occurred.9eb4=服務端發生異常 +i18n.ssh_unauthorized_directory.df78=此ssh未授權操作此目錄 +i18n.event_script_does_not_exist.e726=事件腳本不存在\:{} {} +i18n.async_refresh_in_progress.5550=異步刷新中請稍後刷新頁面查看 +i18n.build_status_message.42a7=當前構建中任務數:{},隊列中任務數:{} 構建任務等待超時或者超出最大等待數量,當前運行中的任務數:{}/{},取消執行當前構建 +i18n.download_action.f26e=下載 +i18n.project_is_not_node_distribution_project_cannot_delete.2a5a=該項目不是節點分發項目,不能在此次刪除 +i18n.node_has_build_items_cannot_delete.a952=該節點存在構建項,不能 +i18n.encoding_error.b685=編碼異常 +i18n.node_distribution.ae68=節點分發 +i18n.two_step_verification_code_required.7e86=請輸入兩步驗證碼 +i18n.close_docker_exec_terminal.fec3=關閉[{}] docker exec 終端:{} +i18n.ssh_error_string.6bdb=ssh 錯誤:{} +i18n.project_id_does_not_exist.6b9b=項目id不存在 +i18n.cluster_not_bound_to_group_for_docker_monitoring.3926=當前集羣還未綁定分組,不能監控 Docker 資產信息 +i18n.no_content_to_execute.66aa=沒有需要執行的內容 +i18n.online_build.6f7a=在線構建 +i18n.export_low_version_data.f1aa=1. 導出低版本數據 【啟動程序參數裏面添加 --backup-h2】 +i18n.docker_asset_imported.0ab4=docker[{}] 資產導入 +i18n.associated_group_required.5889=請選擇關聯分組 +i18n.backup_directory_conflict.c13e=備份目錄衝突: +i18n.delete_service_success.4d73=刪除服務成功 +i18n.select_monitoring_operation.3057=請選擇監控的操作 +i18n.config_file_database_config_not_parsed.47b2=未解析出配置文件中的數據庫配置信息 +i18n.data_already_exists.0397=導入的數據已經存在啦 +i18n.please_check_in_time.3b4f=請及時檢查 +i18n.installation_success.811f=安裝成功 +i18n.cluster_management.74ea=集羣管理 +i18n.type_field_value_error.14cf=第 {} 行 type 字段值錯誤(Git/Svn) +i18n.migrate_data.f556=遷移數據 +i18n.no_script.93c4=沒有對應的腳本 +i18n.node_does_not_exist.4ce4=節點不存在 +i18n.not_super_admin.962e=您不是超級管理員沒有權限\:-2 +i18n.ssh_does_not_exist.5bec=對應的 SSH 不存在 +i18n.file_too_large.9994=上傳文件太大了,請重新選擇一個較小的文件上傳吧 +i18n.backup_product.53c0=備份產物 {} {} +i18n.command_error.d0b4=執行命令錯誤 +i18n.select_alarm_contact.d02a=請選擇報警聯繫人 +i18n.file_signature_info_not_found.83bf=沒有文件簽名信息 +i18n.associated_nodes_warning.64d8=當前機器還關聯{}個節點,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.build_unknown_error.dad6=構建發生未知錯誤 +i18n.new_version_exists_download_unavailable.4ba7=存在新版本,下載地址不可用 +i18n.compare_backup_failure.303e=對比清空項目文件備份失敗 +i18n.upload_success_and_restart.7bc3=上傳成功並重啟 +i18n.no_deletion_condition.19d0=沒有刪除條件 +i18n.get_docker_cluster_failure_with_placeholder.06cb=獲取 {} docker 集羣失敗 {} +i18n.synchronize_project_files_failed.6aa4=同步項目文件失敗: +i18n.parse_system_start_time_error.112c=\ 解析系統啟動時間錯誤: +i18n.service_info_incomplete_with_code1.30f4=服務信息不完整不能操作:-1 +i18n.at_least_one_project_required.2bbd=請至少選擇一個項目 +i18n.cluster_id_conflict.45b7={} 集羣ID衝突:{} {} +i18n.modified_value_is_empty.e4fa=修改的值為空 +i18n.cannot_delete_root_dir.fcdc=不能刪除根目錄 +i18n.manual_cancel_task.e592=手動取消任務 +i18n.unzip_exception.453e={} 解壓異常 {} {} +i18n.start_executing_event_script.377e=開始執行事件腳本: {} +i18n.unlock_success.4cea=解鎖成功 +i18n.login_name_email_format_length_range.25f3=登錄名如果為郵箱格式,長度必須 {}-{} +i18n.ssh_data_repair_not_needed.203f=機器 SSH 表已經存在 {} 條數據,不需要修復機器 SSH 數據 +i18n.new_package_same_as_running_package.e25a=新包和正在運行的包一致 +i18n.config_file_not_exist_with_message.6a40=配置文件不存在 {} +i18n.no_workspace_selected.33d5=沒有選擇任何工作空間 +i18n.associated_data_exists_in_workspace.8827=當前工作空間下還存在關聯數據: +i18n.script_not_bound_to_ssh_node.3459=當前腳本未綁定 SSH 節點,不能使用觸發器執行 +i18n.start_executing_post_release_command.fd06=開始執行 {} 發佈後命令 +i18n.alert_content_and_status.6ed1=報警內容:{} 狀態消息:{} +i18n.incorrect_project_id.5f70=項目id 不正確 +i18n.certificate_management.4001=證書管理 +i18n.download_exception.e616=下載文件異常 +i18n.process_file_deletion_exception.1c6e=處理文件刪除異常 +i18n.trigger_auto_execute_command_template_exception.4e01=觸發自動執行命令模版異常 +i18n.no_build.d163=沒有對應的構建 +i18n.start_executing_publishing_with_file_size.5039=開始執行發佈,需要發佈的文件大小:{} +i18n.command_execution_exception.4ccd=執行命令異常 +i18n.operation_ip.cbd4=操作IP +i18n.auth_directory_cannot_contain_hierarchy.d6ca=授權目錄中不能存在包含關係: +i18n.ip_authorization_interception_exception.8130=IP授權攔截異常,請檢查配置是否正確 +i18n.refresh_token_timeout.3291=刷新token超時 +i18n.missing_script_library_message.be9a=對應的腳本庫不存在: +i18n.operation_file_permission_exception.5a41=操作文件權限異常,請手動處理: +i18n.monitoring_user_actions.f2d5=監控用户操作 +i18n.please_fill_in_user.5f52=請填寫user +i18n.command_template_execution_link_exception.51cf=命令模版執行鏈接異常 +i18n.cleanup_succeeded.02ea=清理成功 +i18n.distribute_node_authorization_failure.bb92=分發 {} 節點授權失敗 {} +i18n.monitoring_item_not_exist.32c8=不存在監控項啦 +i18n.workspace_id_required.c967=工作空間ID不能為空 +i18n.please_fill_in_ipv4_address.d23a=請填寫 ipv4 地址: +i18n.publish_product.5925=發佈產物 +i18n.no_workspace_info_contact_admin_for_authorization.825f=沒有任何工作空間信息,請聯繫管理授權 +i18n.cannot_disable_super_admin.6429=不能禁用超級管理員 +i18n.oshi_system_process_monitoring_exception.a4da=oshi 系統進程監控異常 +i18n.start_async_download.78cc=開始異步下載 +i18n.install_id_does_not_exist.6aee=數據錯誤,安裝 ID 不存在 +i18n.file_format_not_supported.eac4=不支持的文件格式 +i18n.restore_backup_data_success.253a=還原備份數據成功 +i18n.build_info_missing.0ab0=構建信息缺失 +i18n.file_not_exist.5091=對應的文件不存在 +i18n.max_concurrent_shard_ids.f89c=分片id最大同時使用 100 個 +i18n.handle_node_deletion_script_library_failure.4205=處理 {} 節點刪除腳本庫失敗 {} +i18n.data_id_not_found.1b0a=沒有數據id +i18n.yml_config_format_error_tab.f629=yml 配置內容格式有誤請檢查後重新操作(不要使用 \\\\t(TAB) 縮進): +i18n.build_in_progress.4d33=當前構建還在進行中 +i18n.execute_script_exit_code.64a8=執行 {} 類型腳本的退出碼是:{} +i18n.user_does_not_exist.8363=\ 用户不存在請聯繫管理創建 +i18n.no_node_found.6f85=沒有找到對應的節點 +i18n.current_docker_not_in_cluster.f70c=當前 docker {} 不在集羣中 +i18n.no_available_docker_server.9fc6=\ 沒有可用的 docker server +i18n.active_clearance.5870=主動清除 +i18n.login_info_expired_re_login.6bc4=登錄信息已失效,重新登錄 +i18n.release_node_project_failed.764e=釋放節點項目失敗: +i18n.no_corresponding_task.3be5=沒有對應的任務 +i18n.account_does_not_exist.8402=賬號不存在 +i18n.scan_succeeded.7975=掃描成功 +i18n.websocket_error.2bb4=websocket出現錯誤:{} +i18n.no_management_permission2.35d4=您沒有對應管理權限\:-3 +i18n.file_in_use_stop_project_first.a2c3=文件被佔用,請先停止項目 +i18n.product_file_does_not_exist.ee13=產物文件不存在 +i18n.default_setting.18c6=默認 +i18n.address_field_required.3bc8=第 {} 行 address 字段不能位空 +i18n.get_repository_branch_failure.37cc=獲取倉庫分支失敗 +i18n.file_type_project_no_running_status.32a2=file 類型項目沒有運行狀態 +i18n.project_info.6674=項目信息 +i18n.no_changes_in_repository_code_with_details.fd9f=倉庫代碼沒有任何變動終止本次構建:{} {} +i18n.import_save_project_exception.cdbe=導入保存項目異常 +i18n.in_progress.b851=進行中\: +i18n.current_distribution_data_lost_record_id_not_exist.ca07=當前分發數據丟失,記錄id 不存在 +i18n.oauth2_binding_warning.d8f0=當前權限組被 oauth2[{}] 綁定,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.node_status_code_abnormal.4d22=【{}】節點的狀態碼異常:{} +i18n.data_does_not_exist_with_details.d9b5=數據不存在\: +i18n.data_type_not_supported.fd03=不支持的數據類型\: +i18n.refresh_token_failure.de7f=刷新token失敗 +i18n.no_docker_details.3343=沒有對應到docker信息 +i18n.no_permission_for_function.b63d=您沒有對應功能【{}】管理權限 +i18n.auth_config.3d48=授權配置 +i18n.no_info.e59e=沒有任何信息 +i18n.repository_info.22cd=倉庫信息 +i18n.data_id_cannot_be_empty.403b=數據 id 不能為空 +i18n.ssh_does_not_exist.88d7=ssh 不存在 +i18n.select_operation_type.63c6=請選擇操作類型 +i18n.clear_success.2685=清空成功 +i18n.illegal_access.c365=非法訪問 +i18n.certificate_has_no_aliases.3a2f=證書沒有任何:aliases +i18n.docker_asset_management.96d9=DOCKER資產管理 +i18n.docker_tag_incorrect.8b62=docker tag 填寫不正確,沒有找到任何docker +i18n.release_successful.f2ca=釋放成功 +i18n.result_dir_file_required.5f02=resultDirFile 不能為空 +i18n.status_not_distributing.6298=當前狀態不是分發中 +i18n.build_record_not_exist.8186=構建記錄不存在 +i18n.unable_to_connect_to_docker.2bb3=無法連接 docker 請檢查 host 或者 TLS 證書 以及倉庫信息配置是否正確。 +i18n.system_configuration_directory.0f82=系統配置目錄 +i18n.node_authorized_config.f934=節點授權配置 +i18n.cannot_operate_current_directory.aa3d=不能操作當前目錄 +i18n.script_template_exists.3f86=該節點下還存在腳本模版,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.do_not_reopen.f86a=不要重複打開 +i18n.asset_ssh_not_exist.cd43=不存在對應的資產SSH +i18n.package_missing_info.e277=此包沒有版本號、打包時間、最小兼容版本 +i18n.too_many_attempts.d88d=嘗試次數太多,請稍後再來 +i18n.incorrect_account_credentials.b2c5=賬號密碼不正確 +i18n.plugin_system_log.955c=插件端系統日誌 +i18n.cluster_created_successfully.6bf3=集羣創建成功 +i18n.data_associated_id_inconsistent.59f7=數據關聯的id 不一致 +i18n.script_exit_code.716e=執行腳本的退出碼是:{} +i18n.ssh_connections_warning.1ddb=當前機器SSH還關聯{}個ssh,不能直接刪除(需要提前解綁或者刪除關聯數據後才能刪除) +i18n.oauth2_not_configured.9c85=未配置 oauth2 +i18n.disallowed_file_extension.eb05=不允許編輯的文件後綴 +i18n.no_environment_variables_found.46ad=沒有找到任何環境變量 +i18n.remote_download_url.011f={} 遠程下載 url:{} +i18n.correction_data_failure.dac6=修正數據失敗: +i18n.no_corresponding_ssh_item.2deb=沒有對應的ssh項 +i18n.private_key_file_not_exist.49ed=配置的私鑰文件不存在 +i18n.check_docker_exception.a6d1=檢查 docker 異常 +i18n.only_git_repositories_have_branch_info.d7f7=只有 GIT 倉庫才有分支信息 +i18n.select_monitoring_person.0756=請選擇監控人員 +i18n.associated_data_name_not_exist_error.583e=ERROR\:關聯數據名稱不存在 +i18n.file_not_exist.ea6a=不存在對應的文件 +i18n.demo_account_not_support_delete.f9a6=演示賬號不支持刪除 +i18n.no_command_to_execute.340b=沒有需要執行的命令 +i18n.no_files_in_project_directory.108e=項目目錄沒有任何文件,請先到項目文件管理中上傳文件 +i18n.build_record_lost.f6a2=構建記錄丟失,無法繼續構建 +i18n.no_node.2e83=沒有對應的節點 +i18n.reconnect_plugin_failure.cc6c=重連插件端失敗 +i18n.installation_success_with_machine_id.1cd6=安裝成功,本機安裝 ID 為:{} +i18n.query_success.d72b=查詢成功 +i18n.create_success.04a6=創建成功 +i18n.send_message_failure.9621=發送消息失敗 +i18n.project_has_node_distribution_cannot_migrate.cc0e=當前項目存在節點分發,不能直接遷移 +i18n.please_fill_in_node_address.e77e=請填寫 節點地址 +i18n.login_name_length_range.fe8d=登錄名長度範圍 3-50 +i18n.file_already_exists.983d=文件已經存在啦 +i18n.repository_key_file_does_not_exist_or_is_abnormal.1d78=倉庫密鑰文件不存在或者異常,請檢查後操作 +i18n.no_corresponding_docker_info.c47a=沒有對應的 docker 信息 +i18n.type_not_exist_error.09de=ERROR\:類型不存在 +i18n.configure_announcement_title_or_content.7894=請配置公吿標題或者內容 +i18n.no_any_branch.d042=沒有任何分支 +i18n.connection_successful.0515=成功連接 {} {} +i18n.docker_does_not_exist_with_code.689b=對應的 docker 不存在\:-1 +i18n.multiple_worker_nodes_exist.7110=還存在多個工作節點,不能退出最後一個管理節點 +i18n.operation_log.cda8=操作日誌 +i18n.close_resource_failure.dc66=關閉資源失敗 +i18n.free_script.7760=自由腳本 +i18n.project_id_length_range.7064=項目id 長度範圍2-20(英文字母 、數字和下劃線) +i18n.system_cancel.3df2=系統取消 +i18n.configure_correct_user_info_url.1276=請配置正確的用户信息 url diff --git a/modules/common/src/main/resources/i18n/messages_zh_TW.properties b/modules/common/src/main/resources/i18n/messages_zh_TW.properties new file mode 100644 index 0000000000..c89655db99 --- /dev/null +++ b/modules/common/src/main/resources/i18n/messages_zh_TW.properties @@ -0,0 +1,1616 @@ +#i18n zh_TW +#Sun Jun 16 22:24:48 CST 2024 +i18n.ssh_info_does_not_exist.5ed0=ssh 資訊不存在啦 +i18n.incompatible_program_versions.5291=當前程式版本 {} 新版程式最低相容 {} 不能直接升級 +i18n.no_projects_configured.e873=沒有配置任何專案 +i18n.docker_log_thread_ended.8230=docker log 執行緒結束:{} {} +i18n.machine_ssh_info.8dbb=機器SSH資訊 +i18n.machine_info_not_exist.3468=對應的機器資訊不存在 +i18n.unsupported_method.a1de=不支援的方式 +i18n.service_name_in_cluster_required.5446=請填寫叢集中的服務名 +i18n.cluster_manager_node_not_found.1cd0=沒有找到叢集管理節點 +i18n.distribute_id_already_exists.2168=分發id已經存在啦 +i18n.node_authorized_distribution.c5d7=節點授權分發 +i18n.operation_user.4c89=操作使用者 +i18n.no_uploaded_file.07ef=沒有上傳檔案 +i18n.physical_node_pull.874e={} 物理節點拉取到 {} 個{},當前工作空間邏輯節點已經快取 {} 個{},更新 {} 個{},刪除 {} 個快取 +i18n.request_type_not_supported_for_decoding.ea2e=當前請求型別不支援解碼:{} +i18n.account_login_failed_too_many_times_locked.23b2=該賬戶登入失敗次數過多,已被鎖定{},請不要再次嘗試 +i18n.no_corresponding_ssh_info.d864=沒有對應的ssh資訊 +i18n.no_tag_name.40ff=沒有 tag name +i18n.ssh_console_connection_timeout.8eb3=ssh 控制檯連線超時 +i18n.publish_task_execution_failed.b075=執行釋出任務失敗 +i18n.migration_completed.7a30=遷移完成,累計遷移 {} 條資料,耗時:{} +i18n.container_command_execution_exception.a14a=執行容器命令異常 +i18n.plugin_connection_failed.02a1=外掛端連線失敗 +i18n.auto_migrate_exist_logs.c169=自動遷移存在日誌 {} -> {} +i18n.demo_account_password_change_not_supported.91f4=當前賬戶為演示賬號,不支援修改密碼 +i18n.file_type_not_supported3.f551=不支援的檔案型別:{} +i18n.trigger_project_reload_event.a7dc=觸發專案 reload 事件:{} +i18n.alert_contact_exception_message.1072=報警聯絡人異常\: +i18n.chunk_upload_exception.87c1=分片上傳異常:{} {} +i18n.start_waiting_for_data_migration.e76f=開始等待資料遷移 +i18n.empty_file_or_folder_for_publish.cae8=釋出的檔案或者資料夾為空,不能繼續釋出 +i18n.demo_account_cannot_use_feature.a1a1=演示賬號不能使用該功能 +i18n.repository_type_required.9414=請選擇倉庫型別 +i18n.async_resource_expired.2ddc=非同步資源過期,需要主動關閉,{} {} +i18n.mark_cannot_be_empty.1927=標記不能為空 +i18n.get_success.fb55=獲取成功 +i18n.execution_interrupted_reason.e3d7=執行中斷 {} 流程,原因事件指令碼中斷 +i18n.forbidden_operation_time_period.86a3=【禁止操作】當前時間不在可執行的時間段內,限制時間段\: +i18n.host_cannot_be_empty.644a=引數錯誤host不能為空 +i18n.pull_code_exception_with_cleanup.a887=拉取程式碼異常,已經主動清理本地倉庫快取內容,請手動重試。 +i18n.correct_retention_days_required.d542=請填寫正確的保留天數 +i18n.external_config_not_exist_or_not_configured.f24e=外接配置不存在或者未配置:{},使用預設配置 +i18n.cannot_read_tar_archive_entry.85d7=不能讀取tarArchiveEntry {} +i18n.cluster_not_exist.4098=對應的叢集不存在 +i18n.upload_failed_no_matching_project.b219=上傳失敗,沒有找到對應的分發專案 +i18n.no_branches_or_tags_in_repository.76b6=倉庫沒有任何分支或者標籤 +i18n.download_file_description.10cb=下載檔案 {} {} {} +i18n.delay_build.7d62=延遲 {} 秒後開始構建 +i18n.default_cluster.38cf=預設叢集 +i18n.node_and_check_project_failed.ac4b=節點與檢查專案失敗 +i18n.load_oauth2_config.da42=載入 oauth2 配置 :{} {} +i18n.cluster_not_bound_to_group_for_node_monitoring.1586=當前叢集還未繫結分組,不能監控叢集節點資產資訊 +i18n.cannot_execute_error.4c29=不能執行:error +i18n.no_environment_variable.c79f=沒有對應的環境變數 +i18n.restart_completed.42b8=重啟完成 +i18n.node_return_info_exception.0961=節點返回資訊異常,請檢查節點地址是否配置正確或者代理配置是否正確 +i18n.project_associated_with_other_workspaces_message.299c=當前專案已經被其他工作空間關聯,請檢查確認後再操作或者使用孤獨資料修正 +i18n.start_executing_distribution_package.a2cc=開始執行分發包啦,請到分發中檢視詳情狀態 +i18n.clear_old_version_package_failed.021c=清空舊版本重新包失敗 +i18n.node_online_upgrade.f144=節點線上升級 +i18n.execution_ended_with_detail.8f93=執行結束\: {} {} +i18n.ssh_file_upload_exception.5c1c=ssh上傳檔案異常 +i18n.authorization_exception.acc0={} 授權異常 {} +i18n.invalid_or_expired_token.bc43=token錯誤,或者已經失效 +i18n.no_docker_info.d685=沒有對應 docker +i18n.invalid_http_proxy_address.1da1=HTTP代理地址格式不正確 +i18n.execute_dsl_script_exception.0882=執行 DSL 指令碼異常:{} +i18n.package_product.bfbb=打包產物 +i18n.cancel_success.285f=取消成功 +i18n.node_name_required.ac0f=節點名稱 不能為空 +i18n.class_path_and_java_ext_dirs_cp_required.7557=ClassPath、JavaExtDirsCp 模式 MainClass必填 +i18n.parameter_validation_failed.f0a1=引數驗證失敗 +i18n.asset_cluster_and_node_mismatch.8964=資產叢集和節點不匹配 +i18n.script_template.54f2=指令碼模板 +i18n.no_project_specified.0076=沒有對應的專案: +i18n.connection_successful_with_message.5cf2=連線成功: +i18n.old_password_incorrect.9cf6=舊密碼不正確! +i18n.script_template_execution_record.374b=指令碼模版執行記錄 +i18n.ssh_authorization_directory_cannot_be_root.8125=ssh 授權目錄不能是根目錄 +i18n.node_connection_failure_message.aacc=節點連線失敗: +i18n.email_service_not_configured.3180=未配置郵箱服務不能傳送郵件:{} {} +i18n.node_service_stopped_abnormal_restart.a5c0=【{}】節點的【{}】專案{}已經停止,重啟操作異常 +i18n.same_distribution_project_exists.ff41=已經存在相同的分發專案\: +i18n.unbind_success.1c43=解綁成功 +i18n.project_log.2926=專案日誌 +i18n.private_key_file_not_found.4ad9=私鑰檔案不存在: +i18n.unsupported_prefix.4f8c=當前還不支援: +i18n.file_download_failed.7983=檔案下載失敗: +i18n.no_corresponding_distribution_project.6dcd=沒有對應的分發專案 +i18n.build_log.7c0e=構建日誌 +i18n.no_current_static_directory_permission.ed70=沒有當前靜態目錄許可權 +i18n.select_workspace_error.426e=選擇工作空間錯誤 +i18n.task_already_exists.f59a=任務已經存在啦 +i18n.start_distribution_with_count.cdc7=開始分發,需要分發 {} 個專案 +i18n.get_port_error.0698=獲取埠錯誤 +i18n.node_communication_failure.00fb=節點通訊失敗,請優先檢查限制上傳大小配置是否合理,或者網路連線是否被代理終端、防火牆終端等。 +i18n.static_file_management.6ac2=靜態檔案管理 +i18n.error_info.99ed=,錯誤資訊: +i18n.no_corresponding_docker_asset.6f06=沒有對應的 docker 資產 +i18n.build_data_not_exist.0225=構建資料不存在:{},任務自動丟棄\:{} +i18n.project_does_not_exist.3029=專案不存在 +i18n.corresponding_node_does_not_exist.72cb=當前對應的節點不存在 +i18n.upload_success_and_distribute.f446=上傳成功,開始分發\! +i18n.data_table_not_supported_for_grouping.6678=當前資料表不支援分組 +i18n.node_sync_project_failed.a2a7=節點同步專案失敗 +i18n.get_container_execution_result_interrupted.4a48=獲取容器執行結果操作被中斷\: +i18n.no_ssh_entry_found.d0e1=沒有找到對應的ssh項:{} +i18n.build_runs_on_image_interrupted.00fd=構建 runsOn 映象被中斷 +i18n.incorrect_parameter_format.9efb=傳入的引數格式不正確 +i18n.multiple_certificate_files_found.bee3=找到 2 個以上的證書檔案 +i18n.invalid_runs_on_image_name.4b96=runsOn 映象名稱不合法 +i18n.dockerfile_path_required.69ac=請填寫要執行的 Dockerfile 路徑 +i18n.file_upload_mode_not_configured.b3b2=沒有配置檔案上傳模式 +i18n.file_full_path.16cc=檔案全路徑:{} +i18n.container_startup_failure.532e=容器啟動失敗\: +i18n.environment_variables_not_found.dbd4=沒有環境變數 +i18n.workspace_name_already_exists.0f82=對應的工作空間名稱已經存在啦 +i18n.alarm_contact_or_webhook_required.6c24=請選擇一位報警聯絡人或者填寫webhook +i18n.upload_exception.cd6c=上傳異常: +i18n.user_not_exist.5387=不存在對應的使用者 +i18n.no_corresponding_file_colon.8970=沒有對應檔案\: +i18n.start_executing_upload_post_command.1c1b=開始執行上傳後命令 +i18n.data_type_not_configured_correctly.bf16=未正確配置資料型別 +i18n.monitoring_logs.2217=監控日誌 +i18n.ssh_script_batch_trigger_exception.70e1=SSH 指令碼批量觸發異常 +i18n.select_cluster.f8c3=請選擇叢集 +i18n.no_build_history.39f7=沒有對應的構建歷史 +i18n.docker_does_not_exist.bb41=對應的 docker 不存在 +i18n.trigger_exception.d624=觸發異常 +i18n.remote_addresses_not_configured.275e=還沒有配置允許的遠端地址 +i18n.table_error_workspace_data.9021=表 {}[{}] 存在 {} 條錯誤工作空間資料 -> {} +i18n.directory_cannot_skip_levels.179e=目錄不能越級: +i18n.normal_end.3bfe=正常結束 +i18n.start_building.1039=開始構建中 +i18n.empty_execution_result.9fe8=執行結果為空, +i18n.database_exception_due_to_resources.dbf1=資料庫異常,可能因為伺服器資源不足(記憶體、硬碟)等原因造成資料異常關閉。需要手動重啟服務端來恢復,: +i18n.prepare_rollback.dba6=開始準備回滾:{} -> {} +i18n.monitor_node_exception.6ff1=監控 {} 節點異常 {} +i18n.add_new_success.431a=新增成功! +i18n.no_parameters_added_with_minus_one.e47d=沒有新增任何引數\:-1 +i18n.welcome_join_session.1c16=歡迎加入\:{} 會話id\:{} +i18n.backup_old_package_failure_due_to_new_package_absence.b90c=備份舊程式包失敗:{},因為新程式包不存在:{} +i18n.completed_count_insufficient.02e9=完成的個數不足 {}/{} +i18n.cluster_info_incomplete_with_code.246b=叢集資訊不完整,不能加入該叢集\:-1 +i18n.log_recorder_not_enabled.5a4e=日誌記錄器未啟用 +i18n.exit_code.ea65=執行退出碼:{} +i18n.start_executing.f0b9=開始執行\: {} +i18n.authentication_config.964c=認證配置 +i18n.please_fill_in_information_and_check_validity.771a=請填寫資訊,並檢查是否填寫合法 +i18n.publish_exception.cf0b=執行釋出異常 +i18n.detect_local_docker_exception.ccfc=探測本地 docker 異常 +i18n.run_status_not_configured.e959=沒有配置 run.status +i18n.build_log_recorder_closed.1cc7=構建日誌記錄器已關閉,可能手動取消停止構建,流程\:{} +i18n.no_data_found.4ffb=沒有對應資料 +i18n.supported_comparison_operators_message.6d7a=表示式目前僅支援 \=\= 和 \!\= 比較 +i18n.get_container_log_failure.915d=獲取容器日誌失敗 +i18n.content_is_empty.3122=內容為空 +i18n.invalid_jar_file.e80a=jar 包檔案不合法 +i18n.alert_contact_exception.2cec=報警聯絡人異常 +i18n.publish_command_length_limit.66b0=釋出命令長度限制在4000字元 +i18n.unbind.6633=解綁 +i18n.asset_monitoring_thread_pool_rejected_task.222e=資產監控執行緒池拒絕了任務:{} +i18n.container_cluster.a5b4=容器叢集 +i18n.workspace_ssh_already_exists.ccc0=對應的工作空間已經存在當前 SSH 啦 +i18n.non_plaintext_variable_cannot_view.50ca=非明文變數不能檢視 +i18n.connect_plugin_failed.e492=連線外掛端失敗:{} {} {} +i18n.restore_project_failed.7f7c=還原專案失敗 +i18n.no_nodes.17b4=沒有任何節點 +i18n.scheduled_backup_log_failure.a0d7=定時備份日誌失敗 +i18n.folder_download_not_supported.c3b7=暫不支援下載資料夾 +i18n.user_not_exist_trigger_invalid.f375=對應的使用者不存在,觸發器已失效 +i18n.download_remote_file_exception.3ee0=下載遠端檔案異常 +i18n.only_tar_files_supported.dcc4=只支援tar檔案 +i18n.file_management_center.0f5f=檔案管理中心 +i18n.no_file_found.7d40=沒有對應到檔案 +i18n.server_captcha_generation_exception.54d0=當前伺服器生成驗證碼異常,自動禁用驗證碼 +i18n.node_script_template_execution_record.704a=節點指令碼模版執行記錄 +i18n.response_exception_status_code.cbca={} 響應異常 狀態碼錯誤:{} {} +i18n.current_project_associated_with_online_build_and_repository.96c5=當前【專案】關聯的【線上構建】關聯的【倉庫({})】被其他 {} 個不同釋出方式的【線上構建】繫結暫不支援遷移 +i18n.current_address_no_repository.db31=當前地址不存在倉庫: +i18n.dsl_not_configured.8a57=DSL 未配置執行管理或者未配置 {} 流程 +i18n.file_write_success.804a=檔案寫入成功 +i18n.node_address_required.71f1=節點地址不能為空 +i18n.cannot_edit_corresponding_config_file.8d10=不能編輯對應的配置檔案 +i18n.auth_directory_cannot_be_under_jpom.bb67=授權目錄不能位於Jpom目錄下 +i18n.docker_data_repair_not_needed.0fb9=機器 DOCKER 表已經存在 {} 條資料,不需要修復機器 DOCKER 資料 +i18n.invalid_project_path.04f7=專案路徑不能為空,不能為頂級目錄,不能包含中文 +i18n.file_type_not_supported2.d497=不支援的檔案型別: +i18n.docker_management.e7e5=Docker管理 +i18n.no_project_id_found.0f21=沒有找到對應的專案id\: +i18n.agent_response_empty.cc8e=agent 端響應內容為空 +i18n.no_available_maven_versions.dffe=maven 映象庫中沒有找到任何可用的 maven 版本 +i18n.static_directory_cannot_contain_relation.1a90=靜態目錄中不能存在包含關係: +i18n.cluster_info_incomplete.84a1=叢集資訊不完整,不能加入該叢集 +i18n.import_success.b6d1=匯入成功 +i18n.distribute_thread_exception.9725=分發執行緒異常 +i18n.need_handle_build_queue_count.c01e=需要處理的 {} 構建佇列數:{} +i18n.config_file_not_found.310e=未找到配置檔案\: +i18n.correct_verification_code_required.ff0d=請輸入正確的驗證碼 +i18n.no_corresponding_ssh.aa68=沒有對應的ssh +i18n.chunk_upload_file_exception.0dc3=分片上傳檔案異常 +i18n.no_execution_id.68dc=沒有執行ID +i18n.command_execution_record.56d5=命令執行記錄 +i18n.task_not_exist.47e9=不存在對應的任務 +i18n.configure_dsl_content.42e3=請配置 dsl 內容 +i18n.select_correct_node.1b4e=請選擇正確的節點 +i18n.select_node_error.dc0f=選擇節點錯誤 +i18n.docker_certificate_file_missing.ad46=docker 證書檔案丟失 +i18n.select_correct_post_publish_script.49d2=請選擇正確的釋出後指令碼 +i18n.distribute_management.3a2d=分發管理 +i18n.please_fill_in_correct_node_version.8483=請填入正確的 node 版本號 +i18n.sql_statement_too_long.38d6=sql 語句太長啦 +i18n.strict_mode_image_build_failure.ecea=嚴格模式下映象構建失敗,終止任務 +i18n.synchronization_failed.091a={} 同步失敗 {} +i18n.security_warning_h2_console.4669=【安全警告】資料庫賬號密碼使用預設的情況下不建議開啟 h2 資料 web 控制檯 +i18n.start_queuing_for_execution.7417=開始排隊等待執行 +i18n.disallowed_download.06a3=不允許下載當前地址的檔案 +i18n.cluster_id_changed.6e49=叢集ID 發生變化:{} -> {} +i18n.import_success_message.2df3=匯入成功(編碼格式:{}),更新 {} 條資料,因為節點分發/專案副本忽略 {} 條資料 +i18n.close_success.8a31=關閉成功 +i18n.no_config_file_found.9720=沒有找到對應配置檔案: +i18n.server_captcha_available.5570=當前伺服器驗證碼可用 +i18n.download_remote_file.ae84=下載遠端檔案 +i18n.backup_old_package.a7fc=備份舊程式包:{} +i18n.certificate_file_missing.c663=證書檔案丟失 +i18n.incorrect_range_information.a41c=range 傳入的資訊不正確 +i18n.user_directory_not_found.cfe3=使用者目錄沒有找到 +i18n.main_class_attribute_not_found.93e8=中沒有找到對應的MainClass屬性 +i18n.file_system_monitoring_exception.d4c0=檔案系統監控異常: +i18n.account_name_nickname_required.b757=請輸入賬戶暱稱 +i18n.docker_info_not_found.4f64=當前叢集未找到 docker 資訊 +i18n.distribution_exception_saving.8285={} {} 分發異常儲存 +i18n.no_main_class_found.b001=沒有找到對應的MainClass\: +i18n.save_succeeded.3b10=儲存成功 +i18n.select_node_to_modify.6617=請選擇要修改的節 +i18n.correct_dingtalk_address_required.2b4a=請輸入正確釘釘地址 +i18n.query_folder_sftp_failed.9d35=查詢資料夾 SFTP 失敗, +i18n.name_field_required.e0c5=第 {} 行 name 欄位不能位空 +i18n.plugin_end_log_connection_successful.9035=連線成功:外掛端日誌 +i18n.start_building_with_number_and_path.c41c=開始構建 \#{} 構建執行路徑 \: {} +i18n.start_executing_upload_pre_command.fb5c=開始執行上傳前命令 +i18n.delete_container_exception.9ad8=刪除容器異常 +i18n.query_folder_failed.3f0e=查詢資料夾失敗, +i18n.script_library.aed1=指令碼庫 +i18n.container_build_product_path_cannot_use_ant_pattern.ddc7=容器構建的產物路徑不能使用 ant 模式 +i18n.get_folder_failure.0fda=獲取資料夾失敗 +i18n.system_already_initialized.743c=系統已經初始化過啦,請勿重複初始化 +i18n.trigger_auto_execute_server_script_exception.8e84=觸發自動執行伺服器指令碼異常 +i18n.cleaned_data.0e9d={} 清理了 {}條資料 +i18n.no_implemented_feature.af80=沒有實現該功能 +i18n.no_corresponding_repository.dde9=沒有對應的倉庫 +i18n.get_docker_cluster_info_failure_with_code.fa77=獲取 docker 叢集資訊失敗\:-1 +i18n.get_container_execution_result_failure.1828=獲取容器執行結果失敗 +i18n.ssh_already_exists_with_message.d284=對應的SSH已經存在啦 +i18n.build_finished.7f38=構建結束 +i18n.not_jpom_install_package.2cca=此檔案不是 jpom 安裝包 +i18n.republishing.131d=重新發布中 +i18n.node_id_not_found.2f9e=沒有節點id +i18n.no_description.c231=\ 沒有描述 +i18n.certificate_in_use_by_docker.dd63=當前證書被 docker 關聯中,不能直接刪除 +i18n.no_corresponding_build_record.b3b2=沒有對應構建記錄. +i18n.auto_delete_data.ca62=\ 自動刪除 {} 表中資料 {} 條資料 +i18n.active_clearance_colon.96a6=主動清除: +i18n.correct_encoding_format_required.1f7f=請填寫正確的編碼格式, +i18n.notice_script_invocation_error.9002=noticeScript 呼叫錯誤 +i18n.go_plugin_version_required.ccf6=go 外掛 version 不能為空 +i18n.select_correct_log_path_or_no_auth_configured.9a9b=請選擇正確的日誌路徑,或者還沒有配置授權 +i18n.service_info_incomplete.968d=服務資訊不完整不能操作 +i18n.session_already_closed.8dcc=會話已經關閉啦,不能傳送訊息:{} +i18n.modify_or_add_data.e1f0=修改、新增資料 +i18n.alias_code_validation.8b99=別名碼只能是英文、數字 +i18n.configure_run_path_property.356c=請配置執行路徑屬性【jpom.path】 +i18n.node_script_template_log.85e3=節點指令碼模板日誌 +i18n.compression_success.80b3=壓縮成功 +i18n.initialize_workspace.bc97=初始化{}工作空間 +i18n.login_name_cannot_contain_chinese_and_special_characters.48a8=登入名不能包含漢字並且不能包含特殊字元 +i18n.cannot_join_cluster_as_role.01d4=不能以 {} 角色加入叢集 +i18n.not_an_enumeration.8244=不是列舉 +i18n.script_not_exist.b180=對應指令碼已經不存在啦 +i18n.already_offline.d3b5=已經離線啦 +i18n.rebuild_success.5938=重建成功 +i18n.global_workspace_variable_edit_in_system_management.58d2=全域性工作空間變數請到系統管理修改 +i18n.project_management.4363=專案管理 +i18n.node_service_not_running.ad89=【{}】節點的【{}】專案{}已經沒有執行 +i18n.node_script_template_title.4e74=節點指令碼模版 +i18n.no_build_record.66a2=沒有對應的構建記錄 +i18n.node_exception.bca7=節點異常: +i18n.no_distribution_project.d4d1=沒有分發專案 +i18n.missing_package_in_root_dir.8bab=一級目錄沒有%s包,請先到檔案管理中上傳程式的%s +i18n.exported_project_data.fd1f=匯出的專案資料 +i18n.permission_distribution_config_error.e7fb=許可權分發配置錯誤:{} {} +i18n.node_info.2dcf=節點資訊 +i18n.delete_success_with_colon.d44a=刪除成功\: +i18n.delete_table_data.c813=刪除表 {} 中 {} 條工作空間id為:{} 的資料 +i18n.socket_exception.d836=socket 異常 +i18n.file_does_not_exist_for_download.8dd6=檔案不存在,無法下載 +i18n.jpom_project_maintenance_system.7f8e=Jpom專案運維繫統 +i18n.parameter_error_id_cannot_be_empty.86cc=引數錯誤id不能為空 +i18n.prepare_backup.7970=開始準備備份專案檔案:{} {} +i18n.login_log.3fb2=登入日誌 +i18n.hard_drive_monitoring_error.43e7=硬碟資源監控異常: +i18n.dockerfile_not_found_in_repository.4168=倉庫目錄下沒有找到 Dockerfile 檔案\: {} +i18n.login_name_format_incorrect.f789=登入名格式不正確(英文字母 、數字和下劃線),並且長度必須 {}-{} +i18n.no_asset_machine.c77c=沒有對應的資產機器 +i18n.reconnect_plugin_failure_after_upgrade.73e3=升級後重連外掛端失敗\: +i18n.current_system_is_windows.91d1=當前系統為:windows +i18n.execute.1a6a=執行 +i18n.unknown_error.84d3=未知: +i18n.monitor_docker_exception_detail.e334=監控 docker[{}] 異常 {} +i18n.database_username_not_configured.a048=未配置(未解析到)資料庫使用者名稱 +i18n.upload_progress_template.ac3f=上傳檔案進度\:{}/{} {} +i18n.mark_already_exists.0ccc=標記已存在 +i18n.docker_not_found.2a2e=\ 沒有找到任何 docker。可能docker tag 填寫不正確,需要為 docker 配置標籤 +i18n.download_remote_file_failed.fcc3=下載遠端檔案失敗\: +i18n.no_file_found.6f1b=沒有找到 {} 檔案 +i18n.distribute_result.a230=分發結果:{} +i18n.multiple_ssh_addresses_found.b3f7=SSH 地址 {} 存在多個資料,將自動合併使用 {} SSH的配置資訊 +i18n.no_management_permission.fd25=您沒有對應管理許可權\:-2 +i18n.workspace_env_vars.f7e8=工作空間環境變數 +i18n.unsupported_mode.a3d3=暫不支援的模式: +i18n.select_node_and_project.6021=請選擇節點和專案 +i18n.operation_succeeded.3313=操作成功 +i18n.url_length_exceeded.ca1c=url 長度不能超過 200 +i18n.waiting_to_close_process.3634=等待關閉[Process]程序:{} +i18n.node_id.c90a=節點id +i18n.search_result_display.d2c3=在 {} 行中搜尋到並顯示 {} 行 +i18n.reset_super_admin_password_success.50c6=重置超級管理員賬號密碼成功,登入賬號為:{} 新密碼為:{} +i18n.current_address_may_not_be_git.41c6=當前地址可能不是 git 倉庫地址: +i18n.no_trigger_type_specified.5628=沒有對應的觸發器型別: +i18n.user_not_exist.4892=使用者不存在 +i18n.cleanup_history_build_failed_retrying.088e=清理歷史構建產物失敗,已經重新嘗試 +i18n.no_jpom_type_config_found.aa57=沒有找到 Jpom 型別配置 +i18n.repository_info_does_not_exist.4142=倉庫資訊不存在 +i18n.no_run_found_in_steps.a141=steps 中沒有發現任何 run , run 用於執行命令 +i18n.start_migrating_h2_data_to.f478=開始遷移 h2 資料到 {} +i18n.no_distribution_project_found.90b0=沒有找到對應的分發專案 +i18n.no_branch_name.1879=沒有 branch name +i18n.message_send_failed.4dbe=訊息傳送失敗,自動移除此會話\:{} +i18n.send_failed.9ca6=傳送失敗 +i18n.select_run_mode.5a5d=請選擇執行模式 +i18n.database_backup_label.62d8=資料庫備份 +i18n.auto_start_project_failed.c7b5=自動啟動專案失敗:{} {} +i18n.incorrect_type_passed.d42e=傳入的型別錯誤:{} +i18n.build_trigger_batch_exception.47d5=構建觸發批量觸發異常 +i18n.id_cannot_be_empty.8f2c=id 不能為空 +i18n.webhooks_invocation_error.9792=WebHooks 呼叫錯誤 +i18n.data_modification_time_format_incorrect.7ffe=資料修改時間格式不正確 {} {} +i18n.cannot_delete_recent_logs.ee19=不能刪除近一天相關的日誌(檔案修改時間) +i18n.agent_jar_not_exist.28ac=Agent JAR包不存在 +i18n.parse_certificate_unknown_error.c43c=解析證書發生未知錯誤: +i18n.node_info_incomplete.3b69=對應的節點資訊不完整不能繼續 +i18n.ssh_import_template_csv.14fa=ssh匯入模板.csv +i18n.build_info.224a=構建資訊 +i18n.rename_failed.0c76=重新命名失敗\: +i18n.execution_command_required.1cf3=請選擇執行的命令 +i18n.auto_migrate_associated_build_and_repo.0b3f=自動遷移關聯的構建:{} 和 倉庫:{} +i18n.system_logs.84aa=系統日誌 +i18n.machines_ssh_data_fixed.1387=成功修復 {} 條機器 SSH 資料 +i18n.docker_label_required.b690=請填要執行 docker 標籤 +i18n.no_matching_permission.09cf=未匹配到合適的許可權不足 +i18n.data_id_does_not_exist.a566=資料id 不存在 +i18n.no_ssh_info.a8ec=沒有對應 SSH 資訊 +i18n.distribution_with_build_items_message.45f5=當前分發存在構建項,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.please_pass_body_parameter.4e5c=請傳入 body 引數 +i18n.scanning_in_progress.7444=當前正在掃描中 +i18n.get_decrypt_distribution_failure.4feb=獲取解密分發失敗 +i18n.retention_days.3c7d=,保留天數:{} +i18n.no_log_info.d551=還沒有日誌資訊 +i18n.authorized_cannot_be_reloaded.6ece=authorized 不能重複載入 +i18n.private_key_not_found_in_zip.e103=壓縮包裡沒有找到私鑰檔案 +i18n.restore_authorization_data_exception.015a=恢復授權資料異常或者沒有選擇授權目錄 +i18n.select_correct_script.ff2d=請選擇正確的指令碼 +i18n.get_decrypt_implementation_failure.e77a=獲取解密實現失敗 +i18n.no_machine.89ed=沒有對應的機器 +i18n.modify_success.69be=修改成功 +i18n.publish_project_package_success.b0ce=釋出專案包成功:{} +i18n.service_info_incomplete_with_code3.8612=服務資訊不完整不能操作:-3 +i18n.repository_info_error.5b0a=第 {} 行 倉庫資訊有誤 +i18n.node_not_enabled.10ef={} 節點未啟用 +i18n.client_id_not_configured.ab8e=沒有配置 clientId +i18n.build_product_dir_not_empty.ba06=構建產物目錄不能為空,長度1-200 +i18n.trigger_token_error_or_expired.8976=觸發token錯誤,或者已經失效 +i18n.auth_info_error.c184=授權資訊錯誤 +i18n.download_file_error.5bcd=下載檔案異常\: +i18n.rollback_ended.fb1d=執行回滾結束:{} +i18n.reload_project_exception.b566=過載專案異常 +i18n.machine_name_required.e8cf=請填寫機器名稱 +i18n.cumulative_filter_files.448d={} 累積過濾:{} 個檔案 +i18n.project_path_already_exists_as_file.a900=專案路徑是一個已經存在的檔案 +i18n.decrypt_failure.ad83=解密失敗 +i18n.disallowed_file_format.d6e4=不允許的檔案格式 +i18n.operation_monitoring.0cd5=操作監控 +i18n.cron_expression_format_error.6dcd=cron 表示式格式不正確 +i18n.id_already_exists.6208=id已經存在啦 +i18n.ssh_info.ebe6=SSH 資訊 +i18n.current_status.81c0=\ 當前還在: +i18n.project_path_no_spaces.263c=專案路徑不能包含空格 +i18n.please_fill_in_address_of.9e02=請填寫 %s 的 地址 +i18n.rsa_private_key_file_invalid.5f12=rsa 私鑰檔案不存在或者有誤 +i18n.ssh_node_required.4566=請選擇 ssh 節點 +i18n.folder_or_file_exists.c687=資料夾或者檔案已存在 +i18n.parse_error.da6d=\ 解析錯誤\: +i18n.no_corresponding_docker.733e=沒有對應的 docker +i18n.file_published.d1d9=檔案釋出 +i18n.java_ext_dirs_cp_required.1f4a=JavaExtDirsCp 模式 javaExtDirsCp必填 +i18n.correct_url_required.67a3=請填寫正確的 url +i18n.pagination_error.6759=篩選的分頁有問題,當前頁碼查詢不到任何資料 +i18n.correction_success.38bc=修正成功 +i18n.project_id_in_use.1adb=當前專案id已經被正在執行的程式佔用 +i18n.import_success_with_count.22b9=匯入成功,新增 {} 條資料,修改 {} 條資料 +i18n.file_upload_exception.a5f6=發生檔案上傳異常:{} {} +i18n.please_fill_in_correct_maven_version.468c=請填入正確的 maven 版本號,可用的版本如下: +i18n.node_has_log_search_projects_cannot_delete.a388=該節點存在日誌搜尋(閱讀)專案,不能 +i18n.copy_success.20a4=複製成功 +i18n.selected_node_required.d65a=至少選擇一個節點 +i18n.docker_already_exists_in_workspace.a0de=對應工作空間已經存在對應的 docker 啦 +i18n.upload_exception_mismatch.0b25=上傳異常,完成數量不匹配 +i18n.multiple_docker_addresses_found.0f82=DOCKER 地址 {} 存在多個資料,將自動合併使用 {} DOCKER 的配置資訊 +i18n.download_progress.898a=當前進度:{} ,檔案總大小:{},已經下載:{} +i18n.data_not_exist.41f9=對應資料不存在 +i18n.create_folder_failure.b632=建立資料夾失敗(資料夾名可能已經存在啦)\: +i18n.gradle_plugin_version_required.b983=gradle 外掛 version 不能為空 +i18n.node_account_required.2d90=請填寫節點賬號 +i18n.publish_project_package_failed.9514=釋出專案包失敗: +i18n.online_agent_close_not_supported.d81d=不支援線上關閉 Agent 程序 +i18n.port_configuration_check.d888=埠號是否配置正確,防火牆規則, +i18n.local_docker_exists.ec31=已經存在本地 docker 資訊啦,不要重複新增: +i18n.close_client_session_exception.530a=關閉客戶端回話異常 +i18n.start_execution.00d7=開始執行 +i18n.current_operation_not_supported.3aec=不支援當前操作: +i18n.repository_info_cannot_be_empty.67d2=倉庫資訊不能為空 +i18n.permission_distribution_config_error_class_not_found.ca67=許可權分發配置錯誤:{} {} class not find +i18n.invalid_email_format.7526=郵箱格式不正確 +i18n.load_plugin.1f64=載入:{} 外掛 +i18n.host_field_required.5c36=第 {} 行 host 欄位不能位空 +i18n.container_build_interrupted.a17b=容器 build 被中斷\: +i18n.upload_action.d5a7=上傳 +i18n.delete_file_failure.041f=刪除檔案失敗,請檢查 +i18n.no_cluster_info_found.fb40=沒有找到對應的叢集資訊 +i18n.script_template_log.30cb=指令碼模板日誌 +i18n.file_name_not_configured.39fa=沒有配置fileName +i18n.ssh_asset_management.3b6c=SSH資產管理 +i18n.operation_succeeded_with_details.c773=操作成功\: +i18n.no_cache_info.fba1=沒有對應的快取資訊 +i18n.save_distribution_project_failed.ceec=儲存分發專案失敗 +i18n.download_success_and_distribute.ae94=下載成功,開始分發\! +i18n.start_executing_process.9cb8=開始執行 {}流程 +i18n.select_monitoring_function.c6e4=請選擇監控的功能 +i18n.project_path_auth_required.9e58=專案路徑授權不能為空 +i18n.second_level_directory_cannot_skip_levels.c9fb=二級目錄不能越級: +i18n.ssh_error_or_folder_not_configured.c087=ssh error 或者 沒有配置此資料夾 +i18n.static_directory_auth_cannot_be_under_jpom.8879=靜態目錄授權不能位於Jpom目錄下 +i18n.file_size_exceeds_limit.8272=上傳檔案大小超出限制 +i18n.please_fill_in_from.7268=請填寫from +i18n.unsupported_system_type_with_placeholder.d5cc=不支援的系統型別:{} +i18n.configure_correct_cluster_id.5a78=請配置正確的叢集Id,【jpom.clusterId】 +i18n.ssh_not_exist.2e40=不存在對應ssh +i18n.ssl_connection_failed.e26c=SSL 無法連線(請檢查證書信任的地址和配置的 docker host 是否一致)\: +i18n.soft_link_project_does_not_exist.8ad2=被軟鏈的專案已經不存在啦, +i18n.command_name_required.49fa=請輸入命令名稱 +i18n.service_exception.3821=服務異常: +i18n.jpom_log_not_configured.3153=沒有配置 JPOM_LOG +i18n.log_reading.a4c8=日誌閱讀 +i18n.no_corresponding_data_or_permission.1291=沒有對應的資料或者沒有此資料許可權 +i18n.no_log_info_or_log_file_error.2c25=還沒有日誌資訊或者日誌檔案錯誤 +i18n.disable_monitoring.4615=禁用監控 +i18n.account_not_bound_to_any_workspace.fd61=當前賬號沒有繫結任何工作空間,請聯絡管理員處理 +i18n.initialize_user_failure.fe27=初始化使用者失敗 +i18n.container_cli_interrupted.b67f=容器cli被中斷\: +i18n.project_data_lost.2ae3=專案資料丟失 +i18n.permission_function_not_configured_correctly.84dd=許可權功能沒有配置正確 {} +i18n.execution_exception_with_detail.c142=執行異常:{} +i18n.download_failed.65e2=下載失敗 +i18n.remote_download_host_cannot_be_empty.cdf5=執行遠端下載的 host 不能配置為空 +i18n.backup_old_package_failure_due_to_old_package_absence.53aa=備份舊程式包失敗:{},因為舊程式包不存在 +i18n.no_workspace_found_for_data.ac0f=沒有找到資料對應的工作空間,不能進行操作 +i18n.container_build_host_config_field_not_exist.6f61=容器構建 hostConfig 欄位【{}】不存在 +i18n.clear_script_file_failed.f595=清理指令碼檔案失敗 +i18n.import_save_failure.001a=匯入第 {} 條資料儲存失敗\:{} +i18n.not_connected.fa55=還沒有連線上 +i18n.unsupported_request_method.45d7=不被支援的請求方式 +i18n.incomplete_data_not_supported.b5d3=資料不完整,暫不支援操作 +i18n.node_delete_project_failed.534c=節點刪除專案失敗 +i18n.wrong_id.ab4d=錯誤的ID +i18n.soft_link_project_does_not_exist.4e4f=軟鏈專案已經不存在啦 +i18n.invalid_zip_file.3092=上傳的壓縮包不是 Jpom [{}] 包 +i18n.publish_command_non_zero_exit_code.ea80=執行釋出命令退出碼非0,{} +i18n.project_path_promotion_issue.2250=專案路徑存在提升目錄問題 +i18n.handle_node_deletion_script_failure_duplicate.821e=處理 {} 節點刪除指令碼失敗{} +i18n.no_matching_process_type.b468=未匹配到合適的處理型別 +i18n.unsupported_type.7495=不支援的型別 +i18n.submit_task_queue_success.5f5b=提交任務佇列成功,當前佇列數: +i18n.login_failed_please_enter_correct_password_and_account.03b2=登入失敗,請輸入正確的密碼和賬號,多次失敗將鎖定賬號 +i18n.no_docker_info_no_need_to_fix_machine_data.f45e=沒有任何 DOCKER 資訊,不需要修復機器 DOCKER 資料 +i18n.cluster_info_incomplete_for_operation.ad96=叢集資訊不完整,不能操作 +i18n.yml_configuration_content_error.08f8=yml 配置內容錯誤 +i18n.password_cannot_be_empty.89b5=密碼不能為空 +i18n.ssh_not_exist.08a2=SSH不存在 +i18n.file_merge_exception_details.e9d0=檔案合併異常 {}\:{} -> {} +i18n.configure_correct_self_hosted_gitlab_address.ad50=請配置正確的自建 gitlab 地址 +i18n.unknown_jsch_log_level.6a5c=未知的 jsch 日誌級別:{} +i18n.no_oauth2_found.ea74=沒有找到對應的 oauth2, +i18n.no_workspace_info.75ae=沒有任何工作空間資訊 +i18n.do_not_reinitialize_database.9bb5=不要重複初始化資料庫 +i18n.update_container_service_exception.2249=更新容器服務呼叫容器異常 +i18n.file_cleanup_failed.511e=清理檔案失敗 +i18n.cluster_not_bound_to_group_for_ssh_monitoring.c894=當前叢集還未繫結分組,不能監控 SSH 資產資訊 +i18n.container_log_fetch_exception.591a=拉取 容器日誌異常 +i18n.project_path_conflict.8c6f=專案路徑和【{}】專案衝突\:{} +i18n.cannot_delete_default_workspace.0c06=不能刪除預設工作空間 +i18n.operation_failed_with_details.7280=操作失敗\: +i18n.project_type_not_supported_for_startup.7bd1=當前專案型別不支援啟動 +i18n.no_changes_in_repository_code.b1aa=倉庫程式碼沒有任何變動終止本次構建:{} +i18n.content_cannot_be_empty.9f0d=內容不能為空 +i18n.remote_repository_does_not_exist.7009=當前地址遠端不存在倉庫: +i18n.please_fill_in_host.7922=請填寫host +i18n.no_type_specified.8c65=沒有對應型別: +i18n.distribute_name_cannot_be_empty.0637=分發名稱不能為空 +i18n.repository_password_cannot_be_empty.20b3=倉庫密碼不能為空 +i18n.delete_file_failure_with_full_stop.6c96=刪除檔案失敗: +i18n.distribution_not_exist.cf8a=對應的分發不存在 +i18n.user_or_group_bindings_exist_in_workspace.d57b=當前工作空間下還繫結著使用者(許可權組)資訊 +i18n.user_account.cbf7=使用者賬號 +i18n.not_logged_in.c89f=當前未登入不能操作此資料 +i18n.current_system_is_mac.0139=當前系統為:mac +i18n.docker_certificate_migrated.b3d3=docker[{}] 證書成功遷移到證書管理中 +i18n.certificate_type_not_found.6706=沒有證書型別 +i18n.no_permission_to_execute_command.04d4=沒有執行相關命令許可權 +i18n.type_error.395f=型別錯誤 +i18n.node_service_stopped_successful_restart.603b=【{}】節點的【{}】專案{}已經停止,已經執行重啟操作,結果成功 +i18n.git_reset_hard_failed_status_code.d818=git reset --hard失敗狀態碼\: +i18n.no_certificate_files_found.ff6d=沒有找到任何證書檔案 +i18n.introducing_script_content.a55b=引入指令碼內容:{}[{}] +i18n.script_cannot_be_empty.f566=指令碼不能為空 +i18n.user_permission_group.52a4=使用者許可權組 +i18n.python3_plugin_version_required.a0ce=python3 外掛 version 不能為空 +i18n.update_condition_not_found.0870=沒有更新條件 +i18n.repository_id_cannot_be_empty.a42c=倉庫ID不能為空 +i18n.workspace_required.b3bd=請選擇工作空間 +i18n.get_container_log_interrupted_message.83a5=獲取容器日誌被中斷\: +i18n.trigger_auto_execute_ssh_command_template_exception.7451=觸發自動執行SSH命令模版異常 +i18n.start_syncing_to_file_management_center.0a03=開始同步到檔案管理中心{} +i18n.delete_success.0007=刪除成功 +i18n.select_correct_pre_publish_script.d230=請選擇正確的釋出前指令碼 +i18n.greeting.5ecd=您好,Jpom +i18n.ssh_connection_failed.4719=ssh連線失敗 +i18n.oauth2_redirect_failed.6dcd=跳轉 oauth2 失敗,{} {} +i18n.data_workspace_mismatch.ae1d=資料工作空間和操作工作空間不一致 +i18n.process_file_event_exception.e8e6=處理檔案事件異常 +i18n.current_docker_cluster_has_no_management_nodes_online.56cd=當前 {} docker 叢集沒有管理節點線上 +i18n.machine_docker_info.9914=機器DOCKER資訊 +i18n.manual_cancel_distribution.7bf6=手動取消分發 +i18n.correct_information_required.5e12=請輸入正確的資訊 +i18n.cluster_status_code_exception.9d89=叢集狀態碼異常:{} {} +i18n.restart_operation.5e3a=執行重啟操作 +i18n.no_h2_data_info_for_migration.5799=沒有 h2 資料資訊不用遷移 +i18n.publish_success.2fff=釋出成功 +i18n.system_cache.c4a8=系統快取 +i18n.distribution_machine_required.5921=請選擇分發的機器 +i18n.build_call_container_exception.6e04=構建呼叫容器異常 +i18n.process_killed_successfully.a4c3=成功kill +i18n.build_name_not_empty.4154=構建名稱不能為空 +i18n.auto_clear_data_errors.112f=自動清除資料錯誤 {} {} +i18n.publish_directory_is_empty.79c6=釋出目錄為空 +i18n.file_or_directory_not_found.f03e=檔案不存在或者是目錄\: +i18n.clear_file_cache_failed.5cd1=清空檔案快取失敗 +i18n.file_downloading_status.c995=檔案下載中: +i18n.system_IP_authorization.9c08=系統配置IP授權 +i18n.node_failed.20d5=節點失敗: +i18n.project_has_node_distribution_cannot_delete.41b0=當前專案存在節點分發,不能直接刪除 +i18n.oshi_system_monitoring_exception.5c1c=oshi 系統監控異常 +i18n.empty_folder_cannot_be_packed.5a75=資料夾為空,不能打包 \# +i18n.backup_file_not_exist.9628=備份檔案不存在 +i18n.auto_migrate_associated_build.a060=自動遷移關聯的構建:{} +i18n.node_upgrade_failed.4493=節點升級失敗: +i18n.current_docker_offline.a509=當前 {} docker 不線上 +i18n.protocol_field_value_error.2b41=第 {} 行 protocol 欄位值錯誤(http/http/ssh) +i18n.invalid_file_type.7246=上傳的檔案不是 zip +i18n.database_exception.4894=資料庫異常 +i18n.clear_success_message.51f4=清除成功 +i18n.data_not_supported_for_sorting.5431=當前資料不支援排序 +i18n.command_non_zero_exit_code.a6e1=執行命令退出碼非0,{} +i18n.table_info_configuration_error.b050=表資訊配置錯誤 +i18n.close_session_exception_with_detail.85f0=關閉會話異常:{} +i18n.auto_delete_expired_build_history_files.723b=自動刪除過期的構建歷史相關檔案:{} {} +i18n.build_product_file_sync_failed.0e64=構建產物檔案同步到檔案管理中心失敗,當前檔案已經存檔案管理中心存在啦 +i18n.manager_node_not_found.df04=當前叢集未找到管理節點 +i18n.system_restart_cancel_download.444e=系統重啟取消下載任務 +i18n.yml_config_format_error_illegal_field.16ea=yml 配置內容格式有誤請檢查後重新操作(請檢查是否有非法欄位): +i18n.delete_failure_with_colon_and_full_stop.bc42=刪除失敗: +i18n.product_directory_cannot_skip_levels.3ad4=產物目錄不能越級: +i18n.fix_null_workspace_data.4d0b=修復工作空間為 null 的資料 {} {} +i18n.soft_link_project_department_exists.fa97=軟鏈的專案部存在 +i18n.docker_info.00d2=docker 資訊 +i18n.log_file_does_not_exist.f6c6=日誌檔案不存在 +i18n.system_admin_not_found.6f6c=沒有找到系統管理員 +i18n.no_file_info.db01=沒有對應的檔案資訊 +i18n.record_operation_log_exception.8012=記錄操作日誌異常 +i18n.corresponding_file_required.57b3=請選擇對應到檔案 +i18n.build_command_no_delete.df52=構建命令不能包含刪除命令 +i18n.file_merge_error.f32f=檔案合併後異常,檔案不完成可能被損壞 +i18n.script_info_not_found.bd8d=找不到對應的指令碼資訊 +i18n.cannot_modify_own_info.4036=不能修改自己的資訊 +i18n.modify_db_password_must_restart.d08d=修改資料庫密碼必須重啟 +i18n.ssh_already_bound_to_other_node.2d4e=對應的SSH已經被其他節點繫結啦 +i18n.not_jpom_package.ea3e=此包不是Jpom【{}】包 +i18n.auth_failed.2765=授權失敗\: +i18n.node_name.b178=節點名稱 +i18n.current_system_is_linux.e377=當前系統為:linux +i18n.project_id_cannot_contain_spaces.251d=專案Id不能包含空格 +i18n.start_publishing_file.a14e=開始釋出檔案 +i18n.checkout_version.a586=把版本:%s check out +i18n.check_docker_url_exception.4302=檢查 docker url 異常 {} +i18n.execution_frequency.d014={} 秒執行一次 +i18n.parse_jar.a26e=解析jar +i18n.jpom_verification_code.5b5b=Jpom 驗證碼 +i18n.please_fill_in_personal_token.970a=請填寫個人令牌 +i18n.parent_table_info_config_error.2f52=父級表資訊配置錯誤, +i18n.physical_node_pull_records.99df={} 物理節點拉取到 {} 個執行記錄,更新 {} 個執行記錄 +i18n.login_success.71fa=登入成功 +i18n.clear_temp_file_failed_check_directory.7340=清除臨時檔案失敗,請檢查目錄: +i18n.incorrect_repository_credentials.f1c8=倉庫賬號或者密碼錯誤: +i18n.request_failed_message.9c71=請求失敗\: status\: %s body\: %s headers\: %s +i18n.send_alert_error.cd38=傳送報警資訊錯誤 +i18n.binding_success.1974=繫結成功 +i18n.docker_not_exist.7ed8=不存在對應的 docker +i18n.monitored_directory_does_not_exist.fa4e=被監控的目錄不存在忽略建立監聽器:{} +i18n.no_distribution_exists.4425=不存在分發 +i18n.old_version_project_logs_exist_while_running.75ab=存在舊版專案日誌但專案在執行中需要停止執行後手動遷移:{} {} +i18n.data_backup.9e26=資料備份 +i18n.ssh_monitor_execution_error.2d3c={} ssh 監控執行存在異常資訊:{} +i18n.download_file_size.d4de=下載成功檔案大小: +i18n.user_login_log.0c00=使用者登入日誌 +i18n.cluster_address_check_exception.cd92=填寫的叢集地址檢查異常,請確認叢集地址是正確的服務端地址, +i18n.no_shard_id_info.30f8=沒有分片 id 資訊 +i18n.socket_error.18c1=socket 錯誤 +i18n.synchronization_node_failure.8a2c=同步節點 {} 失敗 {} +i18n.build_thread_pool_rejected_task.3bad=構建執行緒池拒絕了未知任務:{} +i18n.need_configure_absolute_path.f2e6=需要配置絕對路徑: +i18n.auto_start_timed_task_message.9637={} 定時任務已經自動啟動\:{} +i18n.need_execute_callbacks.b708=需要執行 {} 個回撥 +i18n.protocol_type_not_supported2.e519=不支援的協議型別 +i18n.parameter_error_id_error.58ce=引數錯誤id error +i18n.unsupported_type_with_placeholder.71a2=不支援的型別:{} +i18n.publish_method_format.4622=釋出的方式:{} +i18n.ssh_bound_to_node_message.7b64=當前ssh被節點繫結,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.send_alert_notification_exception.6788=傳送報警通知異常 +i18n.container_build_host_config_conversion_failure.27aa=容器構建 hostConfig 引數 {} 轉換失敗:{} +i18n.build_task_count_and_queue_count.f0b6=當前構建中任務數:{},佇列中任務數:{} {} +i18n.node_cache.d68c=節點快取 +i18n.cluster_node_not_in_system.0645=當前叢集對應的節點,不在本系統中無法退出叢集 +i18n.file_search_failed.231b=檔案搜尋失敗 +i18n.execution_ended_with_duration.a59b=執行結束 {}流程,耗時:{} +i18n.ssh_monitor_info_result.a660={} ssh 監控資訊結果:{} {} +i18n.certificate_serial_number_not_found.c8d1=沒有證書序列號 +i18n.configure_table_name.f6fd=請配置 table Name +i18n.project_has_monitoring_items_cannot_migrate.c7f6=當前專案存在監控項,不能直接遷移 +i18n.date_format_error.3d1c=日期格式錯誤\: +i18n.unexpected_exception_with_details.247d=發生異常:{} {} +i18n.static_directory_not_configured.acbc=未配置靜態目錄 +i18n.exported_repo_data.bac5=匯出的 倉庫資訊 資料 +i18n.current_cluster_is_bound_to_workspace_cannot_be_deleted_directly.94c2=當前叢集還被工作空間繫結,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.repository_import_template.5e2d=倉庫資訊匯入模板.csv +i18n.execution_node_required.d747=請選擇執行節點 +i18n.program_error_null_pointer.12e1=程式錯誤,空指標 +i18n.delete_build_cache.c7f3=刪除構建快取 +i18n.system_interruption.e37c=系統中斷異常 +i18n.stop_running.1d4e=停止執行 +i18n.selected_weekday_incorrect.4cd4=選擇的周幾不正確 +i18n.node_has_monitoring_items_cannot_delete.0304=該節點存在監控項,不能 +i18n.existing_project_cannot_be_soft_link.aa5a=已經存在的專案不能修改為軟鏈專案 +i18n.git_fetch_failed_status_code.5187=git fetch失敗狀態碼\: +i18n.ssh_folder_creation_exception.6ed2=ssh建立資料夾異常 +i18n.ssh_batch_command_execution_exception.029a=ssh 批量執行命令異常 +i18n.content_type_not_supported.81a9=不支援的 contentType +i18n.cloud_server_network_issues.a865=雲伺服器的安全組配置等網路相關問題排查定位。 +i18n.configure_correct_redirect_url.058e=請配置正確的重定向 url +i18n.no_cache_info_with_minus_one.52f2=沒有對應的快取資訊:-1 +i18n.no_node_entry_found.b1ef=沒有找到對應的節點項:{} +i18n.execution_interrupted_message.2597=執行被中斷:{} +i18n.build_not_exist.c2ac=不存在對應的構建 +i18n.demo_account_not_support_reset_password.a595=演示賬號不支援重置密碼 +i18n.build_command_execution.a55c=執行構建命令 +i18n.no_docker_info_found.6d38=沒有找到對應的 docker 資訊 +i18n.update_docker_machine_id_failed.063d=更新 DOCKER 表機器id 失敗: +i18n.build_status_abnormal.8ca1=構建狀態異常或者被取消 +i18n.node_connection_failure.896d=節點連線失敗,請檢查節點是否線上 +i18n.login_name_already_exists.2511=登入名已經存在 +i18n.port_error.312e=埠錯誤 +i18n.account_disabled.9361=賬號已經被禁用,不能使用 +i18n.file_does_not_exist_anymore.2fab=檔案已經不存在啦 +i18n.system_error.9417=系統錯誤\! +i18n.file_type_not_supported_with_placeholder.db22=不支援的檔案型別\:{} +i18n.suffix_cannot_be_empty.ec72=允許編輯的檔案字尾不能為空 +i18n.read_system_parameter_exception.ee72=讀取系統引數異常 +i18n.check_docker_cert_exception.8042=檢查 docker 證書異常 {} +i18n.ssh_terminal.ec50=SSH終端 +i18n.batch_trigger_project_exception.3c28=專案批量觸發異常 +i18n.data_name_label.5a14=資料名稱 +i18n.cannot_create_config_file_in_environment.55bb=當前環境不能建立配置檔案 +i18n.ssh_file_manager.1482=SSH檔案管理 +i18n.i18n_node_already_exists.632d=對應的節點已經存在拉 +i18n.address_not_configured.f2eb=未配置地址 +i18n.migration_success.b20d={} 遷移成功 {} 條資料 +i18n.no_corresponding_file.97b5=沒有對應檔案 +i18n.please_fill_in_name.52f3=請填寫 名稱 +i18n.unable_to_access_node_network.4e09=無法訪問節點網路(未知的名稱或服務),請檢查主機名或者 DNS 是否可用。 +i18n.ssh_already_exists_in_workspace.569e=對應工作空間已經存在該 ssh 啦\: +i18n.update_operation_log_failed.d348=更新操作日誌失敗 +i18n.cron_expression_incorrect.b41a=cron 表示式不正確, +i18n.script_template_id_required.f339=請填寫指令碼模板id +i18n.start_rolling_back.f020=開始回滾:{} +i18n.execution_exception.b0d5=執行異常 +i18n.corresponding_function.5bb5=對應功能【{}-{}】 +i18n.plugin_parameter_incorrect.a355=外掛端使用引數不正確 +i18n.operation_failed.3d94=操作失敗 +i18n.start_publishing.c0b9=開始釋出中 +i18n.monitor_info.f299=監控資訊 +i18n.node_already_exists.28ea=對應的節點已經存在啦 +i18n.request_needs_decoding.d4d7=當前請求需要解碼:{} +i18n.no_parameters_added.1721=沒有新增任何引數 +i18n.repo_already_exists.38a3=已經存在對應的倉庫資訊啦 +i18n.publish_script_exit_code.0f69=執行釋出指令碼的退出碼是:{} +i18n.method_not_supported.90c4=當前方法不被支援,暫時不能使用 +i18n.general_execution_exception.62e9=執行異常: +i18n.distribute_info_error_no_projects.e75f=分發資訊錯誤,沒有任何專案 +i18n.unknown_prune_type.0931=pruneType 未知 +i18n.error_sql.15ff=錯誤 sql\:{} +i18n.verification_method_not_configured.7358={}未配置驗證方法:{} +i18n.local_git_certificate_not_supported.b395=暫時不支援本地 git 指定證書拉取程式碼 +i18n.unsupported_method_with_colon.eae8=不支援的方式: +i18n.current_docker_has_no_cluster_info.0b52=當前 docker 沒有叢集資訊 +i18n.cannot_delete_self.fec9=不能刪除自己 +i18n.operation_monitoring_error.8036=執行操作監控錯誤 +i18n.alias_or_token_error.d5c6=別名或者token錯誤,或者已經失效 +i18n.delete_old_package.ca95=刪除舊程式包:{} +i18n.table_info_configuration_error_message.6452=表資訊配置錯誤, +i18n.login_name_already_taken.5b46=當前登入名已經被系統佔用 +i18n.auto_backup_h2_database.2ed0=自動備份 h2 資料庫檔案,備份檔案位於:{} +i18n.verification_code_disabled.349b=驗證碼已禁用 +i18n.unsupported_type_with_colon2.7de2=不支援的型別: +i18n.node_service_stopped_failed_restart.4307=【{}】節點的【{}】專案{}已經停止,已經執行重啟操作,結果失敗 +i18n.associated_workspace.885b=所屬工作空間 +i18n.auto_clear_machine_node_stats_logs.5279=自動清理 {} 條機器節點統計日誌 +i18n.unsupported_mode.501d=不支援的模式 +i18n.missing_script_message.af89=找不到對應的指令碼 +i18n.start_migrating.20d6=開始遷移 {} {} +i18n.incompatible_database_version.8f7b=資料庫版本不相容,需要處理跨版本升級。 +i18n.admin_account_required.31e0=系統中的系統管理員賬號數量必須存在一個以上 +i18n.process_does_not_exist.4e39=流程不存在 +i18n.no_ssh_commands_to_execute_after_publish.89ba=沒有需要執行釋出後的ssh命令 +i18n.correct_remote_address_required.0ce1=請輸入正確的遠端地址 +i18n.comparison_data_not_found.413e=沒有要對比的資料 +i18n.invalid_remote_address_format.7f32=配置的遠端地址不規範,請重新填寫: +i18n.docker_console_connection_timeout.b2c7=docker 控制檯連線超時 +i18n.send_success.9db9=傳送成功 +i18n.start_distribution.bce5=開始分發 +i18n.heartbeat_message_forwarding_failed.89cc=心跳訊息轉發失敗 {} {} +i18n.asset_machine_node_statistics.4a03=資產機器節點統計 +i18n.repository_does_not_exist.3cdb=倉庫不存在 +i18n.select_correct_build_method.84c4=請選擇正確的構建方式 +i18n.project_file_manager.c8cb=專案檔案管理 +i18n.no_ssh_info_no_need_to_fix_machine_data.0946=沒有任何ssh資訊,不需要修復機器 SSH 資料 +i18n.data_id_already_exists.28b6=資料Id已經存在啦:{} \: {} +i18n.event_script_interrupted.8c79=事件指令碼中斷: +i18n.configure_user_notification.250d=請配置使用者通知 +i18n.operation_status_code.8231=操作狀態碼 +i18n.agent_jar_damaged.74a8=Agent JAR 損壞請重新上傳, +i18n.read_error.7fa5=讀取錯誤 +i18n.network_resource_monitoring_error.4ede=網絡卡資源監控異常: +i18n.select_workspace_to_modify.ac87=請選擇要修改的工作空間 +i18n.decode_failure.822e=解碼失敗 +i18n.restart_failed.f92a=重啟失敗 +i18n.login_name_already_taken_message.b01f=當前登入名已經被系統佔用啦 +i18n.forbidden_operation_time.d83d=【禁止操作】當前時段禁止執行 +i18n.log_file_not_found.7f2e=沒有日誌檔案\: +i18n.protocol_type_not_supported.7a66=不支援到協議型別 +i18n.execution_result_file_not_found_in_container.cf18=容器中沒有找到執行結果檔案\: {} +i18n.execution_exception_with_flow.6d4b=執行異常[{}]流程:{} +i18n.trigger_success.f9d1=觸發成功 +i18n.start_pulling.57ab=開始拉取 +i18n.publish_task_execution_exception.c296=執行釋出任務異常 +i18n.no_node_specified.fa3d=沒有對應的節點: +i18n.no_corresponding_command.165e=沒有對應對命令 +i18n.exclusion_success.7d46=剔除成功 +i18n.oauth2_not_enabled.c8b7=沒有開啟此 {} oauth2 +i18n.cannot_delete_online_cluster.11ad=不能刪除線上的叢集 +i18n.no_corresponding_script_info_or_global_script_selected.765b=沒有對應到指令碼資訊或者選擇全域性指令碼 +i18n.select_file.9feb=請選擇檔案 +i18n.project_has_build_items_cannot_delete.c2df=當前專案存在構建項,不能直接刪除 +i18n.data_id_label.81b6=資料id +i18n.key_field_not_configured.7b22=沒有配置 KEY 欄位, +i18n.upgrade_duration_message.bab4=升級(重啟)中大約需要30秒~2分鐘左右 +i18n.git_submodule_update_failed_status_code.2218=git submodule update 失敗狀態碼\: +i18n.project_id_not_found.b87e=沒有專案id +i18n.operation_type.de9c=操作型別 +i18n.project_has_logs_cannot_migrate.2e0e=當前專案存在日誌閱讀,不能直接遷移 +i18n.delete_failure_with_colon.b429=刪除失敗\: +i18n.tag_cannot_contain_colon.f9ae=標籤不能包含 : +i18n.distribute_id_already_exists_globally.6478=分發id已經存在啦,分發id需要全域性唯一 +i18n.import_exception.04b6=匯入第 {} 條資料異常\:{} +i18n.script_template_log2.6b2c=指令碼模版日誌 +i18n.id_is_empty.3bbf=id 為空 +i18n.mfa_incorrect_code.8783=\ mfa 驗證碼不正確 +i18n.parameter_parsing_exception.0056=引數解析異常\:{} +i18n.file_upload_failure_due_to_missing_chunks.1865=檔案上傳失敗,存在分片丟失的情況, {} \!\= {} +i18n.root_path.1396=根路徑 +i18n.node_not_exist.0027=不存在對應的節點 +i18n.public_key_or_private_key_does_not_exist.dc0d=公鑰或者私鑰不存在 +i18n.no_user_info.0355=沒有對應的使用者資訊 +i18n.correct_verification_code2_required.df13=請輸入正確驗證碼 +i18n.please_fill_in_correct_python3_version.abb1=請填入正確的 python3 版本號 +i18n.incorrect_account_credentials_or_unsupported_auth.1ef9=賬號密碼不正確或者不支援的身份驗證, +i18n.file_already_exists.d60c=當前檔案已經存在啦,請勿重複上傳 +i18n.start_executing_pre_release_command.6c7e=開始執行 {} 釋出前命令 +i18n.publish_to_ssh_directory_required.56a6=請輸入釋出到ssh中的目錄 +i18n.config_path_exceeds_length_limit.f684=配置路徑超過{}長度限制\:{} +i18n.cluster_response_incorrect.c08a=叢集響應資訊不正確,請確認叢集地址是正確的服務端地址 +i18n.monitor_docker_timeout.b03b=監控 docker[{}] 超時 {} +i18n.project_soft_linked_by.8556=專案被{}軟鏈中 +i18n.log_recorder_error_message.ee3e=日誌記錄器被關閉/或者未啟用 +i18n.distribution_project_required.2560=請選擇分發專案 +i18n.test_result.8441=測試結果:{} {} +i18n.environment_variable.3867=環境變數 +i18n.unsupported_mode_with_colon.c6de=不支援的模式: +i18n.message_conversion_exception.cce8=訊息轉換異常 +i18n.oauth2_login_failure.3841=OAuth 2 登入失敗,平臺賬號不符合本系統要求 +i18n.node_did_not_pull_anything.8af5=節點沒有拉取到任何 +i18n.project_has_monitoring_items_cannot_delete.c9a3=當前專案存在監控項,不能直接刪除 +i18n.compression_type_not_supported.9dea=不支援的壓縮型別, +i18n.cache_plugin_path_required.2093=cache 外掛 path 不能為空 +i18n.data_file_content_error.e86f=資料檔案內容錯誤,請檢查檔案是否被非法修改: +i18n.parameter_error_path_error.f482=引數錯誤path錯誤 +i18n.script_template_not_exist.e05f=指令碼模版不存在\: +i18n.execute_event_script_error.7c69=執行事件指令碼錯誤 +i18n.associated_group2_required.bd05=請選擇關聯的分組 +i18n.gradle_plugin_depends_on_java.2bb3=gradle 外掛依賴 java , 使用 gradle 外掛必須優先引入 java 外掛 +i18n.no_server_management_permission.ee19=您沒有服務端管理許可權\:-2 +i18n.no_static_directory_configured.d3c0=當前沒有配置靜態目錄,自動取消定時任務 +i18n.distribute_log.c612=分發日誌 +i18n.trigger_result.364e=[{}]-{}觸發器結果:{} +i18n.no_parameters_added_with_minus_two.a7cf=沒有新增任何引數\:-2 +i18n.distribute_id_requirements.9c63=分發id 不能為空並且長度在2-20(英文字母 、數字和下劃線) +i18n.repository_authorization_error.4f50=倉庫授權資訊錯誤 +i18n.ssh_rename_failed_exception.94aa=ssh重新命名失敗異常 +i18n.associated_data_and_exist_in_workspace.5fa7=當前工作空間下還存在關聯:{} 和 {} 資料 +i18n.delete_log_file_failure.bf0b=刪除日誌檔案失敗 +i18n.content_format_error_with_detail.c846=內容格式錯誤,請檢查修正\: +i18n.project_log_storage_path_required.d0bb=請填寫的專案日誌儲存路徑,或者還沒有配置授權 +i18n.forbidden_operation_range.247f=【禁止操作】{} {} 至 {} +i18n.please_do_not_delete_this_file.0a7f=請勿刪除此檔案,刪除後關聯 id 將失效 +i18n.strict_execution_mode_event_script_error.c82a=嚴格執行模式,事件指令碼返回狀態碼異常, +i18n.file_missing_cannot_publish.3818=當前檔案丟失不能執行釋出任務 +i18n.log_file_does_not_exist_or_error.a0e7=日誌檔案不存在或者錯誤 +i18n.build_task_waiting.e303=構建任務繼續等待\:{} {} +i18n.no_manager_node_found.5934=當前叢集未找到任何管理節點 +i18n.file_transfer_exception.bda6=轉發檔案異常 +i18n.select_node.f8a6=請選擇節點 +i18n.upload_progress_with_units.44ad=上傳檔案進度\:{} {}/{} {} +i18n.ssh_terminal_execution_log.58f1=ssh 終端執行日誌 +i18n.project_monitor.d2ff=專案監控 +i18n.push_image_interrupted.6377=push image 被中斷\: +i18n.default_value.1aa9={} [預設] +i18n.read_additional_variables.5eb0=讀取附加變數:{} {} +i18n.node_transfer_info_encoding_exception.12c8=節點傳輸資訊編碼異常\: +i18n.user_operation_log.2233=使用者操作日誌 +i18n.handle_node_deletion_script_failure.071b=處理 {} 節點刪除指令碼失敗 {} +i18n.ssh_terminal_log.775f=SSH終端日誌 +i18n.query_data_error.45e7=查詢資料錯誤 +i18n.build_image_call_container_exception.7e13=構建映象呼叫容器異常 +i18n.machines_docker_data_fixed.af8a=成功修復 {} 條機器 DOCKER 資料 +i18n.project_name.31ec=專案 +i18n.load_success.154e=載入成功 +i18n.start_checking_backup_project_files.baa7=開始檢查備份專案檔案:{} {} +i18n.no_script_template_specified.7d14=沒有對應指令碼模板\: +i18n.push_image_container_exception.2090=推送映象呼叫容器異常 +i18n.ssh_with_build_items_message.0f6d=當前ssh存在構建項,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.ssh_item_distribution_required.2884=請選擇分發SSH項 +i18n.file_name_already_exists.0d4e=檔名已經存在拉 +i18n.waiting_to_start.b267=等待開始\: +i18n.no_corresponding_data.4703=沒有對應的資料 +i18n.info_to_retrieve_not_found.96d7=沒有要獲取的資訊 +i18n.http_proxy_address_unavailable.b3f2=HTTP代理地址不可用\: +i18n.maven_plugin_depends_on_java.23f8=maven 外掛依賴 java , 使用 maven 外掛必須優先引入 java 外掛 +i18n.join_beta_program.5c1f=是否加入 beta 計劃 +i18n.cluster_does_not_exist.97a4=當前叢集不存在 +i18n.publish_command_contains_forbidden_command.097d=釋出命令中包含禁止執行的命令 +i18n.close_exception.5b86=關閉異常 +i18n.no_project_info_found.725a=沒有找到對應的專案資訊 +i18n.upgrade_database_process.e604=升級資料庫流程: +i18n.no_type.9153=沒有對應的型別 +i18n.user_operation_alarm.15b9=使用者操作報警 +i18n.start_executing_build_task.a5ac=開始執行構建任務,任務等待時間:{} +i18n.event_type_not_supported.e9c3=不支援的事件型別:{} +i18n.user_directory_not_found_private_key_info.6ce4=使用者目錄沒有找到私鑰資訊 +i18n.upload_progress_message_format.b91c=上傳檔案進度:{}[{}/{}] {}/{} {} +i18n.download_failed_generic.be4f=下載檔案失敗 +i18n.oshi_network_card_monitoring_exception.6d41=oshi 網絡卡資源監控異常 +i18n.connection_successful.b331=連線成功 +i18n.incomplete_upload_info_now_slice.34aa=上傳資訊不完成:nowSlice +i18n.account_locked_cannot_change_password.d6ab=當前賬號被鎖定中,不能修改密碼 +i18n.no_publish_distribution_related_data_id.a077=沒有釋出分發對應關聯資料ID +i18n.build_failed.a79a=構建失敗\: +i18n.delete_ssh_temp_file_failure.6e5f=刪除 ssh 臨時檔案失敗 +i18n.branch_required.5095=請選擇分支 +i18n.trigger_token.abe6=觸發器 token +i18n.user_binding_warning.16b0=當前許可權組還繫結使用者,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.account_already_bound_to_mfa.5122=當前賬號已經繫結 mfa 啦 +i18n.compare_files_result.bec4=對比檔案結果,產物檔案 {} 個、需要上傳 {} 個 +i18n.content_format_error.ce15=內容格式錯誤,請檢查修正 +i18n.login_name_cannot_be_empty.9a99=登入名不能為空 +i18n.correct_host_required.8c49=請填寫正確的 host +i18n.select_correct_project_path_or_no_auth_configured.366a=請選擇正確的專案路徑,或者還沒有配置授權 +i18n.need_initialize_system.fb62=需要初始化系統 +i18n.not_logged_in.6605=沒有登入 +i18n.old_and_new_passwords_match.55b4=新舊密碼一致 +i18n.super_admin_cannot_reset_password_this_way.0761=超級管理員不能通過此方式重置密碼 +i18n.protocol_required.b4f8=請選擇協議 +i18n.incorrect_ip_address.b872=ip 地址資訊不正確 +i18n.main_class_not_found.8a12=沒有找到執行的主類 +i18n.data_creation_time_format_incorrect.7772=資料建立時間格式不正確 {} {} +i18n.delete_backup_data_file_failure.2ebf=刪除備份資料檔案失敗 +i18n.user_custom_workspace.ef93=使用者自定義工作空間 +i18n.editable_suffixes_not_configured.5b41=沒有配置可允許編輯的字尾 +i18n.invalid_repository_info.b4ad=無效的倉庫資訊 +i18n.login_JPOM.0de6=登入JPOM +i18n.ssh_does_not_exist_with_message.de6c=對應的 ssh 不存在 +i18n.not_running.4f8a=未執行 +i18n.no_get_id_method.2a65=沒有 getId 方法 +i18n.project_id_keyword_occupied.1cae=專案id {} 關鍵詞被系統佔用 +i18n.delete_log_file_failure_with_colon.d867=刪除日誌檔案失敗\: +i18n.manual_cancel.8464=手動取消 +i18n.super_admin_mfa_verification_disabled.b97d=成功關閉超級管理員賬號 mfa 驗證:{} +i18n.prepare_to_build.1830=準備構建 +i18n.unknown_exception_on_pull_code.2b2e=拉取程式碼發生未知異常建議清除構建重新操作: +i18n.close_connection_exception.c855=關閉連線異常 +i18n.delete_action.2f4a=刪除 +i18n.configure_monitoring_interval.9741=請配置監控週期 +i18n.restart_self_exception.85b7=重啟自身異常 +i18n.build_finished_duration.7f7c=構建結束-累計耗時\:{} +i18n.handle_node_sync_script_failure.e99f=處理 {} 節點同步指令碼失敗 {} +i18n.project_disabled.f8b3=當前專案被禁用 +i18n.certificate_already_exists.adf9=當前證書已經存在啦(系統全域性範圍內) +i18n.oshi_file_system_monitoring_exception.dc24=oshi 檔案系統資源監控異常 +i18n.image_cannot_be_empty.1600=映象不能為空 +i18n.no_corresponding_node_info.cd24=沒有對應到節點資訊 +i18n.node_plugin_version_required.2318=node 外掛 version 不能為空 +i18n.docker_exec_terminal_process_ended.c734=[{}] docker exec 終端程序結束 +i18n.node_migration_project_failure.d5ff=節點遷移專案失敗 +i18n.cannot_cancel_super_admin_permissions.99b5=不能取消超級管理員的許可權 +i18n.distribute_node_configuration_failure.8146=分發 {} 節點配置失敗 {} +i18n.file_upload_failed.462e=上傳檔案失敗: +i18n.node_service_resumed_normal_operation.2cbd=【{}】節點的【{}】專案{}已經恢復正常執行 +i18n.file_type_no_restart.0977=file 型別專案沒有 restart +i18n.current_distribution_data_lost.f9f8=當前分發資料丟失 +i18n.deletion_success_message.4359=刪除成功! +i18n.pull_repository_code.3f51=拉取倉庫程式碼 +i18n.database_mode_config_missing.ae5d=資料庫Mode配置缺失 +i18n.rsa_private_key_file_error.b687=第 {} 行 rsa 私鑰檔案不存在或者有誤 +i18n.protocol_not_supported.b906=不支援的 protocol +i18n.force_unbind_succeeded.5bfd=強制解綁成功 +i18n.unexpected_exception.2b52=發生異常 +i18n.check_email_error.636c=檢查郵箱資訊錯誤:{} +i18n.initialization_success.4725=初始化成功 +i18n.running_status.d679=執行中 +i18n.error_message.483d=未找到指令碼庫資訊\:{},請檢查引用標記是否正確或者指令碼是否被刪除 +i18n.check_git_client_exception.42a3=檢查 git 客戶端異常 +i18n.update_success.55aa=更新成功 +i18n.initialize_database_failure.2ef9=初始化資料庫失敗 {} +i18n.docker_cluster_associated_workspaces_message.5520=當前 docker 還關聯{}個 工作空間 docker 叢集,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.at_least_one_node_required.a290=請至少選擇一個節點 +i18n.reset_failed.5281=重置失敗: +i18n.no_current_data_permission.17d7=沒有當前資料許可權,需要管理員或者資料建立人才操作該資料 +i18n.database_not_initialized.e5e7=還沒有初始化資料庫 +i18n.project_path_occupied.cddd=當前專案路徑已經被【{}】佔用,請檢查 +i18n.configuration_modification_not_supported.5872=當前環境下不支援線上修改配置檔案 +i18n.non_dsl_project_unsupported_operation.5c09=非 DSL 專案不支援此操作 +i18n.contact_does_not_exist.3369=聯絡人不存在 +i18n.check_cluster_info_exception.7b0c=檢查叢集資訊異常 +i18n.docker_cluster_info.a2eb=docker 叢集資訊 +i18n.line_number_error.c65d=行號錯誤 +i18n.node_machine_table_exists_no_need_to_fix.2625=節點機器表已經存在 {} 條資料,不需要修復機器資料 +i18n.execution_exception_message.ef79=執行異常\: +i18n.auto_detect_local_docker_and_add.af72=自動探測到本地 docker 並且自動新增: +i18n.database_event_execution_ended.690b=資料庫 {} 事件執行結束,:{} +i18n.build_history.a05c=構建歷史 +i18n.machine_installation_id.d0b9=本機安裝 ID 為:{} +i18n.no_available_docker_server.6aaa={} 沒有可用的 docker server +i18n.credential_cannot_be_empty.d055=憑證不能為空 +i18n.workspace_label.98d6=工作空間 +i18n.file_modification_event.5bc2=檔案修改事件:{} {} +i18n.no_corresponding_build_record_ignore_deletion.86a0=沒有對應構建記錄,忽略刪除 +i18n.cannot_configure_root_path.d86e=不能配置根路徑: +i18n.completed_and_successful_count_insufficient.92fa=完成併成功的個數不足 {}/{} +i18n.get_project_info_failure.ddff=獲取專案資訊失敗\: +i18n.name_required.856d=請填寫名稱 +i18n.build_source.2ef9=構建來源, +i18n.joined_beta_program.c4e2=成功加入 beta 計劃 +i18n.recover_abnormal_data.9adf={} 恢復 {} 條異常資料 +i18n.file_name_error_message.7a25=檔名不能包含/ +i18n.config_file_not_exist.09dd=配置檔案不存在 +i18n.parent_task_not_found.bac1=沒有找到父級任務 +i18n.static_file_scanning_disabled.2b2b=未開啟靜態檔案掃描 +i18n.no_resource_found.dc22=沒有找到對應的資源 +i18n.container_name_cannot_be_empty.14b1=容器名稱不能為空 +i18n.token_parse_failed.cadf=token 解析失敗: +i18n.distribute_exception.da82=分發異常 +i18n.script_content_cannot_be_empty.49be=指令碼內容不能為空 +i18n.need_execute_pre_events.b848=需要執行 {} 個前置事件 +i18n.cluster_binding_success.eb7e=叢集繫結成功 +i18n.port_field_required_or_incorrect.8426=第 {} 行 port 欄位不能位空或者不正確 +i18n.delete_failure.acf0=刪除失敗 +i18n.prepare_to_migrate_data.f251=準備遷移資料 +i18n.ignore_log_record.48f5=忽略記錄日誌 {} +i18n.need_handle_build_micro_queue_count.3010=需要處理構建微佇列數:{} +i18n.user_management.7d94=使用者管理 +i18n.file_directory_too_long.c101=檔案目錄長度超過 500 ,自動忽略此類檔案:{} +i18n.temporary_result_file_does_not_exist.1c7e=臨時結果檔案不存在\: {} +i18n.command_content_required.6005=請輸入命令內容 +i18n.system_parameters.c7b0=系統引數 +i18n.node_address_not_found.f955=沒有節點地址,不能繼續操作 +i18n.verification_code_incorrect_retry.d88d=驗證碼不正確,請重新輸入 +i18n.git_installation_location.7984=git安裝位置:{} +i18n.scheduled_task_exception.f077=定時任務異常 {} +i18n.monitor_docker_exception.e326=監控 docker[{}] 異常 +i18n.restart_result.253f=重啟結果: +i18n.start_building_with_thread_execution.83cd=開始構建,構建執行緒執行 +i18n.task_ended_successfully.e176=任務正常結束 +i18n.non_existent_build_product.1df4={} 不存在,處理構建產物失敗 +i18n.current_not_supported.78b7=當前不支援: +i18n.no_user.3b69=沒有對應的使用者 +i18n.wait_for_seconds.ff7b=執行等待 {} 秒 +i18n.unknown_script_template_or_workspace.27f1=指令碼模板或者工作空間未知 +i18n.send_message_failure_prefix.6f8c=傳送訊息失敗\: +i18n.select_correct_ssh.aa93=請選擇正確的ssh +i18n.synchronization_failed.d610=同步失敗 +i18n.client_secret_not_configured.6923=沒有配置 clientSecret +i18n.workspace_not_exist.a6fd=不存在對應的工作空間 +i18n.no_user_specified.6650=沒有對應的使用者: +i18n.backup_database.9524=備份資料庫 +i18n.delete_project_file_failure.f007=刪除專案檔案失敗\: +i18n.incorrect_certificate_info.aee1=填寫的證書資訊錯誤 +i18n.build_command_not_empty.2e37=構建命令不能為空 +i18n.auto_migrate_exist_backup_logs.dc33=自動遷移存在備份日誌 {} -> {} +i18n.close_session_exception.3491=關閉會話異常 +i18n.login_failure_O_auth2_message.3e91=登入失敗(OAuth2),請聯絡管理員! +i18n.log_retention_days.99d1=統計日誌保留天數 {} +i18n.env_must_be_map_type.f8ad=env 必須是 map 型別 +i18n.batch_trigger_script_exception.8fb4=服務端指令碼批量觸發異常 +i18n.upgrade_failure.4ae2=升級失敗 +i18n.no_branch_or_tag_message.8ae3=沒有 {} 分支/標籤 +i18n.general_error_message.728a=啊哦,好像哪裡出錯了,請稍候再試試吧~ +i18n.service_info_incomplete_with_code2.e9ca=服務資訊不完整不能操作:-2 +i18n.ignored_operation.edee=忽略的操作:{} +i18n.monitor_name_cannot_be_empty.514a=監控名稱不能為空 +i18n.get_project_pid_failure.17b0=獲取專案pid 失敗 +i18n.please_fill_in_steps.229d=請填寫 steps +i18n.distribute_success.c689=分發成功 +i18n.unable_to_get_container_execution_result_file.7b2c=無法獲取容器執行結果檔案 +i18n.import_data.8ef8=匯入資料 +i18n.unsupported_mode_with_script_log.6a7a=不支援的模式,script log +i18n.ssh_connection_failed.74ab=ssh連線失敗,請檢查使用者名稱、密碼、host、埠等填寫是否正確,超時時間是否合理: +i18n.node_name_required.5bdf=請填寫節點名稱 +i18n.client_terminated_connection.6886=客戶端終止連線:{} +i18n.close_thread_pool.4cd9=關閉 {} 執行緒池 +i18n.no_corresponding_folder.621f=沒有對應資料夾 +i18n.no_online_manager_node_found.05d7=當前叢集未找到線上的管理節點 +i18n.command_execution_failed.90ef=執行命令失敗 +i18n.monitor_ssh_timeout.59fd=監控 ssh[{}] 超時 {} +i18n.execution_completed.24a1=執行完畢\: +i18n.close_project_failure.a1d2=關閉專案失敗: +i18n.select_folder_to_compress.915f=請選擇資料夾進行壓縮 +i18n.config_file_not_found.fc87=均未找到配置檔案 +i18n.update_ssh_machine_id_failed.bd24=更新 SSH 表機器id 失敗: +i18n.select_pull_code_protocol.fc24=請選擇拉取程式碼的協議 +i18n.auth_directory_cannot_be_empty.21ba=授權目錄不能為空 +i18n.status_not_in_progress.f410=當前狀態不在進行中, +i18n.image_tag_required.92cf=請填寫映象標籤 +i18n.privacy_variable_cannot_trigger.dbc9=隱私變數不能生成觸發器 +i18n.no_error_data_in_table.3092=當前表沒有錯誤資料 +i18n.update_restore_data.1b0b=更新還原資料:{} +i18n.script_template.1f77=指令碼模版 +i18n.certificate_info_incorrect.a950=證書資訊不正確,證書壓縮包裡面必須包含:ca.pem、key.pem、cert.pem +i18n.trim_completed_with_recovered_space.0463=修剪完成,總回收空間: +i18n.default_workspace_cannot_delete.18b4=系統預設的工作空間,不能刪除 +i18n.synchronization_node_failure_with_details.8660=同步節點{}失敗\:{} +i18n.email_verification_failed.5863=驗證郵箱資訊失敗,請檢查配置的郵箱資訊。埠號、授權碼等。 +i18n.permission_group.ea59=許可權分組 +i18n.node_exception_null_pointer.d408=節點異常,空指標 +i18n.node_system_logs.3ac9=節點系統日誌 +i18n.parameter_value_required.3a29=請填寫引數值 +i18n.node_connection_lost.b6c7=節點連線丟失或者還沒有連線上 +i18n.associated_data_lost_error.becb=ERROR\:關聯資料丟失 +i18n.ssh_modify_permission_error.0cd3=ssh修改檔案許可權異常...\: {} {} +i18n.please_fill_in_password.455f=請填寫pass +i18n.fuzzy_match_files.139d={} 模糊匹配到 {} 個檔案 +i18n.no_distribution_id_found.8df2=沒有找到對應的分發id +i18n.command_script_not_found_in_service.25ac=當前服務中沒有命令指令碼:{}.{} +i18n.search_project.7e9b=搜尋專案 +i18n.machine_asset_management.36ea=機器資產管理 +i18n.no_matching_data_found.fe9d=未找到匹配的資料 +i18n.project_console.3a94=專案控制檯 +i18n.script_template_not_exist.1d5b=對應的指令碼模版已經不存在拉 +i18n.subnet_mask_incorrect.6c27=子掩碼不正確: +i18n.no_implemented_publish_distribution.fcf8=沒有實現的釋出分發:{} +i18n.h2_connection_successful.11f3=成功連線 H2 ,開始嘗試自動備份 +i18n.container_build_exception.a98f=容器構建異常:{} -> {} +i18n.import_project_template_csv.c6f1=專案匯入模板.csv +i18n.handle_message_exception_with_colon.56f0=處理訊息異常: +i18n.node_already_exists_in_workspace.9499=對應工作空間已經存在該節點啦\: +i18n.plugin_not_initialized.3ea9=對應的外掛端還沒有被初始化 +i18n.no_node_info.6366=沒有任何節點資訊 +i18n.associated_ssh_node_contains_nonexistent_node.c7f5=關聯 SSH 節點包含不存在的節點 +i18n.node_statistics.b4e1=節點統計 +i18n.user_workspace_relation_table.851e=使用者(許可權組)工作空間關係表 +i18n.dsl_info_not_configured.3487=未配置 dsl 資訊(專案資訊錯誤) +i18n.migration_success_message.e546=專案遷移成功:{} | {} +i18n.node_connection_failed.8497={} 節點連線失敗 {} +i18n.ssh_management.9e0f=SSH管理 +i18n.synchronization_script_exception.9c70=同步指令碼異常 +i18n.previous_node_distribution_failure.d556=前一個節點分發失敗,取消分發 +i18n.cleanup_token_exception.760e=執行清理 token[{}] 異常 +i18n.incorrect_node_info_node_does_not_exist.2fd8=節點資訊不正確,對應對節點不存在 +i18n.plugin_not_found.a6e5=對應找到對應到外掛: +i18n.file_storage_center.6acf=檔案儲存中心 +i18n.system_administrator.181f=系統管理員 +i18n.parameter.3d0a=引數 +i18n.ignore_execution_event_script.8872=忽略執行事件指令碼 {} {} {} +i18n.login_info_expired_please_re_login.fbbc=登入資訊已經過期請重新登入 +i18n.clear_build_product_failed.edd4=清除構建產物失敗 +i18n.prepare_to_delete_current_database_file.1e6a=準備刪除當前資料庫檔案 +i18n.distribution_in_progress.c3ae=當前還在分發中,請等待分發結束 +i18n.initialization_failure.19e9=初始化失敗\: +i18n.project_exists.f4e0=該節點下還存在專案,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.modify_service_success.bd75=修改服務成功 +i18n.database_connection_not_configured.c80e=沒有配置資料庫連線 +i18n.clear_temp_file_failed_manually.0dad=清除臨時檔案失敗,請手動清理: +i18n.reset_log_failure.b3d3=重置日誌失敗 +i18n.type_field_required.7637=第 {} 行 type 欄位不能位空 +i18n.migration_target_workspace_node_mismatch.d9cf=要遷移到的目標工作空間和節點不一致 +i18n.start_deleting_files.210c=開始刪除 {} 檔案 {} +i18n.download_success.5094=下載成功 +i18n.running_project_cannot_change_path.5888=正在執行的專案不能修改路徑 +i18n.please_fill_in_correct_gradle_version.6e19=請填入正確的 gradle 版本號 +i18n.get_node_monitoring_info_failure.595a=獲取節點監控資訊失敗 +i18n.docker_authorization_failed.8ede=docker 授權失敗\:{} +i18n.supported_java_plugin_versions.bd70=目前java 外掛支援的版本\: %s +i18n.delete_success_with_cleanup.6155=刪除成功,並且清理歷史構建產物成功 +i18n.cluster_info.32e0=叢集資訊 +i18n.file_type_not_supported.ae5d=不支援的檔案型別\: +i18n.verification_code_incorrect.d8c0=驗證碼不正確 +i18n.main_class_not_found.b4b7=中沒有找到對應的MainClass\: +i18n.node_not_exist.760e=對應的節點不存在 +i18n.start_distribution_exclamation.9fc2=開始分發\! +i18n.failure_prefix.115a=失敗\: +i18n.connection_closed.6d4e=連線關閉 {} {} +i18n.no_corresponding_workspace_permission.8402=沒有對應的工作空間許可權 +i18n.post_distribution_action_required.8cc8=請選擇分發後的操作 +i18n.unsupported_item.bcf4=不支援的: +i18n.no_available_new_version_upgrade.d8f2=沒有可用的新版本升級\:-1 +i18n.docker_associated_workspaces_message.de78=當前 docker 還關聯{}個 工作空間 docker 不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.static_directory_not_configured.9bd6=還未配置靜態目錄 +i18n.server_jps_command_exception.e380=當前伺服器 jps 命令異常,請檢查 jdk 是否完整,以及 java 環境變數是否配置正確 +i18n.data_download_failed.9499=資料下載失敗 +i18n.delete_data.40f8=刪除資料 +i18n.image_name_required.ab44=請填寫鏡名稱 +i18n.listen_log_changes.9081=監聽日誌變化 +i18n.configure_correct_auth_url.22e7=請配置正確的授權 url +i18n.current_upload_chunk_info_incorrect.900e=當前上傳的分片資訊錯誤 +i18n.listen_log_success_currently_sessions_viewing.a74a=監聽{}日誌成功,目前共有{}個會話正在檢視 +i18n.node_script_template.be6a=節點指令碼模板 +i18n.post_packaging_action_required.bf66=請選擇打包後的操作 +i18n.multiple_clusters_exist.196b=系統中存在多個叢集,不需要自動繫結資料 +i18n.variable_name_already_exists.70f2=對應的變數名稱已經存在啦 +i18n.node_management.b26d=節點管理 +i18n.account_mfa_not_enabled.fd39=當前賬號沒有開啟兩步驗證 +i18n.exit_successful.8150=退出成功 +i18n.main_class_attribute_not_found.24c9=清單檔案中沒有找到對應的MainClass屬性 +i18n.code_pull_conflict.6e8e=拉取程式碼發生衝突,可以嘗試清除構建或者解決倉庫裡面的衝突後重新操作。: +i18n.cannot_delete_super_admin.68e2=不能刪除超級管理員 +i18n.node_running_status_abnormal.3160=【{}】節點的執行狀態異常 +i18n.pull_code_failed.70d6=拉取程式碼失敗:{} +i18n.execution_succeeded.f56c=執行成功 +i18n.docker_already_exists.d9a5=對應的 docker 已經存在啦 +i18n.build_task_queue_waiting.5f06=構建任務開始進入佇列等待.... +i18n.system_process_monitoring_error.fe1d=系統程序監控異常: +i18n.pull_log_exception.cc3e=拉取日誌異常 +i18n.create_file_watch_failure.bc1a=建立檔案監聽失敗 +i18n.no_project_found.ef5e=沒有找到對應的專案 +i18n.delete_project_file_failure_with_full_stop.85b8=刪除專案檔案失敗: +i18n.create_plugin_endpoint_connection_failure.30f8=建立外掛端連線失敗 {} +i18n.manual_cache_refresh_exception.9d91=手動重新整理快取異常 +i18n.no_script_template_found.0498=沒有找到對應的指令碼模板 +i18n.user_field_required.8732=第 {} 行 user 欄位不能位空 +i18n.cannot_delete_running_project.e56b=不能刪除正在執行的專案 +i18n.incorrect_parameter.02ce=引數存在不正確 +i18n.update_node_machine_id_failed.51d9=更新節點表機器 id 失敗: +i18n.compare_files_result_with_delete.033d=對比檔案結果,產物檔案 {} 個、需要上傳 {} 個、需要刪除 {} 個 +i18n.build_trigger_queue_result.a1fe=構建觸發器佇列執行結果:{} +i18n.unsupported_type_with_colon.1050=不支援的型別\: +i18n.no_machine_found.c16c=沒有找到對應的機器 +i18n.admin_email_not_configured.ecb8=管理員還沒有配置系統郵箱,請聯絡管理配置發件資訊 +i18n.node_response_error.efc6=\ 節點響應異常,狀態碼錯誤: +i18n.target_database_info_not_specified.2ff6=未指定目標資料庫資訊 +i18n.file_type_no_stop.00ff=file 型別專案沒有 stop +i18n.monitor_ssh_exception.e9ce=監控 ssh[{}] 異常 +i18n.running_node_cannot_be_empty.ffc6=執行節點不能為空 +i18n.push_registration_to_server_failed.5949=向服務端推送註冊失敗 {} +i18n.delete_script_template_execution_error.8bc5=刪除指令碼模版執行資料錯誤\:{} +i18n.no_menus_contact_admin.cfec=沒有任何選單,請聯絡管理員 +i18n.check_passed.dce8=檢查通過 +i18n.upload_progress_with_colon.dd5b=上傳檔案進度:{} {}/{} {} +i18n.machine_node_info.6a75=機器節點資訊 +i18n.ssh_command_log.7fd1=SSH命令日誌 +i18n.upgrade_failure_with_colon.59f1=升級失敗\: +i18n.script_tag_modification_not_allowed.cb75=指令碼標記不能修改 +i18n.auth_exception.27be=授權異常 +i18n.unsupported_plugin_message.2889=當前還不支援 {} 外掛 +i18n.operation_succeeded_refresh_backup.54a9=操作成功,請稍後重新整理檢視備份狀態 +i18n.no_data.55a2=沒有任何資料 +i18n.node_not_enabled.a14d=節點未啟用 +i18n.no_build_id.a0b8=沒有buildId +i18n.user_not_select_permission_group.1091=使用者未選擇許可權組 +i18n.project_has_logs_cannot_delete.1d2a=當前專案存在日誌閱讀,不能直接刪除 +i18n.unsupported_encoding_with_placeholder.3bd9=不支援的編碼方式:{} +i18n.export_image_exception.cb1c=匯出映象異常 +i18n.backup_data_not_exist.f88c=備份資料不存在 +i18n.illegal_character_encoding_format.af7a=配置的字元編碼格式不合法: +i18n.select_file_to_delete.33d6=請選擇要刪除的檔案 +i18n.online_upgrade_cannot_downgrade.d419=線上升級不能降級操作 +i18n.incorrect_cluster_address.893f=填寫的叢集地址不正確 +i18n.check_docker_dependency_error.60f7=檢查 docker 依賴錯誤\:{} +i18n.program_already_running.96e1=當前程式正在執行中,不能重複啟動,PID\: +i18n.password_change_success.8013=修改密碼成功! +i18n.build_resource_cleanup_failed.c4cf=清理構建資源失敗 +i18n.incorrect_publish_method.e095=釋出方法不正確 +i18n.import_low_version_data_to_new_version.247b=2. 將匯出的低版本資料( sql 檔案) 匯入到新版本中【啟動程式引數裡面新增 --replace-import-h2-sql\=/xxxx.sql (路徑需要替換為第一步控制檯輸出的 sql 檔案儲存路徑)】 +i18n.incorrect_line_number.5877=行號不正確 +i18n.file_deletion_event_with_details.7537=檔案刪除事件:{} {} +i18n.configure_correct_token_url.7bba=請配置正確的令牌 url +i18n.maven_plugin_version_required.71f1=maven 外掛 version 不能為空 +i18n.trigger_token_error_or_expired_with_code.393b=觸發token錯誤,或者已經失效\:-1 +i18n.invalid_webhooks_address.d836=WebHooks 地址不合法 +i18n.project_path_auth_not_under_jpom.0e18=專案路徑授權不能位於Jpom目錄下 +i18n.reconnect_failure.7c01=重連失敗 +i18n.project_operations.03d9=專案運維 +i18n.image_not_exist.ee17=映象不存在 +i18n.no_data.1ac0=沒有資料 +i18n.load_file_failure.86cc=載入檔案失敗\: +i18n.no_corresponding_ssh_script_info.1c12=沒有對應的ssh指令碼資訊 +i18n.log_file_error.473b=日誌檔案錯誤 +i18n.compare_id_not_exist.43be=比較 id 不存在 +i18n.private_file_not_found.ee45=沒有隱私檔案 +i18n.detect_local_docker_exception_with_details.7cc9=探測本地 docker 異常: +i18n.prepare_restart.8251=開始準備專案重啟:{} {} +i18n.save_node_data_failed.f314=儲存節點資料失敗\: +i18n.range_format_not_supported.d69e=不支援的 range 格式 +i18n.select_at_least_one_node_project.637c=至少選擇1個節點專案 +i18n.close_beta_plan_success.5a94=關閉 beta 計劃成功 +i18n.start_rolling_back_execution.a019=開始回滾執行 +i18n.get_container_log_interrupted.041d=獲取容器日誌操作被中斷\: +i18n.node_has_no_workspace.69c0=節點沒有工作空間 +i18n.correct_enterprise_wechat_address_required.5f2d=請輸入正確企業微信地址 +i18n.unknown_jsch_log_level_with_details.1f9a=未知的 jsch 日誌級別:{} {} +i18n.file_not_found.d952=檔案不存在 +i18n.node_communication_failure_signal.5aae=節點通訊失敗,遠端地址和埠時發生錯誤的訊號。通常,由於中間的防火牆或中間路由器已關閉,無法訪問遠端主機。 +i18n.multiple_node_data_exists_merge_config.043f=節點地址 {} 存在多個資料,將自動合併使用 {} 節點的配置資訊 +i18n.unknown_data_loss.5a24=未知(資料丟失) +i18n.command_execution_failed_details.77ed=執行命令失敗,詳情如下: +i18n.unknown_parameter.96dd=未知引數 +i18n.publish_file_second_level_directory_required.2f65=請填寫釋出檔案的二級目錄 +i18n.node_id_required_and_format.5926=節點id不能為空並且2-50(英文字母 、數字和下劃線) +i18n.restore_backup_data_failed.58af=還原備份資料失敗 +i18n.system_task_execution_exception.d559=執行系統任務異常 +i18n.command_management.621f=命令管理 +i18n.no_project_specified2.a7f5=沒有對應專案: +i18n.certificate_info_error_issuer_or_subject_DN_not_found.805d=證書資訊出現錯誤,未找到 issuerDN 或者 subjectDN +i18n.exported_ssh_data.2896=匯出的 ssh 資料 +i18n.refreshing_cache.c969=正在重新整理快取中,請勿重複重新整理 +i18n.start_executing_database_event.fc57=開始執行資料庫事件:{} +i18n.static_file_storage.35f6=靜態檔案儲存 +i18n.project_log_is_existing_folder.a80a=專案log是一個已經存在的資料夾 +i18n.project_data_workspace_id_inconsistency.7ed6=專案資料工作空間ID[{}]查詢出節點ID不一致, 舊資料\: {}, 新資料\: {} +i18n.query_workspace_error.6a0d=查詢錯誤的工作空間失敗 +i18n.unknown_database_mode.f9e5=當前資料庫模式未知 +i18n.secondary_directory_match.0aec={} 二級目錄模糊匹配到 {} 個檔案, 當前檔案保留方式 {} +i18n.ssh_server_alive_interval_config_error.1f11=配置 ssh serverAliveInterval 錯誤 +i18n.reset_success.faa3=重置成功 +i18n.please_fill_in_repository_name.9f0d=請填寫倉庫名稱 +i18n.parameter_error_ssh_name_cannot_be_empty.ff4f=引數錯誤ssh名稱不能為空 +i18n.current_docker_cluster_still_associated_with_workspaces.b301=當前 docker 叢集還關聯 {} 個工作空間叢集,不能退出叢集 +i18n.current_distribution_has_only_one_project.cd59=當前分發只有一個專案啦,刪除整個分發即可 +i18n.no_corresponding_execution_log.9545=沒有對應的執行日誌 +i18n.import_success_with_details.a4a0=匯入成功(編碼格式:{}),新增 {} 條資料,修改 {} 條資料 +i18n.current_distribution_has_build_items_cannot_unbind.a8e9=當前分發存在構建項,不能解綁 +i18n.listen_file_failed_may_not_exist.fd56=監聽檔案失敗,可能檔案不存在 +i18n.log_file_cleanup_failed.3a3b=清理日誌檔案失敗 +i18n.parse_csv_exception.885e=解析 csv 異常 +i18n.no_build_record_found.76f4=還沒有對應的構建記錄 +i18n.login_info_required.973b=請輸入登入資訊 +i18n.h2_database_backup_success.a099=H2 資料庫備份成功:{} +i18n.uses_only_supports_string_type.ac54=uses 只支援 String 型別 +i18n.link_id_required.5dc7=Link 模式 LinkId必填 +i18n.oshi_hard_disk_monitoring_exception.c642=oshi 硬碟資源監控異常 +i18n.get_docker_cluster_info_failure.c80d=獲取 docker 叢集資訊失敗 +i18n.exit_code.3b54=本次執行退出碼\: {} +i18n.compare_project_failure.e6ab=對比專案檔案失敗: +i18n.parse_file_exception.374d=解析檔案異常, +i18n.certificate_info_table.fff8=證書資訊表 +i18n.online_upgrade.da8c=線上升級 +i18n.no_log_file.bacf=還沒有日誌檔案 +i18n.listen_task_lost_or_not_found.347f=監聽任務丟失或者未找到:{} +i18n.parse_project_csv_exception.ece1=解析專案 csv 異常 +i18n.ssh_file_deletion_exception.5ba5=ssh刪除檔案異常 +i18n.token_invalid_or_expired.cb96=token錯誤,或者已經失效\:-1 +i18n.list_and_query.c783=列表、查詢 +i18n.migration_docker_cert_error.a5ea=遷移 docker[{}] 證書發生異常 +i18n.file_downloading.7a8f=檔案下載中 +i18n.mark_must_contain_letters_numbers_underscores.667d=標記只能包含字母、數字、下劃線 +i18n.please_fill_in_runs_on.c2ff=請填寫runsOn。 +i18n.create_build_task_exception.06f1=建立構建任務異常 +i18n.execution_interrupted.1bb6=執行被中斷 +i18n.event_loss_or_execution_error.7b14=事件丟失或者執行錯誤:{} {} +i18n.public_key_and_private_key_mismatch.4aa2=公鑰和私鑰不匹配 +i18n.restore_success.4c7f=還原成功 +i18n.monitoring_notifications.de94=監控通知 +i18n.machines_node_data_fixed.7744=成功修復 {} 條機器節點資料 +i18n.upload_success.a769=上傳成功 +i18n.send_email_failure.1ab3=傳送郵件失敗: +i18n.email_configuration.b3f7=郵箱配置 +i18n.server_script_not_exist.de24=不存在對應的服務端指令碼,請重新選擇 +i18n.java_plugin_version_required.de39=java 外掛 version 不能為空 +i18n.soft_link_project_mode_error.ffa0=被軟鏈的專案不能是File或Link模式 +i18n.node_null_pointer_exception.76fe={}節點,程式空指標異常 +i18n.parse_certificate_exception.3b6c=解析證書異常 +i18n.get_build_status_exception.914e=獲取構建狀態異常 +i18n.node_has_distribution_projects_cannot_delete.3987=該節點存在分發專案,不能 +i18n.selected_user_status_abnormal.efcf=選擇的使用者狀態異常 +i18n.target_workspace_consistency.e04c=目標工作空間與當前工作空間一致並且目標節點與當前節點一致 +i18n.nickname_length_limit.6312=暱稱長度只能是2-10 +i18n.please_pass_parameter.3182=請傳入引數 +i18n.node_no_data_pulled.0dae={} 節點沒有拉取到任何 {},但是刪除了資料:{} +i18n.node_info_not_found.2c8c=沒有查詢到節點資訊: +i18n.please_fill_in_repository_address.0cf8=請填寫倉庫地址 +i18n.table_without_primary_key.7392=表沒有主鍵 +i18n.invalid_shard_id.46fd=不合法的分片id +i18n.execution_ended.b793=執行結束\:{} +i18n.user_trigger_unavailable.9866=當前使用者觸發器不可用 +i18n.task_ended.b341=任務結束\: +i18n.session_closed_reason.103a=會話[{}]關閉原因:{} +i18n.static_directory_auth_cannot_be_empty.2cb2=靜態目錄授權不能為空 +i18n.file_publish_task_record.edc4=檔案釋出任務記錄 +i18n.no_files_in_zip.1af6=壓縮包裡沒有任何檔案 +i18n.unable_to_connect_to_repository.52df=無法連線此倉庫, +i18n.parent_task_not_exist.ca1b=父任務不存在 +i18n.distribute_exception_with_detail.28fe=分發異常 {} +i18n.unknown_database_dialect_type.951b=未知的資料庫方言型別\: +i18n.fill_download_address.763c=填寫下載地址 +i18n.verification_code_is.5af5=驗證碼是:{} +i18n.pull_exception.b38d=拉取異常 +i18n.incomplete_upload_info_total_slice.7e85=上傳資訊不完成:totalSlice +i18n.database_corrupted.944e=資料庫異常,可能資料庫檔案已經損壞(可能丟失部分資料),需要重新初始化。可以嘗試在啟動引數裡面新增 --recover\:h2db 來自動恢復,: +i18n.handle_node_synchronization_script_library_failure.14e4=處理 {} 節點同步指令碼庫失敗 {} +i18n.cluster_name_required.5ca6=請填寫叢集名稱 +i18n.build_method_incorrect.5319=構建方式不正確 +i18n.data_does_not_exist.b201=資料不存在 +i18n.operation_time.7e95=操作時間 +i18n.communication_ip_cannot_be_empty.ae35=通訊 IP 不能為空 +i18n.build_info_not_exist.4470=不存在對應的構建資訊 +i18n.protocol_field_required.7cc2=第 {} 行 protocol 欄位不能位空 +i18n.incorrect_mode_for_migration.caef=當前模式不正確,不能直接遷移到 {} +i18n.socket_session_establishment_failed.4924=socket 會話建立失敗,授權資訊錯誤 +i18n.handle_message_exception.0bdc=處理訊息異常 +i18n.index_field_not_configured.96d9=索引未配置欄位 +i18n.file_name_not_found.b0ed=沒有檔名 +i18n.file_deletion_event.a51c=檔案刪除事件:{} +i18n.no_asset_management_permission.739e=您沒有資產管理許可權 +i18n.current_docker_already_in_other_cluster.e629=當前 docker 已經加入到其他叢集啦 +i18n.addition_succeeded.3fda=新增成功 +i18n.node_upgrade.3bf3=節點升級 +i18n.server_system_config.3181=服務端系統配置 +i18n.empty_file_cannot_upload.88df=空檔案不能上傳 +i18n.config_file_already_exists.c5fe=對應的配置檔案已經存在啦 +i18n.login_password_required.9605=請填寫登入密碼 +i18n.current_repository_associated_with_build.4b6e=當前倉庫被構建關聯,不能直接刪除 +i18n.unzip_exception.92cc=解壓異常 {} by InputStream {} +i18n.read_global_script_file_error.0d4c=讀取全域性指令碼檔案失敗 +i18n.build_product_sync_success.f7d1=構建產物檔案成功同步到檔案管理中心,{} +i18n.decrypt_parameter_failure.d10a=解密引數失敗 +i18n.no_record.ff41=沒有對應的記錄 +i18n.workspace_error_or_no_permission.7c8b=工作空間錯誤,或者沒有許可權編輯此資料 +i18n.upload_failed.b019=上傳失敗\: +i18n.no_permission.e343=您沒有對應許可權 +i18n.listener_key_not_found.6d3a=沒有找到監聽 key +i18n.download_failed_retry.c113=下載失敗。請重新整理頁面後重試 +i18n.please_fill_in_correct_go_version.44ed=請填入正確的 go 版本號 +i18n.forbidden_operation_time_range.92bf=【禁止操作】當前時段禁止執行 {} 至 {} +i18n.no_matching_files.b7a6={} 沒有匹配到任何檔案 +i18n.multi_download_not_supported.94b9=不支援分片多端下載 +i18n.parameter_error_port_error.810d=引數錯誤port錯誤 +i18n.incomplete_node_info_missing_machine_id.1c9a=節點資訊不完整,缺少機器id +i18n.node_network_connection_exception_or_timeout.5904=節點網路連線異常或超時,請優先檢查外掛端執行狀態再檢查 IP 地址、 +i18n.ssh_command_management.c40a=SSH命令管理 +i18n.variable_name_rules.480a=變數名稱 1-50 英文字母 、數字和下劃線 +i18n.parameter_error_user_cannot_be_empty.9239=引數錯誤user不能為空 +i18n.no_database_config_header_found.9ee3=沒有找到資料庫配置標識頭 +i18n.week_day_range_format.ebec=周{} 的 {} 至 {} +i18n.no_node_info_no_need_to_fix_machine_data.562e=沒有任何節點資訊,不需要修復機器資料 +i18n.send_message_exception.7817=發訊息異常 +i18n.static_file_task_load_failure.b995=靜態檔案任務載入失敗 +i18n.server_exception_occurred.9eb4=服務端發生異常 +i18n.ssh_unauthorized_directory.df78=此ssh未授權操作此目錄 +i18n.event_script_does_not_exist.e726=事件指令碼不存在\:{} {} +i18n.async_refresh_in_progress.5550=非同步重新整理中請稍後重新整理頁面檢視 +i18n.build_status_message.42a7=當前構建中任務數:{},佇列中任務數:{} 構建任務等待超時或者超出最大等待數量,當前執行中的任務數:{}/{},取消執行當前構建 +i18n.download_action.f26e=下載 +i18n.project_is_not_node_distribution_project_cannot_delete.2a5a=該專案不是節點分發專案,不能在此次刪除 +i18n.node_has_build_items_cannot_delete.a952=該節點存在構建項,不能 +i18n.encoding_error.b685=編碼異常 +i18n.node_distribution.ae68=節點分發 +i18n.two_step_verification_code_required.7e86=請輸入兩步驗證碼 +i18n.close_docker_exec_terminal.fec3=關閉[{}] docker exec 終端:{} +i18n.ssh_error_string.6bdb=ssh 錯誤:{} +i18n.project_id_does_not_exist.6b9b=專案id不存在 +i18n.cluster_not_bound_to_group_for_docker_monitoring.3926=當前叢集還未繫結分組,不能監控 Docker 資產資訊 +i18n.no_content_to_execute.66aa=沒有需要執行的內容 +i18n.online_build.6f7a=線上構建 +i18n.export_low_version_data.f1aa=1. 匯出低版本資料 【啟動程式引數裡面新增 --backup-h2】 +i18n.docker_asset_imported.0ab4=docker[{}] 資產匯入 +i18n.associated_group_required.5889=請選擇關聯分組 +i18n.backup_directory_conflict.c13e=備份目錄衝突: +i18n.delete_service_success.4d73=刪除服務成功 +i18n.select_monitoring_operation.3057=請選擇監控的操作 +i18n.config_file_database_config_not_parsed.47b2=未解析出配置檔案中的資料庫配置資訊 +i18n.data_already_exists.0397=匯入的資料已經存在啦 +i18n.please_check_in_time.3b4f=請及時檢查 +i18n.installation_success.811f=安裝成功 +i18n.cluster_management.74ea=叢集管理 +i18n.type_field_value_error.14cf=第 {} 行 type 欄位值錯誤(Git/Svn) +i18n.migrate_data.f556=遷移資料 +i18n.no_script.93c4=沒有對應的指令碼 +i18n.node_does_not_exist.4ce4=節點不存在 +i18n.not_super_admin.962e=您不是超級管理員沒有許可權\:-2 +i18n.ssh_does_not_exist.5bec=對應的 SSH 不存在 +i18n.file_too_large.9994=上傳檔案太大了,請重新選擇一個較小的檔案上傳吧 +i18n.backup_product.53c0=備份產物 {} {} +i18n.command_error.d0b4=執行命令錯誤 +i18n.select_alarm_contact.d02a=請選擇報警聯絡人 +i18n.file_signature_info_not_found.83bf=沒有檔案簽名資訊 +i18n.associated_nodes_warning.64d8=當前機器還關聯{}個節點,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.build_unknown_error.dad6=構建發生未知錯誤 +i18n.new_version_exists_download_unavailable.4ba7=存在新版本,下載地址不可用 +i18n.compare_backup_failure.303e=對比清空專案檔案備份失敗 +i18n.upload_success_and_restart.7bc3=上傳成功並重啟 +i18n.no_deletion_condition.19d0=沒有刪除條件 +i18n.get_docker_cluster_failure_with_placeholder.06cb=獲取 {} docker 叢集失敗 {} +i18n.synchronize_project_files_failed.6aa4=同步專案檔案失敗: +i18n.parse_system_start_time_error.112c=\ 解析系統啟動時間錯誤: +i18n.service_info_incomplete_with_code1.30f4=服務資訊不完整不能操作:-1 +i18n.at_least_one_project_required.2bbd=請至少選擇一個專案 +i18n.cluster_id_conflict.45b7={} 叢集ID衝突:{} {} +i18n.modified_value_is_empty.e4fa=修改的值為空 +i18n.cannot_delete_root_dir.fcdc=不能刪除根目錄 +i18n.manual_cancel_task.e592=手動取消任務 +i18n.unzip_exception.453e={} 解壓異常 {} {} +i18n.start_executing_event_script.377e=開始執行事件指令碼: {} +i18n.unlock_success.4cea=解鎖成功 +i18n.login_name_email_format_length_range.25f3=登入名如果為郵箱格式,長度必須 {}-{} +i18n.ssh_data_repair_not_needed.203f=機器 SSH 表已經存在 {} 條資料,不需要修復機器 SSH 資料 +i18n.new_package_same_as_running_package.e25a=新包和正在執行的包一致 +i18n.config_file_not_exist_with_message.6a40=配置檔案不存在 {} +i18n.no_workspace_selected.33d5=沒有選擇任何工作空間 +i18n.associated_data_exists_in_workspace.8827=當前工作空間下還存在關聯資料: +i18n.script_not_bound_to_ssh_node.3459=當前指令碼未繫結 SSH 節點,不能使用觸發器執行 +i18n.start_executing_post_release_command.fd06=開始執行 {} 釋出後命令 +i18n.alert_content_and_status.6ed1=報警內容:{} 狀態訊息:{} +i18n.incorrect_project_id.5f70=專案id 不正確 +i18n.certificate_management.4001=證書管理 +i18n.download_exception.e616=下載檔案異常 +i18n.process_file_deletion_exception.1c6e=處理檔案刪除異常 +i18n.trigger_auto_execute_command_template_exception.4e01=觸發自動執行命令模版異常 +i18n.no_build.d163=沒有對應的構建 +i18n.start_executing_publishing_with_file_size.5039=開始執行釋出,需要釋出的檔案大小:{} +i18n.command_execution_exception.4ccd=執行命令異常 +i18n.operation_ip.cbd4=操作IP +i18n.auth_directory_cannot_contain_hierarchy.d6ca=授權目錄中不能存在包含關係: +i18n.ip_authorization_interception_exception.8130=IP授權攔截異常,請檢查配置是否正確 +i18n.refresh_token_timeout.3291=重新整理token超時 +i18n.missing_script_library_message.be9a=對應的指令碼庫不存在: +i18n.operation_file_permission_exception.5a41=操作檔案許可權異常,請手動處理: +i18n.monitoring_user_actions.f2d5=監控使用者操作 +i18n.please_fill_in_user.5f52=請填寫user +i18n.command_template_execution_link_exception.51cf=命令模版執行連結異常 +i18n.cleanup_succeeded.02ea=清理成功 +i18n.distribute_node_authorization_failure.bb92=分發 {} 節點授權失敗 {} +i18n.monitoring_item_not_exist.32c8=不存在監控項啦 +i18n.workspace_id_required.c967=工作空間ID不能為空 +i18n.please_fill_in_ipv4_address.d23a=請填寫 ipv4 地址: +i18n.publish_product.5925=釋出產物 +i18n.no_workspace_info_contact_admin_for_authorization.825f=沒有任何工作空間資訊,請聯絡管理授權 +i18n.cannot_disable_super_admin.6429=不能禁用超級管理員 +i18n.oshi_system_process_monitoring_exception.a4da=oshi 系統程序監控異常 +i18n.start_async_download.78cc=開始非同步下載 +i18n.install_id_does_not_exist.6aee=資料錯誤,安裝 ID 不存在 +i18n.file_format_not_supported.eac4=不支援的檔案格式 +i18n.restore_backup_data_success.253a=還原備份資料成功 +i18n.build_info_missing.0ab0=構建資訊缺失 +i18n.file_not_exist.5091=對應的檔案不存在 +i18n.max_concurrent_shard_ids.f89c=分片id最大同時使用 100 個 +i18n.handle_node_deletion_script_library_failure.4205=處理 {} 節點刪除指令碼庫失敗 {} +i18n.data_id_not_found.1b0a=沒有資料id +i18n.yml_config_format_error_tab.f629=yml 配置內容格式有誤請檢查後重新操作(不要使用 \\\\t(TAB) 縮排): +i18n.build_in_progress.4d33=當前構建還在進行中 +i18n.execute_script_exit_code.64a8=執行 {} 型別指令碼的退出碼是:{} +i18n.user_does_not_exist.8363=\ 使用者不存在請聯絡管理建立 +i18n.no_node_found.6f85=沒有找到對應的節點 +i18n.current_docker_not_in_cluster.f70c=當前 docker {} 不在叢集中 +i18n.no_available_docker_server.9fc6=\ 沒有可用的 docker server +i18n.active_clearance.5870=主動清除 +i18n.login_info_expired_re_login.6bc4=登入資訊已失效,重新登入 +i18n.release_node_project_failed.764e=釋放節點專案失敗: +i18n.no_corresponding_task.3be5=沒有對應的任務 +i18n.account_does_not_exist.8402=賬號不存在 +i18n.scan_succeeded.7975=掃描成功 +i18n.websocket_error.2bb4=websocket出現錯誤:{} +i18n.no_management_permission2.35d4=您沒有對應管理許可權\:-3 +i18n.file_in_use_stop_project_first.a2c3=檔案被佔用,請先停止專案 +i18n.product_file_does_not_exist.ee13=產物檔案不存在 +i18n.default_setting.18c6=預設 +i18n.address_field_required.3bc8=第 {} 行 address 欄位不能位空 +i18n.get_repository_branch_failure.37cc=獲取倉庫分支失敗 +i18n.file_type_project_no_running_status.32a2=file 型別專案沒有執行狀態 +i18n.project_info.6674=專案資訊 +i18n.no_changes_in_repository_code_with_details.fd9f=倉庫程式碼沒有任何變動終止本次構建:{} {} +i18n.import_save_project_exception.cdbe=匯入儲存專案異常 +i18n.in_progress.b851=進行中\: +i18n.current_distribution_data_lost_record_id_not_exist.ca07=當前分發資料丟失,記錄id 不存在 +i18n.oauth2_binding_warning.d8f0=當前許可權組被 oauth2[{}] 繫結,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.node_status_code_abnormal.4d22=【{}】節點的狀態碼異常:{} +i18n.data_does_not_exist_with_details.d9b5=資料不存在\: +i18n.data_type_not_supported.fd03=不支援的資料型別\: +i18n.refresh_token_failure.de7f=重新整理token失敗 +i18n.no_docker_details.3343=沒有對應到docker資訊 +i18n.no_permission_for_function.b63d=您沒有對應功能【{}】管理許可權 +i18n.auth_config.3d48=授權配置 +i18n.no_info.e59e=沒有任何資訊 +i18n.repository_info.22cd=倉庫資訊 +i18n.data_id_cannot_be_empty.403b=資料 id 不能為空 +i18n.ssh_does_not_exist.88d7=ssh 不存在 +i18n.select_operation_type.63c6=請選擇操作型別 +i18n.clear_success.2685=清空成功 +i18n.illegal_access.c365=非法訪問 +i18n.certificate_has_no_aliases.3a2f=證書沒有任何:aliases +i18n.docker_asset_management.96d9=DOCKER資產管理 +i18n.docker_tag_incorrect.8b62=docker tag 填寫不正確,沒有找到任何docker +i18n.release_successful.f2ca=釋放成功 +i18n.result_dir_file_required.5f02=resultDirFile 不能為空 +i18n.status_not_distributing.6298=當前狀態不是分發中 +i18n.build_record_not_exist.8186=構建記錄不存在 +i18n.unable_to_connect_to_docker.2bb3=無法連線 docker 請檢查 host 或者 TLS 證書 以及倉庫資訊配置是否正確。 +i18n.system_configuration_directory.0f82=系統配置目錄 +i18n.node_authorized_config.f934=節點授權配置 +i18n.cannot_operate_current_directory.aa3d=不能操作當前目錄 +i18n.script_template_exists.3f86=該節點下還存在指令碼模版,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.do_not_reopen.f86a=不要重複開啟 +i18n.asset_ssh_not_exist.cd43=不存在對應的資產SSH +i18n.package_missing_info.e277=此包沒有版本號、打包時間、最小相容版本 +i18n.too_many_attempts.d88d=嘗試次數太多,請稍後再來 +i18n.incorrect_account_credentials.b2c5=賬號密碼不正確 +i18n.plugin_system_log.955c=外掛端系統日誌 +i18n.cluster_created_successfully.6bf3=叢集建立成功 +i18n.data_associated_id_inconsistent.59f7=資料關聯的id 不一致 +i18n.script_exit_code.716e=執行指令碼的退出碼是:{} +i18n.ssh_connections_warning.1ddb=當前機器SSH還關聯{}個ssh,不能直接刪除(需要提前解綁或者刪除關聯資料後才能刪除) +i18n.oauth2_not_configured.9c85=未配置 oauth2 +i18n.disallowed_file_extension.eb05=不允許編輯的檔案字尾 +i18n.no_environment_variables_found.46ad=沒有找到任何環境變數 +i18n.remote_download_url.011f={} 遠端下載 url:{} +i18n.correction_data_failure.dac6=修正資料失敗: +i18n.no_corresponding_ssh_item.2deb=沒有對應的ssh項 +i18n.private_key_file_not_exist.49ed=配置的私鑰檔案不存在 +i18n.check_docker_exception.a6d1=檢查 docker 異常 +i18n.only_git_repositories_have_branch_info.d7f7=只有 GIT 倉庫才有分支資訊 +i18n.select_monitoring_person.0756=請選擇監控人員 +i18n.associated_data_name_not_exist_error.583e=ERROR\:關聯資料名稱不存在 +i18n.file_not_exist.ea6a=不存在對應的檔案 +i18n.demo_account_not_support_delete.f9a6=演示賬號不支援刪除 +i18n.no_command_to_execute.340b=沒有需要執行的命令 +i18n.no_files_in_project_directory.108e=專案目錄沒有任何檔案,請先到專案檔案管理中上傳檔案 +i18n.build_record_lost.f6a2=構建記錄丟失,無法繼續構建 +i18n.no_node.2e83=沒有對應的節點 +i18n.reconnect_plugin_failure.cc6c=重連外掛端失敗 +i18n.installation_success_with_machine_id.1cd6=安裝成功,本機安裝 ID 為:{} +i18n.query_success.d72b=查詢成功 +i18n.create_success.04a6=建立成功 +i18n.send_message_failure.9621=傳送訊息失敗 +i18n.project_has_node_distribution_cannot_migrate.cc0e=當前專案存在節點分發,不能直接遷移 +i18n.please_fill_in_node_address.e77e=請填寫 節點地址 +i18n.login_name_length_range.fe8d=登入名長度範圍 3-50 +i18n.file_already_exists.983d=檔案已經存在啦 +i18n.repository_key_file_does_not_exist_or_is_abnormal.1d78=倉庫金鑰檔案不存在或者異常,請檢查後操作 +i18n.no_corresponding_docker_info.c47a=沒有對應的 docker 資訊 +i18n.type_not_exist_error.09de=ERROR\:型別不存在 +i18n.configure_announcement_title_or_content.7894=請配置公告標題或者內容 +i18n.no_any_branch.d042=沒有任何分支 +i18n.connection_successful.0515=成功連線 {} {} +i18n.docker_does_not_exist_with_code.689b=對應的 docker 不存在\:-1 +i18n.multiple_worker_nodes_exist.7110=還存在多個工作節點,不能退出最後一個管理節點 +i18n.operation_log.cda8=操作日誌 +i18n.close_resource_failure.dc66=關閉資源失敗 +i18n.free_script.7760=自由指令碼 +i18n.project_id_length_range.7064=專案id 長度範圍2-20(英文字母 、數字和下劃線) +i18n.system_cancel.3df2=系統取消 +i18n.configure_correct_user_info_url.1276=請配置正確的使用者資訊 url diff --git a/modules/common/src/main/resources/i18n/words.json b/modules/common/src/main/resources/i18n/words.json new file mode 100644 index 0000000000..9af395422f --- /dev/null +++ b/modules/common/src/main/resources/i18n/words.json @@ -0,0 +1,1616 @@ +{ + "i18n.account_already_bound_to_mfa.5122":"当前账号已经绑定 mfa 啦", + "i18n.account_disabled.9361":"账号已经被禁用,不能使用", + "i18n.account_does_not_exist.8402":"账号不存在", + "i18n.account_locked_cannot_change_password.d6ab":"当前账号被锁定中,不能修改密码", + "i18n.account_login_failed_too_many_times_locked.23b2":"该账户登录失败次数过多,已被锁定{},请不要再次尝试", + "i18n.account_mfa_not_enabled.fd39":"当前账号没有开启两步验证", + "i18n.account_name_nickname_required.b757":"请输入账户昵称", + "i18n.account_not_bound_to_any_workspace.fd61":"当前账号没有绑定任何工作空间,请联系管理员处理", + "i18n.active_clearance.5870":"主动清除 ", + "i18n.active_clearance_colon.96a6":"主动清除:", + "i18n.add_new_success.431a":"新增成功!", + "i18n.addition_succeeded.3fda":"添加成功", + "i18n.address_field_required.3bc8":"第 {} 行 address 字段不能位空", + "i18n.address_not_configured.f2eb":"未配置地址", + "i18n.admin_account_required.31e0":"系统中的系统管理员账号数量必须存在一个以上", + "i18n.admin_email_not_configured.ecb8":"管理员还没有配置系统邮箱,请联系管理配置发件信息", + "i18n.agent_jar_damaged.74a8":"Agent JAR 损坏请重新上传,", + "i18n.agent_jar_not_exist.28ac":"Agent JAR包不存在", + "i18n.agent_response_empty.cc8e":"agent 端响应内容为空", + "i18n.alarm_contact_or_webhook_required.6c24":"请选择一位报警联系人或者填写webhook", + "i18n.alert_contact_exception.2cec":"报警联系人异常", + "i18n.alert_contact_exception_message.1072":"报警联系人异常:", + "i18n.alert_content_and_status.6ed1":"报警内容:{} 状态消息:{}", + "i18n.alias_code_validation.8b99":"别名码只能是英文、数字", + "i18n.alias_or_token_error.d5c6":"别名或者token错误,或者已经失效", + "i18n.already_offline.d3b5":"已经离线啦", + "i18n.asset_cluster_and_node_mismatch.8964":"资产集群和节点不匹配", + "i18n.asset_machine_node_statistics.4a03":"资产机器节点统计", + "i18n.asset_monitoring_thread_pool_rejected_task.222e":"资产监控线程池拒绝了任务:{}", + "i18n.asset_ssh_not_exist.cd43":"不存在对应的资产SSH", + "i18n.associated_data_and_exist_in_workspace.5fa7":"当前工作空间下还存在关联:{} 和 {} 数据", + "i18n.associated_data_exists_in_workspace.8827":"当前工作空间下还存在关联数据:", + "i18n.associated_data_lost_error.becb":"ERROR:关联数据丢失", + "i18n.associated_data_name_not_exist_error.583e":"ERROR:关联数据名称不存在", + "i18n.associated_group2_required.bd05":"请选择关联的分组", + "i18n.associated_group_required.5889":"请选择关联分组", + "i18n.associated_nodes_warning.64d8":"当前机器还关联{}个节点,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.associated_ssh_node_contains_nonexistent_node.c7f5":"关联 SSH 节点包含不存在的节点", + "i18n.associated_workspace.885b":"所属工作空间", + "i18n.async_refresh_in_progress.5550":"异步刷新中请稍后刷新页面查看", + "i18n.async_resource_expired.2ddc":"异步资源过期,需要主动关闭,{} {}", + "i18n.at_least_one_node_required.a290":"请至少选择一个节点", + "i18n.at_least_one_project_required.2bbd":"请至少选择一个项目", + "i18n.auth_config.3d48":"授权配置", + "i18n.auth_directory_cannot_be_empty.21ba":"授权目录不能为空", + "i18n.auth_directory_cannot_be_under_jpom.bb67":"授权目录不能位于Jpom目录下", + "i18n.auth_directory_cannot_contain_hierarchy.d6ca":"授权目录中不能存在包含关系:", + "i18n.auth_exception.27be":"授权异常", + "i18n.auth_failed.2765":"授权失败:", + "i18n.auth_info_error.c184":"授权信息错误", + "i18n.authentication_config.964c":"认证配置", + "i18n.authorization_exception.acc0":"{} 授权异常 {}", + "i18n.authorized_cannot_be_reloaded.6ece":"authorized 不能重复加载", + "i18n.auto_backup_h2_database.2ed0":"自动备份 h2 数据库文件,备份文件位于:{}", + "i18n.auto_clear_data_errors.112f":"自动清除数据错误 {} {}", + "i18n.auto_clear_machine_node_stats_logs.5279":"自动清理 {} 条机器节点统计日志", + "i18n.auto_delete_data.ca62":" 自动删除 {} 表中数据 {} 条数据", + "i18n.auto_delete_expired_build_history_files.723b":"自动删除过期的构建历史相关文件:{} {}", + "i18n.auto_detect_local_docker_and_add.af72":"自动探测到本地 docker 并且自动添加:", + "i18n.auto_migrate_associated_build.a060":"自动迁移关联的构建:{}", + "i18n.auto_migrate_associated_build_and_repo.0b3f":"自动迁移关联的构建:{} 和 仓库:{}", + "i18n.auto_migrate_exist_backup_logs.dc33":"自动迁移存在备份日志 {} -> {}", + "i18n.auto_migrate_exist_logs.c169":"自动迁移存在日志 {} -> {}", + "i18n.auto_start_project_failed.c7b5":"自动启动项目失败:{} {}", + "i18n.auto_start_timed_task_message.9637":"{} 定时任务已经自动启动:{}", + "i18n.backup_data_not_exist.f88c":"备份数据不存在", + "i18n.backup_database.9524":"备份数据库", + "i18n.backup_directory_conflict.c13e":"备份目录冲突:", + "i18n.backup_file_not_exist.9628":"备份文件不存在", + "i18n.backup_old_package.a7fc":"备份旧程序包:{}", + "i18n.backup_old_package_failure_due_to_new_package_absence.b90c":"备份旧程序包失败:{},因为新程序包不存在:{}", + "i18n.backup_old_package_failure_due_to_old_package_absence.53aa":"备份旧程序包失败:{},因为旧程序包不存在", + "i18n.backup_product.53c0":"备份产物 {} {}", + "i18n.batch_trigger_project_exception.3c28":"项目批量触发异常", + "i18n.batch_trigger_script_exception.8fb4":"服务端脚本批量触发异常", + "i18n.binding_success.1974":"绑定成功", + "i18n.branch_required.5095":"请选择分支", + "i18n.build_call_container_exception.6e04":"构建调用容器异常", + "i18n.build_command_execution.a55c":"执行构建命令", + "i18n.build_command_no_delete.df52":"构建命令不能包含删除命令", + "i18n.build_command_not_empty.2e37":"构建命令不能为空", + "i18n.build_data_not_exist.0225":"构建数据不存在:{},任务自动丢弃:{}", + "i18n.build_failed.a79a":"构建失败:", + "i18n.build_finished.7f38":"构建结束", + "i18n.build_finished_duration.7f7c":"构建结束-累计耗时:{}", + "i18n.build_history.a05c":"构建历史", + "i18n.build_image_call_container_exception.7e13":"构建镜像调用容器异常", + "i18n.build_in_progress.4d33":"当前构建还在进行中", + "i18n.build_info.224a":"构建信息", + "i18n.build_info_missing.0ab0":"构建信息缺失", + "i18n.build_info_not_exist.4470":"不存在对应的构建信息", + "i18n.build_log.7c0e":"构建日志", + "i18n.build_log_recorder_closed.1cc7":"构建日志记录器已关闭,可能手动取消停止构建,流程:{}", + "i18n.build_method_incorrect.5319":"构建方式不正确", + "i18n.build_name_not_empty.4154":"构建名称不能为空", + "i18n.build_not_exist.c2ac":"不存在对应的构建", + "i18n.build_product_dir_not_empty.ba06":"构建产物目录不能为空,长度1-200", + "i18n.build_product_file_sync_failed.0e64":"构建产物文件同步到文件管理中心失败,当前文件已经存文件管理中心存在啦", + "i18n.build_product_sync_success.f7d1":"构建产物文件成功同步到文件管理中心,{}", + "i18n.build_record_lost.f6a2":"构建记录丢失,无法继续构建", + "i18n.build_record_not_exist.8186":"构建记录不存在", + "i18n.build_resource_cleanup_failed.c4cf":"清理构建资源失败", + "i18n.build_runs_on_image_interrupted.00fd":"构建 runsOn 镜像被中断", + "i18n.build_source.2ef9":"构建来源,", + "i18n.build_status_abnormal.8ca1":"构建状态异常或者被取消", + "i18n.build_status_message.42a7":"当前构建中任务数:{},队列中任务数:{} 构建任务等待超时或者超出最大等待数量,当前运行中的任务数:{}/{},取消执行当前构建", + "i18n.build_task_count_and_queue_count.f0b6":"当前构建中任务数:{},队列中任务数:{} {}", + "i18n.build_task_queue_waiting.5f06":"构建任务开始进入队列等待....", + "i18n.build_task_waiting.e303":"构建任务继续等待:{} {}", + "i18n.build_thread_pool_rejected_task.3bad":"构建线程池拒绝了未知任务:{}", + "i18n.build_trigger_batch_exception.47d5":"构建触发批量触发异常", + "i18n.build_trigger_queue_result.a1fe":"构建触发器队列执行结果:{}", + "i18n.build_unknown_error.dad6":"构建发生未知错误", + "i18n.cache_plugin_path_required.2093":"cache 插件 path 不能为空", + "i18n.cancel_success.285f":"取消成功", + "i18n.cannot_cancel_super_admin_permissions.99b5":"不能取消超级管理员的权限", + "i18n.cannot_configure_root_path.d86e":"不能配置根路径:", + "i18n.cannot_create_config_file_in_environment.55bb":"当前环境不能创建配置文件", + "i18n.cannot_delete_default_workspace.0c06":"不能删除默认工作空间", + "i18n.cannot_delete_online_cluster.11ad":"不能删除在线的集群", + "i18n.cannot_delete_recent_logs.ee19":"不能删除近一天相关的日志(文件修改时间)", + "i18n.cannot_delete_root_dir.fcdc":"不能删除根目录", + "i18n.cannot_delete_running_project.e56b":"不能删除正在运行的项目", + "i18n.cannot_delete_self.fec9":"不能删除自己", + "i18n.cannot_delete_super_admin.68e2":"不能删除超级管理员", + "i18n.cannot_disable_super_admin.6429":"不能禁用超级管理员", + "i18n.cannot_edit_corresponding_config_file.8d10":"不能编辑对应的配置文件", + "i18n.cannot_execute_error.4c29":"不能执行:error", + "i18n.cannot_join_cluster_as_role.01d4":"不能以 {} 角色加入集群", + "i18n.cannot_modify_own_info.4036":"不能修改自己的信息", + "i18n.cannot_operate_current_directory.aa3d":"不能操作当前目录", + "i18n.cannot_read_tar_archive_entry.85d7":"不能读取tarArchiveEntry {}", + "i18n.certificate_already_exists.adf9":"当前证书已经存在啦(系统全局范围内)", + "i18n.certificate_file_missing.c663":"证书文件丢失", + "i18n.certificate_has_no_aliases.3a2f":"证书没有任何:aliases", + "i18n.certificate_in_use_by_docker.dd63":"当前证书被 docker 关联中,不能直接删除", + "i18n.certificate_info_error_issuer_or_subject_DN_not_found.805d":"证书信息出现错误,未找到 issuerDN 或者 subjectDN", + "i18n.certificate_info_incorrect.a950":"证书信息不正确,证书压缩包里面必须包含:ca.pem、key.pem、cert.pem", + "i18n.certificate_info_table.fff8":"证书信息表", + "i18n.certificate_management.4001":"证书管理", + "i18n.certificate_serial_number_not_found.c8d1":"没有证书序列号", + "i18n.certificate_type_not_found.6706":"没有证书类型", + "i18n.check_cluster_info_exception.7b0c":"检查集群信息异常", + "i18n.check_docker_cert_exception.8042":"检查 docker 证书异常 {}", + "i18n.check_docker_dependency_error.60f7":"检查 docker 依赖错误:{}", + "i18n.check_docker_exception.a6d1":"检查 docker 异常", + "i18n.check_docker_url_exception.4302":"检查 docker url 异常 {}", + "i18n.check_email_error.636c":"检查邮箱信息错误:{}", + "i18n.check_git_client_exception.42a3":"检查 git 客户端异常", + "i18n.check_passed.dce8":"检查通过", + "i18n.checkout_version.a586":"把版本:%s check out ", + "i18n.chunk_upload_exception.87c1":"分片上传异常:{} {}", + "i18n.chunk_upload_file_exception.0dc3":"分片上传文件异常", + "i18n.class_path_and_java_ext_dirs_cp_required.7557":"ClassPath、JavaExtDirsCp 模式 MainClass必填", + "i18n.cleaned_data.0e9d":"{} 清理了 {}条数据", + "i18n.cleanup_history_build_failed_retrying.088e":"清理历史构建产物失败,已经重新尝试", + "i18n.cleanup_succeeded.02ea":"清理成功", + "i18n.cleanup_token_exception.760e":"执行清理 token[{}] 异常", + "i18n.clear_build_product_failed.edd4":"清除构建产物失败", + "i18n.clear_file_cache_failed.5cd1":"清空文件缓存失败", + "i18n.clear_old_version_package_failed.021c":"清空旧版本重新包失败", + "i18n.clear_script_file_failed.f595":"清理脚本文件失败", + "i18n.clear_success.2685":"清空成功", + "i18n.clear_success_message.51f4":"清除成功", + "i18n.clear_temp_file_failed_check_directory.7340":"清除临时文件失败,请检查目录:", + "i18n.clear_temp_file_failed_manually.0dad":"清除临时文件失败,请手动清理:", + "i18n.client_id_not_configured.ab8e":"没有配置 clientId", + "i18n.client_secret_not_configured.6923":"没有配置 clientSecret", + "i18n.client_terminated_connection.6886":"客户端终止连接:{}", + "i18n.close_beta_plan_success.5a94":"关闭 beta 计划成功", + "i18n.close_client_session_exception.530a":"关闭客户端回话异常", + "i18n.close_connection_exception.c855":"关闭连接异常", + "i18n.close_docker_exec_terminal.fec3":"关闭[{}] docker exec 终端:{}", + "i18n.close_exception.5b86":"关闭异常", + "i18n.close_project_failure.a1d2":"关闭项目失败:", + "i18n.close_resource_failure.dc66":"关闭资源失败", + "i18n.close_session_exception.3491":"关闭会话异常", + "i18n.close_session_exception_with_detail.85f0":"关闭会话异常:{}", + "i18n.close_success.8a31":"关闭成功", + "i18n.close_thread_pool.4cd9":"关闭 {} 线程池", + "i18n.cloud_server_network_issues.a865":"云服务器的安全组配置等网络相关问题排查定位。", + "i18n.cluster_address_check_exception.cd92":"填写的集群地址检查异常,请确认集群地址是正确的服务端地址,", + "i18n.cluster_binding_success.eb7e":"集群绑定成功", + "i18n.cluster_created_successfully.6bf3":"集群创建成功", + "i18n.cluster_does_not_exist.97a4":"当前集群不存在", + "i18n.cluster_id_changed.6e49":"集群ID 发生变化:{} -> {}", + "i18n.cluster_id_conflict.45b7":"{} 集群ID冲突:{} {}", + "i18n.cluster_info.32e0":"集群信息", + "i18n.cluster_info_incomplete.84a1":"集群信息不完整,不能加入该集群", + "i18n.cluster_info_incomplete_for_operation.ad96":"集群信息不完整,不能操作", + "i18n.cluster_info_incomplete_with_code.246b":"集群信息不完整,不能加入该集群:-1", + "i18n.cluster_management.74ea":"集群管理", + "i18n.cluster_manager_node_not_found.1cd0":"没有找到集群管理节点", + "i18n.cluster_name_required.5ca6":"请填写集群名称", + "i18n.cluster_node_not_in_system.0645":"当前集群对应的节点,不在本系统中无法退出集群", + "i18n.cluster_not_bound_to_group_for_docker_monitoring.3926":"当前集群还未绑定分组,不能监控 Docker 资产信息", + "i18n.cluster_not_bound_to_group_for_node_monitoring.1586":"当前集群还未绑定分组,不能监控集群节点资产信息", + "i18n.cluster_not_bound_to_group_for_ssh_monitoring.c894":"当前集群还未绑定分组,不能监控 SSH 资产信息", + "i18n.cluster_not_exist.4098":"对应的集群不存在", + "i18n.cluster_response_incorrect.c08a":"集群响应信息不正确,请确认集群地址是正确的服务端地址", + "i18n.cluster_status_code_exception.9d89":"集群状态码异常:{} {}", + "i18n.code_pull_conflict.6e8e":"拉取代码发生冲突,可以尝试清除构建或者解决仓库里面的冲突后重新操作。:", + "i18n.command_content_required.6005":"请输入命令内容", + "i18n.command_error.d0b4":"执行命令错误", + "i18n.command_execution_exception.4ccd":"执行命令异常", + "i18n.command_execution_failed.90ef":"执行命令失败", + "i18n.command_execution_failed_details.77ed":"执行命令失败,详情如下:", + "i18n.command_execution_record.56d5":"命令执行记录", + "i18n.command_management.621f":"命令管理", + "i18n.command_name_required.49fa":"请输入命令名称", + "i18n.command_non_zero_exit_code.a6e1":"执行命令退出码非0,{}", + "i18n.command_script_not_found_in_service.25ac":"当前服务中没有命令脚本:{}.{}", + "i18n.command_template_execution_link_exception.51cf":"命令模版执行链接异常", + "i18n.communication_ip_cannot_be_empty.ae35":"通信 IP 不能为空", + "i18n.compare_backup_failure.303e":"对比清空项目文件备份失败", + "i18n.compare_files_result.bec4":"对比文件结果,产物文件 {} 个、需要上传 {} 个", + "i18n.compare_files_result_with_delete.033d":"对比文件结果,产物文件 {} 个、需要上传 {} 个、需要删除 {} 个", + "i18n.compare_id_not_exist.43be":"比较 id 不存在", + "i18n.compare_project_failure.e6ab":"对比项目文件失败:", + "i18n.comparison_data_not_found.413e":"没有要对比的数据", + "i18n.completed_and_successful_count_insufficient.92fa":"完成并成功的个数不足 {}/{}", + "i18n.completed_count_insufficient.02e9":"完成的个数不足 {}/{}", + "i18n.compression_success.80b3":"压缩成功", + "i18n.compression_type_not_supported.9dea":"不支持的压缩类型,", + "i18n.config_file_already_exists.c5fe":"对应的配置文件已经存在啦", + "i18n.config_file_database_config_not_parsed.47b2":"未解析出配置文件中的数据库配置信息", + "i18n.config_file_not_exist.09dd":"配置文件不存在", + "i18n.config_file_not_exist_with_message.6a40":"配置文件不存在 {}", + "i18n.config_file_not_found.310e":"未找到配置文件:", + "i18n.config_file_not_found.fc87":"均未找到配置文件", + "i18n.config_path_exceeds_length_limit.f684":"配置路径超过{}长度限制:{}", + "i18n.configuration_modification_not_supported.5872":"当前环境下不支持在线修改配置文件", + "i18n.configure_announcement_title_or_content.7894":"请配置公告标题或者内容", + "i18n.configure_correct_auth_url.22e7":"请配置正确的授权 url", + "i18n.configure_correct_cluster_id.5a78":"请配置正确的集群Id,【jpom.clusterId】", + "i18n.configure_correct_redirect_url.058e":"请配置正确的重定向 url", + "i18n.configure_correct_self_hosted_gitlab_address.ad50":"请配置正确的自建 gitlab 地址", + "i18n.configure_correct_token_url.7bba":"请配置正确的令牌 url", + "i18n.configure_correct_user_info_url.1276":"请配置正确的用户信息 url", + "i18n.configure_dsl_content.42e3":"请配置 dsl 内容", + "i18n.configure_monitoring_interval.9741":"请配置监控周期", + "i18n.configure_run_path_property.356c":"请配置运行路径属性【jpom.path】", + "i18n.configure_table_name.f6fd":"请配置 table Name", + "i18n.configure_user_notification.250d":"请配置用户通知", + "i18n.connect_plugin_failed.e492":"连接插件端失败:{} {} {}", + "i18n.connection_closed.6d4e":"连接关闭 {} {}", + "i18n.connection_successful.0515":"成功连接 {} {}", + "i18n.connection_successful.b331":"连接成功", + "i18n.connection_successful_with_message.5cf2":"连接成功:", + "i18n.contact_does_not_exist.3369":"联系人不存在", + "i18n.container_build_exception.a98f":"容器构建异常:{} -> {}", + "i18n.container_build_host_config_conversion_failure.27aa":"容器构建 hostConfig 参数 {} 转换失败:{}", + "i18n.container_build_host_config_field_not_exist.6f61":"容器构建 hostConfig 字段【{}】不存在", + "i18n.container_build_interrupted.a17b":"容器 build 被中断:", + "i18n.container_build_product_path_cannot_use_ant_pattern.ddc7":"容器构建的产物路径不能使用 ant 模式", + "i18n.container_cli_interrupted.b67f":"容器cli被中断:", + "i18n.container_cluster.a5b4":"容器集群", + "i18n.container_command_execution_exception.a14a":"执行容器命令异常", + "i18n.container_log_fetch_exception.591a":"拉取 容器日志异常", + "i18n.container_name_cannot_be_empty.14b1":"容器名称不能为空", + "i18n.container_startup_failure.532e":"容器启动失败:", + "i18n.content_cannot_be_empty.9f0d":"内容不能为空", + "i18n.content_format_error.ce15":"内容格式错误,请检查修正", + "i18n.content_format_error_with_detail.c846":"内容格式错误,请检查修正:", + "i18n.content_is_empty.3122":"内容为空", + "i18n.content_type_not_supported.81a9":"不支持的 contentType", + "i18n.copy_success.20a4":"复制成功", + "i18n.correct_dingtalk_address_required.2b4a":"请输入正确钉钉地址", + "i18n.correct_encoding_format_required.1f7f":"请填写正确的编码格式,", + "i18n.correct_enterprise_wechat_address_required.5f2d":"请输入正确企业微信地址", + "i18n.correct_host_required.8c49":"请填写正确的 host", + "i18n.correct_information_required.5e12":"请输入正确的信息", + "i18n.correct_remote_address_required.0ce1":"请输入正确的远程地址", + "i18n.correct_retention_days_required.d542":"请填写正确的保留天数", + "i18n.correct_url_required.67a3":"请填写正确的 url", + "i18n.correct_verification_code2_required.df13":"请输入正确验证码", + "i18n.correct_verification_code_required.ff0d":"请输入正确的验证码", + "i18n.correction_data_failure.dac6":"修正数据失败:", + "i18n.correction_success.38bc":"修正成功", + "i18n.corresponding_file_required.57b3":"请选择对应到文件", + "i18n.corresponding_function.5bb5":"对应功能【{}-{}】", + "i18n.corresponding_node_does_not_exist.72cb":"当前对应的节点不存在", + "i18n.create_build_task_exception.06f1":"创建构建任务异常", + "i18n.create_file_watch_failure.bc1a":"创建文件监听失败", + "i18n.create_folder_failure.b632":"创建文件夹失败(文件夹名可能已经存在啦):", + "i18n.create_plugin_endpoint_connection_failure.30f8":"创建插件端连接失败 {}", + "i18n.create_success.04a6":"创建成功", + "i18n.credential_cannot_be_empty.d055":"凭证不能为空", + "i18n.cron_expression_format_error.6dcd":"cron 表达式格式不正确", + "i18n.cron_expression_incorrect.b41a":"cron 表达式不正确,", + "i18n.cumulative_filter_files.448d":"{} 累积过滤:{} 个文件 ", + "i18n.current_address_may_not_be_git.41c6":"当前地址可能不是 git 仓库地址:", + "i18n.current_address_no_repository.db31":"当前地址不存在仓库:", + "i18n.current_cluster_is_bound_to_workspace_cannot_be_deleted_directly.94c2":"当前集群还被工作空间绑定,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.current_distribution_data_lost.f9f8":"当前分发数据丢失", + "i18n.current_distribution_data_lost_record_id_not_exist.ca07":"当前分发数据丢失,记录id 不存在", + "i18n.current_distribution_has_build_items_cannot_unbind.a8e9":"当前分发存在构建项,不能解绑", + "i18n.current_distribution_has_only_one_project.cd59":"当前分发只有一个项目啦,删除整个分发即可", + "i18n.current_docker_already_in_other_cluster.e629":"当前 docker 已经加入到其他集群啦", + "i18n.current_docker_cluster_has_no_management_nodes_online.56cd":"当前 {} docker 集群没有管理节点在线", + "i18n.current_docker_cluster_still_associated_with_workspaces.b301":"当前 docker 集群还关联 {} 个工作空间集群,不能退出集群", + "i18n.current_docker_has_no_cluster_info.0b52":"当前 docker 没有集群信息", + "i18n.current_docker_not_in_cluster.f70c":"当前 docker {} 不在集群中", + "i18n.current_docker_offline.a509":"当前 {} docker 不在线", + "i18n.current_not_supported.78b7":"当前不支持:", + "i18n.current_operation_not_supported.3aec":"不支持当前操作:", + "i18n.current_project_associated_with_online_build_and_repository.96c5":"当前【项目】关联的【在线构建】关联的【仓库({})】被其他 {} 个不同发布方式的【在线构建】绑定暂不支持迁移", + "i18n.current_repository_associated_with_build.4b6e":"当前仓库被构建关联,不能直接删除", + "i18n.current_status.81c0":" 当前还在:", + "i18n.current_system_is_linux.e377":"当前系统为:linux", + "i18n.current_system_is_mac.0139":"当前系统为:mac", + "i18n.current_system_is_windows.91d1":"当前系统为:windows", + "i18n.current_upload_chunk_info_incorrect.900e":"当前上传的分片信息错误", + "i18n.data_already_exists.0397":"导入的数据已经存在啦", + "i18n.data_associated_id_inconsistent.59f7":"数据关联的id 不一致", + "i18n.data_backup.9e26":"数据备份", + "i18n.data_creation_time_format_incorrect.7772":"数据创建时间格式不正确 {} {}", + "i18n.data_does_not_exist.b201":"数据不存在", + "i18n.data_does_not_exist_with_details.d9b5":"数据不存在:", + "i18n.data_download_failed.9499":"数据下载失败", + "i18n.data_file_content_error.e86f":"数据文件内容错误,请检查文件是否被非法修改:", + "i18n.data_id_already_exists.28b6":"数据Id已经存在啦:{} : {}", + "i18n.data_id_cannot_be_empty.403b":"数据 id 不能为空", + "i18n.data_id_does_not_exist.a566":"数据id 不存在", + "i18n.data_id_label.81b6":"数据id", + "i18n.data_id_not_found.1b0a":"没有数据id", + "i18n.data_modification_time_format_incorrect.7ffe":"数据修改时间格式不正确 {} {}", + "i18n.data_name_label.5a14":"数据名称", + "i18n.data_not_exist.41f9":"对应数据不存在", + "i18n.data_not_supported_for_sorting.5431":"当前数据不支持排序", + "i18n.data_table_not_supported_for_grouping.6678":"当前数据表不支持分组", + "i18n.data_type_not_configured_correctly.bf16":"未正确配置数据类型", + "i18n.data_type_not_supported.fd03":"不支持的数据类型:", + "i18n.data_workspace_mismatch.ae1d":"数据工作空间和操作工作空间不一致", + "i18n.database_backup_label.62d8":"数据库备份", + "i18n.database_connection_not_configured.c80e":"没有配置数据库连接", + "i18n.database_corrupted.944e":"数据库异常,可能数据库文件已经损坏(可能丢失部分数据),需要重新初始化。可以尝试在启动参数里面添加 --recover:h2db 来自动恢复,:", + "i18n.database_event_execution_ended.690b":"数据库 {} 事件执行结束,:{}", + "i18n.database_exception.4894":"数据库异常", + "i18n.database_exception_due_to_resources.dbf1":"数据库异常,可能因为服务器资源不足(内存、硬盘)等原因造成数据异常关闭。需要手动重启服务端来恢复,:", + "i18n.database_mode_config_missing.ae5d":"数据库Mode配置缺失", + "i18n.database_not_initialized.e5e7":"还没有初始化数据库", + "i18n.database_username_not_configured.a048":"未配置(未解析到)数据库用户名", + "i18n.date_format_error.3d1c":"日期格式错误:", + "i18n.decode_failure.822e":"解码失败", + "i18n.decrypt_failure.ad83":"解密失败", + "i18n.decrypt_parameter_failure.d10a":"解密参数失败", + "i18n.default_cluster.38cf":"默认集群", + "i18n.default_setting.18c6":"默认", + "i18n.default_value.1aa9":"{} [默认]", + "i18n.default_workspace_cannot_delete.18b4":"系统默认的工作空间,不能删除", + "i18n.delay_build.7d62":"延迟 {} 秒后开始构建", + "i18n.delete_action.2f4a":"删除", + "i18n.delete_backup_data_file_failure.2ebf":"删除备份数据文件失败", + "i18n.delete_build_cache.c7f3":"删除构建缓存", + "i18n.delete_container_exception.9ad8":"删除容器异常", + "i18n.delete_data.40f8":"删除数据", + "i18n.delete_failure.acf0":"删除失败", + "i18n.delete_failure_with_colon.b429":"删除失败:", + "i18n.delete_failure_with_colon_and_full_stop.bc42":"删除失败:", + "i18n.delete_file_failure.041f":"删除文件失败,请检查", + "i18n.delete_file_failure_with_full_stop.6c96":"删除文件失败:", + "i18n.delete_log_file_failure.bf0b":"删除日志文件失败", + "i18n.delete_log_file_failure_with_colon.d867":"删除日志文件失败:", + "i18n.delete_old_package.ca95":"删除旧程序包:{}", + "i18n.delete_project_file_failure.f007":"删除项目文件失败:", + "i18n.delete_project_file_failure_with_full_stop.85b8":"删除项目文件失败:", + "i18n.delete_script_template_execution_error.8bc5":"删除脚本模版执行数据错误:{}", + "i18n.delete_service_success.4d73":"删除服务成功", + "i18n.delete_ssh_temp_file_failure.6e5f":"删除 ssh 临时文件失败", + "i18n.delete_success.0007":"删除成功", + "i18n.delete_success_with_cleanup.6155":"删除成功,并且清理历史构建产物成功", + "i18n.delete_success_with_colon.d44a":"删除成功:", + "i18n.delete_table_data.c813":"删除表 {} 中 {} 条工作空间id为:{} 的数据", + "i18n.deletion_success_message.4359":"删除成功!", + "i18n.demo_account_cannot_use_feature.a1a1":"演示账号不能使用该功能", + "i18n.demo_account_not_support_delete.f9a6":"演示账号不支持删除", + "i18n.demo_account_not_support_reset_password.a595":"演示账号不支持重置密码", + "i18n.demo_account_password_change_not_supported.91f4":"当前账户为演示账号,不支持修改密码", + "i18n.detect_local_docker_exception.ccfc":"探测本地 docker 异常", + "i18n.detect_local_docker_exception_with_details.7cc9":"探测本地 docker 异常:", + "i18n.directory_cannot_skip_levels.179e":"目录不能越级:", + "i18n.disable_monitoring.4615":"禁用监控", + "i18n.disallowed_download.06a3":"不允许下载当前地址的文件", + "i18n.disallowed_file_extension.eb05":"不允许编辑的文件后缀", + "i18n.disallowed_file_format.d6e4":"不允许的文件格式", + "i18n.distribute_exception.da82":"分发异常", + "i18n.distribute_exception_with_detail.28fe":"分发异常 {}", + "i18n.distribute_id_already_exists.2168":"分发id已经存在啦", + "i18n.distribute_id_already_exists_globally.6478":"分发id已经存在啦,分发id需要全局唯一", + "i18n.distribute_id_requirements.9c63":"分发id 不能为空并且长度在2-20(英文字母 、数字和下划线)", + "i18n.distribute_info_error_no_projects.e75f":"分发信息错误,没有任何项目", + "i18n.distribute_log.c612":"分发日志", + "i18n.distribute_management.3a2d":"分发管理", + "i18n.distribute_name_cannot_be_empty.0637":"分发名称不能为空", + "i18n.distribute_node_authorization_failure.bb92":"分发 {} 节点授权失败 {}", + "i18n.distribute_node_configuration_failure.8146":"分发 {} 节点配置失败 {}", + "i18n.distribute_result.a230":"分发结果:{}", + "i18n.distribute_success.c689":"分发成功 ", + "i18n.distribute_thread_exception.9725":"分发线程异常", + "i18n.distribution_exception_saving.8285":"{} {} 分发异常保存", + "i18n.distribution_in_progress.c3ae":"当前还在分发中,请等待分发结束", + "i18n.distribution_machine_required.5921":"请选择分发的机器", + "i18n.distribution_not_exist.cf8a":"对应的分发不存在", + "i18n.distribution_project_required.2560":"请选择分发项目", + "i18n.distribution_with_build_items_message.45f5":"当前分发存在构建项,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.do_not_reinitialize_database.9bb5":"不要重复初始化数据库", + "i18n.do_not_reopen.f86a":"不要重复打开", + "i18n.docker_already_exists.d9a5":"对应的 docker 已经存在啦", + "i18n.docker_already_exists_in_workspace.a0de":"对应工作空间已经存在对应的 docker 啦", + "i18n.docker_asset_imported.0ab4":"docker[{}] 资产导入", + "i18n.docker_asset_management.96d9":"DOCKER资产管理", + "i18n.docker_associated_workspaces_message.de78":"当前 docker 还关联{}个 工作空间 docker 不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.docker_authorization_failed.8ede":"docker 授权失败:{}", + "i18n.docker_certificate_file_missing.ad46":"docker 证书文件丢失", + "i18n.docker_certificate_migrated.b3d3":"docker[{}] 证书成功迁移到证书管理中", + "i18n.docker_cluster_associated_workspaces_message.5520":"当前 docker 还关联{}个 工作空间 docker 集群,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.docker_cluster_info.a2eb":"docker 集群信息", + "i18n.docker_console_connection_timeout.b2c7":"docker 控制台连接超时", + "i18n.docker_data_repair_not_needed.0fb9":"机器 DOCKER 表已经存在 {} 条数据,不需要修复机器 DOCKER 数据", + "i18n.docker_does_not_exist.bb41":"对应的 docker 不存在", + "i18n.docker_does_not_exist_with_code.689b":"对应的 docker 不存在:-1", + "i18n.docker_exec_terminal_process_ended.c734":"[{}] docker exec 终端进程结束", + "i18n.docker_info.00d2":"docker 信息", + "i18n.docker_info_not_found.4f64":"当前集群未找到 docker 信息", + "i18n.docker_label_required.b690":"请填要执行 docker 标签", + "i18n.docker_log_thread_ended.8230":"docker log 线程结束:{} {}", + "i18n.docker_management.e7e5":"Docker管理", + "i18n.docker_not_exist.7ed8":"不存在对应的 docker", + "i18n.docker_not_found.2a2e":" 没有找到任何 docker。可能docker tag 填写不正确,需要为 docker 配置标签", + "i18n.docker_tag_incorrect.8b62":"docker tag 填写不正确,没有找到任何docker", + "i18n.dockerfile_not_found_in_repository.4168":"仓库目录下没有找到 Dockerfile 文件: {}", + "i18n.dockerfile_path_required.69ac":"请填写要执行的 Dockerfile 路径", + "i18n.download_action.f26e":"下载", + "i18n.download_exception.e616":"下载文件异常", + "i18n.download_failed.65e2":"下载失败", + "i18n.download_failed_generic.be4f":"下载文件失败", + "i18n.download_failed_retry.c113":"下载失败。请刷新页面后重试", + "i18n.download_file_description.10cb":"下载文件 {} {} {}", + "i18n.download_file_error.5bcd":"下载文件异常:", + "i18n.download_file_size.d4de":"下载成功文件大小:", + "i18n.download_progress.898a":"当前进度:{} ,文件总大小:{},已经下载:{}", + "i18n.download_remote_file.ae84":"下载远程文件", + "i18n.download_remote_file_exception.3ee0":"下载远程文件异常", + "i18n.download_remote_file_failed.fcc3":"下载远程文件失败:", + "i18n.download_success.5094":"下载成功", + "i18n.download_success_and_distribute.ae94":"下载成功,开始分发!", + "i18n.dsl_info_not_configured.3487":"未配置 dsl 信息(项目信息错误)", + "i18n.dsl_not_configured.8a57":"DSL 未配置运行管理或者未配置 {} 流程", + "i18n.editable_suffixes_not_configured.5b41":"没有配置可允许编辑的后缀", + "i18n.email_configuration.b3f7":"邮箱配置", + "i18n.email_service_not_configured.3180":"未配置邮箱服务不能发送邮件:{} {}", + "i18n.email_verification_failed.5863":"验证邮箱信息失败,请检查配置的邮箱信息。端口号、授权码等。", + "i18n.empty_execution_result.9fe8":"执行结果为空,", + "i18n.empty_file_cannot_upload.88df":"空文件不能上传", + "i18n.empty_file_or_folder_for_publish.cae8":"发布的文件或者文件夹为空,不能继续发布", + "i18n.empty_folder_cannot_be_packed.5a75":"文件夹为空,不能打包 #", + "i18n.encoding_error.b685":"编码异常", + "i18n.env_must_be_map_type.f8ad":"env 必须是 map 类型", + "i18n.environment_variable.3867":"环境变量", + "i18n.environment_variables_not_found.dbd4":"没有环境变量", + "i18n.error_info.99ed":",错误信息:", + "i18n.error_message.483d":"未找到脚本库信息:{},请检查引用标记是否正确或者脚本是否被删除", + "i18n.error_sql.15ff":"错误 sql:{}", + "i18n.event_loss_or_execution_error.7b14":"事件丢失或者执行错误:{} {}", + "i18n.event_script_does_not_exist.e726":"事件脚本不存在:{} {}", + "i18n.event_script_interrupted.8c79":"事件脚本中断:", + "i18n.event_type_not_supported.e9c3":"不支持的事件类型:{}", + "i18n.exclusion_success.7d46":"剔除成功", + "i18n.execute.1a6a":"执行", + "i18n.execute_dsl_script_exception.0882":"执行 DSL 脚本异常:{}", + "i18n.execute_event_script_error.7c69":"执行事件脚本错误", + "i18n.execute_script_exit_code.64a8":"执行 {} 类型脚本的退出码是:{}", + "i18n.execution_command_required.1cf3":"请选择执行的命令", + "i18n.execution_completed.24a1":"执行完毕:", + "i18n.execution_ended.b793":"执行结束:{}", + "i18n.execution_ended_with_detail.8f93":"执行结束: {} {}", + "i18n.execution_ended_with_duration.a59b":"执行结束 {}流程,耗时:{}", + "i18n.execution_exception.b0d5":"执行异常", + "i18n.execution_exception_message.ef79":"执行异常:", + "i18n.execution_exception_with_detail.c142":"执行异常:{}", + "i18n.execution_exception_with_flow.6d4b":"执行异常[{}]流程:{}", + "i18n.execution_frequency.d014":"{} 秒执行一次", + "i18n.execution_interrupted.1bb6":"执行被中断", + "i18n.execution_interrupted_message.2597":"执行被中断:{}", + "i18n.execution_interrupted_reason.e3d7":"执行中断 {} 流程,原因事件脚本中断", + "i18n.execution_node_required.d747":"请选择执行节点", + "i18n.execution_result_file_not_found_in_container.cf18":"容器中没有找到执行结果文件: {}", + "i18n.execution_succeeded.f56c":"执行成功", + "i18n.existing_project_cannot_be_soft_link.aa5a":"已经存在的项目不能修改为软链项目", + "i18n.exit_code.3b54":"本次执行退出码: {}", + "i18n.exit_code.ea65":"执行退出码:{}", + "i18n.exit_successful.8150":"退出成功", + "i18n.export_image_exception.cb1c":"导出镜像异常", + "i18n.export_low_version_data.f1aa":"1. 导出低版本数据 【启动程序参数里面添加 --backup-h2】", + "i18n.exported_project_data.fd1f":"导出的项目数据 ", + "i18n.exported_repo_data.bac5":"导出的 仓库信息 数据 ", + "i18n.exported_ssh_data.2896":"导出的 ssh 数据 ", + "i18n.external_config_not_exist_or_not_configured.f24e":"外置配置不存在或者未配置:{},使用默认配置", + "i18n.failure_prefix.115a":"失败:", + "i18n.file_already_exists.983d":"文件已经存在啦", + "i18n.file_already_exists.d60c":"当前文件已经存在啦,请勿重复上传", + "i18n.file_cleanup_failed.511e":"清理文件失败", + "i18n.file_deletion_event.a51c":"文件删除事件:{}", + "i18n.file_deletion_event_with_details.7537":"文件删除事件:{} {}", + "i18n.file_directory_too_long.c101":"文件目录长度超过 500 ,自动忽略此类文件:{}", + "i18n.file_does_not_exist_anymore.2fab":"文件已经不存在啦", + "i18n.file_does_not_exist_for_download.8dd6":"文件不存在,无法下载", + "i18n.file_download_failed.7983":"文件下载失败:", + "i18n.file_downloading.7a8f":"文件下载中", + "i18n.file_downloading_status.c995":"文件下载中:", + "i18n.file_format_not_supported.eac4":"不支持的文件格式", + "i18n.file_full_path.16cc":"文件全路径:{}", + "i18n.file_in_use_stop_project_first.a2c3":"文件被占用,请先停止项目", + "i18n.file_management_center.0f5f":"文件管理中心", + "i18n.file_merge_error.f32f":"文件合并后异常,文件不完成可能被损坏", + "i18n.file_merge_exception_details.e9d0":"文件合并异常 {}:{} -> {}", + "i18n.file_missing_cannot_publish.3818":"当前文件丢失不能执行发布任务", + "i18n.file_modification_event.5bc2":"文件修改事件:{} {}", + "i18n.file_name_already_exists.0d4e":"文件名已经存在拉", + "i18n.file_name_error_message.7a25":"文件名不能包含/", + "i18n.file_name_not_configured.39fa":"没有配置fileName", + "i18n.file_name_not_found.b0ed":"没有文件名", + "i18n.file_not_exist.5091":"对应的文件不存在", + "i18n.file_not_exist.ea6a":"不存在对应的文件", + "i18n.file_not_found.d952":"文件不存在", + "i18n.file_or_directory_not_found.f03e":"文件不存在或者是目录:", + "i18n.file_publish_task_record.edc4":"文件发布任务记录", + "i18n.file_published.d1d9":"文件发布", + "i18n.file_search_failed.231b":"文件搜索失败", + "i18n.file_signature_info_not_found.83bf":"没有文件签名信息", + "i18n.file_size_exceeds_limit.8272":"上传文件大小超出限制", + "i18n.file_storage_center.6acf":"文件存储中心", + "i18n.file_system_monitoring_exception.d4c0":"文件系统监控异常:", + "i18n.file_too_large.9994":"上传文件太大了,请重新选择一个较小的文件上传吧", + "i18n.file_transfer_exception.bda6":"转发文件异常", + "i18n.file_type_no_restart.0977":"file 类型项目没有 restart", + "i18n.file_type_no_stop.00ff":"file 类型项目没有 stop", + "i18n.file_type_not_supported.ae5d":"不支持的文件类型:", + "i18n.file_type_not_supported2.d497":"不支持的文件类型:", + "i18n.file_type_not_supported3.f551":"不支持的文件类型:{}", + "i18n.file_type_not_supported_with_placeholder.db22":"不支持的文件类型:{}", + "i18n.file_type_project_no_running_status.32a2":"file 类型项目没有运行状态", + "i18n.file_upload_exception.a5f6":"发生文件上传异常:{} {}", + "i18n.file_upload_failed.462e":"上传文件失败:", + "i18n.file_upload_failure_due_to_missing_chunks.1865":"文件上传失败,存在分片丢失的情况, {} != {}", + "i18n.file_upload_mode_not_configured.b3b2":"没有配置文件上传模式", + "i18n.file_write_success.804a":"文件写入成功", + "i18n.fill_download_address.763c":"填写下载地址", + "i18n.fix_null_workspace_data.4d0b":"修复工作空间为 null 的数据 {} {}", + "i18n.folder_download_not_supported.c3b7":"暂不支持下载文件夹", + "i18n.folder_or_file_exists.c687":"文件夹或者文件已存在", + "i18n.forbidden_operation_range.247f":"【禁止操作】{} {} 至 {}", + "i18n.forbidden_operation_time.d83d":"【禁止操作】当前时段禁止执行", + "i18n.forbidden_operation_time_period.86a3":"【禁止操作】当前时间不在可执行的时间段内,限制时间段:", + "i18n.forbidden_operation_time_range.92bf":"【禁止操作】当前时段禁止执行 {} 至 {}", + "i18n.force_unbind_succeeded.5bfd":"强制解绑成功", + "i18n.free_script.7760":"自由脚本", + "i18n.fuzzy_match_files.139d":"{} 模糊匹配到 {} 个文件", + "i18n.general_error_message.728a":"啊哦,好像哪里出错了,请稍候再试试吧~", + "i18n.general_execution_exception.62e9":"执行异常:", + "i18n.get_build_status_exception.914e":"获取构建状态异常", + "i18n.get_container_execution_result_failure.1828":"获取容器执行结果失败", + "i18n.get_container_execution_result_interrupted.4a48":"获取容器执行结果操作被中断:", + "i18n.get_container_log_failure.915d":"获取容器日志失败", + "i18n.get_container_log_interrupted.041d":"获取容器日志操作被中断:", + "i18n.get_container_log_interrupted_message.83a5":"获取容器日志被中断:", + "i18n.get_decrypt_distribution_failure.4feb":"获取解密分发失败", + "i18n.get_decrypt_implementation_failure.e77a":"获取解密实现失败", + "i18n.get_docker_cluster_failure_with_placeholder.06cb":"获取 {} docker 集群失败 {}", + "i18n.get_docker_cluster_info_failure.c80d":"获取 docker 集群信息失败", + "i18n.get_docker_cluster_info_failure_with_code.fa77":"获取 docker 集群信息失败:-1", + "i18n.get_folder_failure.0fda":"获取文件夹失败", + "i18n.get_node_monitoring_info_failure.595a":"获取节点监控信息失败", + "i18n.get_port_error.0698":"获取端口错误", + "i18n.get_project_info_failure.ddff":"获取项目信息失败:", + "i18n.get_project_pid_failure.17b0":"获取项目pid 失败", + "i18n.get_repository_branch_failure.37cc":"获取仓库分支失败", + "i18n.get_success.fb55":"获取成功", + "i18n.git_fetch_failed_status_code.5187":"git fetch失败状态码:", + "i18n.git_installation_location.7984":"git安装位置:{}", + "i18n.git_reset_hard_failed_status_code.d818":"git reset --hard失败状态码:", + "i18n.git_submodule_update_failed_status_code.2218":"git submodule update 失败状态码:", + "i18n.global_workspace_variable_edit_in_system_management.58d2":"全局工作空间变量请到系统管理修改", + "i18n.go_plugin_version_required.ccf6":"go 插件 version 不能为空", + "i18n.gradle_plugin_depends_on_java.2bb3":"gradle 插件依赖 java , 使用 gradle 插件必须优先引入 java 插件", + "i18n.gradle_plugin_version_required.b983":"gradle 插件 version 不能为空", + "i18n.greeting.5ecd":"您好,Jpom", + "i18n.h2_connection_successful.11f3":"成功连接 H2 ,开始尝试自动备份", + "i18n.h2_database_backup_success.a099":"H2 数据库备份成功:{}", + "i18n.handle_message_exception.0bdc":"处理消息异常", + "i18n.handle_message_exception_with_colon.56f0":"处理消息异常:", + "i18n.handle_node_deletion_script_failure.071b":"处理 {} 节点删除脚本失败 {}", + "i18n.handle_node_deletion_script_failure_duplicate.821e":"处理 {} 节点删除脚本失败{}", + "i18n.handle_node_deletion_script_library_failure.4205":"处理 {} 节点删除脚本库失败 {}", + "i18n.handle_node_sync_script_failure.e99f":"处理 {} 节点同步脚本失败 {}", + "i18n.handle_node_synchronization_script_library_failure.14e4":"处理 {} 节点同步脚本库失败 {}", + "i18n.hard_drive_monitoring_error.43e7":"硬盘资源监控异常:", + "i18n.heartbeat_message_forwarding_failed.89cc":"心跳消息转发失败 {} {}", + "i18n.host_cannot_be_empty.644a":"参数错误host不能为空", + "i18n.host_field_required.5c36":"第 {} 行 host 字段不能位空", + "i18n.http_proxy_address_unavailable.b3f2":"HTTP代理地址不可用:", + "i18n.i18n_node_already_exists.632d":"对应的节点已经存在拉", + "i18n.id_already_exists.6208":"id已经存在啦", + "i18n.id_cannot_be_empty.8f2c":"id 不能为空", + "i18n.id_is_empty.3bbf":"id 为空", + "i18n.ignore_execution_event_script.8872":"忽略执行事件脚本 {} {} {}", + "i18n.ignore_log_record.48f5":"忽略记录日志 {}", + "i18n.ignored_operation.edee":"忽略的操作:{}", + "i18n.illegal_access.c365":"非法访问", + "i18n.illegal_character_encoding_format.af7a":"配置的字符编码格式不合法:", + "i18n.image_cannot_be_empty.1600":"镜像不能为空", + "i18n.image_name_required.ab44":"请填写镜名称", + "i18n.image_not_exist.ee17":"镜像不存在", + "i18n.image_tag_required.92cf":"请填写镜像标签", + "i18n.import_data.8ef8":"导入数据", + "i18n.import_exception.04b6":"导入第 {} 条数据异常:{}", + "i18n.import_low_version_data_to_new_version.247b":"2. 将导出的低版本数据( sql 文件) 导入到新版本中【启动程序参数里面添加 --replace-import-h2-sql=/xxxx.sql (路径需要替换为第一步控制台输出的 sql 文件保存路径)】", + "i18n.import_project_template_csv.c6f1":"项目导入模板.csv", + "i18n.import_save_failure.001a":"导入第 {} 条数据保存失败:{}", + "i18n.import_save_project_exception.cdbe":"导入保存项目异常", + "i18n.import_success.b6d1":"导入成功", + "i18n.import_success_message.2df3":"导入成功(编码格式:{}),更新 {} 条数据,因为节点分发/项目副本忽略 {} 条数据", + "i18n.import_success_with_count.22b9":"导入成功,添加 {} 条数据,修改 {} 条数据", + "i18n.import_success_with_details.a4a0":"导入成功(编码格式:{}),添加 {} 条数据,修改 {} 条数据", + "i18n.in_progress.b851":"进行中:", + "i18n.incompatible_database_version.8f7b":"数据库版本不兼容,需要处理跨版本升级。", + "i18n.incompatible_program_versions.5291":"当前程序版本 {} 新版程序最低兼容 {} 不能直接升级", + "i18n.incomplete_data_not_supported.b5d3":"数据不完整,暂不支持操作", + "i18n.incomplete_node_info_missing_machine_id.1c9a":"节点信息不完整,缺少机器id", + "i18n.incomplete_upload_info_now_slice.34aa":"上传信息不完成:nowSlice", + "i18n.incomplete_upload_info_total_slice.7e85":"上传信息不完成:totalSlice", + "i18n.incorrect_account_credentials.b2c5":"账号密码不正确", + "i18n.incorrect_account_credentials_or_unsupported_auth.1ef9":"账号密码不正确或者不支持的身份验证,", + "i18n.incorrect_certificate_info.aee1":"填写的证书信息错误", + "i18n.incorrect_cluster_address.893f":"填写的集群地址不正确", + "i18n.incorrect_ip_address.b872":"ip 地址信息不正确", + "i18n.incorrect_line_number.5877":"行号不正确", + "i18n.incorrect_mode_for_migration.caef":"当前模式不正确,不能直接迁移到 {}", + "i18n.incorrect_node_info_node_does_not_exist.2fd8":"节点信息不正确,对应对节点不存在", + "i18n.incorrect_parameter.02ce":"参数存在不正确", + "i18n.incorrect_parameter_format.9efb":"传入的参数格式不正确", + "i18n.incorrect_project_id.5f70":"项目id 不正确", + "i18n.incorrect_publish_method.e095":"发布方法不正确", + "i18n.incorrect_range_information.a41c":"range 传入的信息不正确", + "i18n.incorrect_repository_credentials.f1c8":"仓库账号或者密码错误:", + "i18n.incorrect_type_passed.d42e":"传入的类型错误:{}", + "i18n.index_field_not_configured.96d9":"索引未配置字段", + "i18n.info_to_retrieve_not_found.96d7":"没有要获取的信息", + "i18n.initialization_failure.19e9":"初始化失败:", + "i18n.initialization_success.4725":"初始化成功", + "i18n.initialize_database_failure.2ef9":"初始化数据库失败 {}", + "i18n.initialize_user_failure.fe27":"初始化用户失败", + "i18n.initialize_workspace.bc97":"初始化{}工作空间", + "i18n.install_id_does_not_exist.6aee":"数据错误,安装 ID 不存在", + "i18n.installation_success.811f":"安装成功", + "i18n.installation_success_with_machine_id.1cd6":"安装成功,本机安装 ID 为:{}", + "i18n.introducing_script_content.a55b":"引入脚本内容:{}[{}]", + "i18n.invalid_email_format.7526":"邮箱格式不正确", + "i18n.invalid_file_type.7246":"上传的文件不是 zip", + "i18n.invalid_http_proxy_address.1da1":"HTTP代理地址格式不正确", + "i18n.invalid_jar_file.e80a":"jar 包文件不合法", + "i18n.invalid_or_expired_token.bc43":"token错误,或者已经失效", + "i18n.invalid_project_path.04f7":"项目路径不能为空,不能为顶级目录,不能包含中文", + "i18n.invalid_remote_address_format.7f32":"配置的远程地址不规范,请重新填写:", + "i18n.invalid_repository_info.b4ad":"无效的仓库信息", + "i18n.invalid_runs_on_image_name.4b96":"runsOn 镜像名称不合法", + "i18n.invalid_shard_id.46fd":"不合法的分片id", + "i18n.invalid_webhooks_address.d836":"WebHooks 地址不合法", + "i18n.invalid_zip_file.3092":"上传的压缩包不是 Jpom [{}] 包", + "i18n.ip_authorization_interception_exception.8130":"IP授权拦截异常,请检查配置是否正确", + "i18n.java_ext_dirs_cp_required.1f4a":"JavaExtDirsCp 模式 javaExtDirsCp必填", + "i18n.java_plugin_version_required.de39":"java 插件 version 不能为空", + "i18n.join_beta_program.5c1f":"是否加入 beta 计划", + "i18n.joined_beta_program.c4e2":"成功加入 beta 计划", + "i18n.jpom_log_not_configured.3153":"没有配置 JPOM_LOG", + "i18n.jpom_project_maintenance_system.7f8e":"Jpom项目运维系统", + "i18n.jpom_verification_code.5b5b":"Jpom 验证码", + "i18n.key_field_not_configured.7b22":"没有配置 KEY 字段,", + "i18n.line_number_error.c65d":"行号错误", + "i18n.link_id_required.5dc7":"Link 模式 LinkId必填", + "i18n.list_and_query.c783":"列表、查询", + "i18n.listen_file_failed_may_not_exist.fd56":"监听文件失败,可能文件不存在", + "i18n.listen_log_changes.9081":"监听日志变化", + "i18n.listen_log_success_currently_sessions_viewing.a74a":"监听{}日志成功,目前共有{}个会话正在查看", + "i18n.listen_task_lost_or_not_found.347f":"监听任务丢失或者未找到:{}", + "i18n.listener_key_not_found.6d3a":"没有找到监听 key", + "i18n.load_file_failure.86cc":"加载文件失败:", + "i18n.load_oauth2_config.da42":"加载 oauth2 配置 :{} {}", + "i18n.load_plugin.1f64":"加载:{} 插件", + "i18n.load_success.154e":"加载成功", + "i18n.local_docker_exists.ec31":"已经存在本地 docker 信息啦,不要重复添加:", + "i18n.local_git_certificate_not_supported.b395":"暂时不支持本地 git 指定证书拉取代码", + "i18n.log_file_cleanup_failed.3a3b":"清理日志文件失败", + "i18n.log_file_does_not_exist.f6c6":"日志文件不存在", + "i18n.log_file_does_not_exist_or_error.a0e7":"日志文件不存在或者错误", + "i18n.log_file_error.473b":"日志文件错误", + "i18n.log_file_not_found.7f2e":"没有日志文件:", + "i18n.log_reading.a4c8":"日志阅读", + "i18n.log_recorder_error_message.ee3e":"日志记录器被关闭/或者未启用", + "i18n.log_recorder_not_enabled.5a4e":"日志记录器未启用", + "i18n.log_retention_days.99d1":"统计日志保留天数 {}", + "i18n.login_JPOM.0de6":"登录JPOM", + "i18n.login_failed_please_enter_correct_password_and_account.03b2":"登录失败,请输入正确的密码和账号,多次失败将锁定账号", + "i18n.login_failure_O_auth2_message.3e91":"登录失败(OAuth2),请联系管理员!", + "i18n.login_info_expired_please_re_login.fbbc":"登录信息已经过期请重新登录", + "i18n.login_info_expired_re_login.6bc4":"登录信息已失效,重新登录", + "i18n.login_info_required.973b":"请输入登录信息", + "i18n.login_log.3fb2":"登录日志", + "i18n.login_name_already_exists.2511":"登录名已经存在", + "i18n.login_name_already_taken.5b46":"当前登录名已经被系统占用", + "i18n.login_name_already_taken_message.b01f":"当前登录名已经被系统占用啦", + "i18n.login_name_cannot_be_empty.9a99":"登录名不能为空", + "i18n.login_name_cannot_contain_chinese_and_special_characters.48a8":"登录名不能包含汉字并且不能包含特殊字符", + "i18n.login_name_email_format_length_range.25f3":"登录名如果为邮箱格式,长度必须 {}-{}", + "i18n.login_name_format_incorrect.f789":"登录名格式不正确(英文字母 、数字和下划线),并且长度必须 {}-{}", + "i18n.login_name_length_range.fe8d":"登录名长度范围 3-50", + "i18n.login_password_required.9605":"请填写登录密码", + "i18n.login_success.71fa":"登录成功", + "i18n.machine_asset_management.36ea":"机器资产管理", + "i18n.machine_docker_info.9914":"机器DOCKER信息", + "i18n.machine_info_not_exist.3468":"对应的机器信息不存在", + "i18n.machine_installation_id.d0b9":"本机安装 ID 为:{}", + "i18n.machine_name_required.e8cf":"请填写机器名称", + "i18n.machine_node_info.6a75":"机器节点信息", + "i18n.machine_ssh_info.8dbb":"机器SSH信息", + "i18n.machines_docker_data_fixed.af8a":"成功修复 {} 条机器 DOCKER 数据", + "i18n.machines_node_data_fixed.7744":"成功修复 {} 条机器节点数据", + "i18n.machines_ssh_data_fixed.1387":"成功修复 {} 条机器 SSH 数据", + "i18n.main_class_attribute_not_found.24c9":"清单文件中没有找到对应的MainClass属性", + "i18n.main_class_attribute_not_found.93e8":"中没有找到对应的MainClass属性", + "i18n.main_class_not_found.8a12":"没有找到运行的主类", + "i18n.main_class_not_found.b4b7":"中没有找到对应的MainClass:", + "i18n.manager_node_not_found.df04":"当前集群未找到管理节点", + "i18n.manual_cache_refresh_exception.9d91":"手动刷新缓存异常", + "i18n.manual_cancel.8464":"手动取消", + "i18n.manual_cancel_distribution.7bf6":"手动取消分发", + "i18n.manual_cancel_task.e592":"手动取消任务", + "i18n.mark_already_exists.0ccc":"标记已存在", + "i18n.mark_cannot_be_empty.1927":"标记不能为空", + "i18n.mark_must_contain_letters_numbers_underscores.667d":"标记只能包含字母、数字、下划线", + "i18n.maven_plugin_depends_on_java.23f8":"maven 插件依赖 java , 使用 maven 插件必须优先引入 java 插件", + "i18n.maven_plugin_version_required.71f1":"maven 插件 version 不能为空", + "i18n.max_concurrent_shard_ids.f89c":"分片id最大同时使用 100 个", + "i18n.message_conversion_exception.cce8":"消息转换异常", + "i18n.message_send_failed.4dbe":"消息发送失败,自动移除此会话:{}", + "i18n.method_not_supported.90c4":"当前方法不被支持,暂时不能使用", + "i18n.mfa_incorrect_code.8783":" mfa 验证码不正确", + "i18n.migrate_data.f556":"迁移数据", + "i18n.migration_completed.7a30":"迁移完成,累计迁移 {} 条数据,耗时:{}", + "i18n.migration_docker_cert_error.a5ea":"迁移 docker[{}] 证书发生异常", + "i18n.migration_success.b20d":"{} 迁移成功 {} 条数据", + "i18n.migration_success_message.e546":"项目迁移成功:{} | {}", + "i18n.migration_target_workspace_node_mismatch.d9cf":"要迁移到的目标工作空间和节点不一致", + "i18n.missing_package_in_root_dir.8bab":"一级目录没有%s包,请先到文件管理中上传程序的%s", + "i18n.missing_script_library_message.be9a":"对应的脚本库不存在:", + "i18n.missing_script_message.af89":"找不到对应的脚本", + "i18n.modified_value_is_empty.e4fa":"修改的值为空", + "i18n.modify_db_password_must_restart.d08d":"修改数据库密码必须重启", + "i18n.modify_or_add_data.e1f0":"修改、添加数据", + "i18n.modify_service_success.bd75":"修改服务成功", + "i18n.modify_success.69be":"修改成功", + "i18n.monitor_docker_exception.e326":"监控 docker[{}] 异常", + "i18n.monitor_docker_exception_detail.e334":"监控 docker[{}] 异常 {}", + "i18n.monitor_docker_timeout.b03b":"监控 docker[{}] 超时 {}", + "i18n.monitor_info.f299":"监控信息", + "i18n.monitor_name_cannot_be_empty.514a":"监控名称不能为空", + "i18n.monitor_node_exception.6ff1":"监控 {} 节点异常 {}", + "i18n.monitor_ssh_exception.e9ce":"监控 ssh[{}] 异常", + "i18n.monitor_ssh_timeout.59fd":"监控 ssh[{}] 超时 {}", + "i18n.monitored_directory_does_not_exist.fa4e":"被监控的目录不存在忽略创建监听器:{}", + "i18n.monitoring_item_not_exist.32c8":"不存在监控项啦", + "i18n.monitoring_logs.2217":"监控日志", + "i18n.monitoring_notifications.de94":"监控通知", + "i18n.monitoring_user_actions.f2d5":"监控用户操作", + "i18n.multi_download_not_supported.94b9":"不支持分片多端下载", + "i18n.multiple_certificate_files_found.bee3":"找到 2 个以上的证书文件", + "i18n.multiple_clusters_exist.196b":"系统中存在多个集群,不需要自动绑定数据", + "i18n.multiple_docker_addresses_found.0f82":"DOCKER 地址 {} 存在多个数据,将自动合并使用 {} DOCKER 的配置信息", + "i18n.multiple_node_data_exists_merge_config.043f":"节点地址 {} 存在多个数据,将自动合并使用 {} 节点的配置信息", + "i18n.multiple_ssh_addresses_found.b3f7":"SSH 地址 {} 存在多个数据,将自动合并使用 {} SSH的配置信息", + "i18n.multiple_worker_nodes_exist.7110":"还存在多个工作节点,不能退出最后一个管理节点", + "i18n.name_field_required.e0c5":"第 {} 行 name 字段不能位空", + "i18n.name_required.856d":"请填写名称", + "i18n.need_configure_absolute_path.f2e6":"需要配置绝对路径:", + "i18n.need_execute_callbacks.b708":"需要执行 {} 个回调", + "i18n.need_execute_pre_events.b848":"需要执行 {} 个前置事件", + "i18n.need_handle_build_micro_queue_count.3010":"需要处理构建微队列数:{}", + "i18n.need_handle_build_queue_count.c01e":"需要处理的 {} 构建队列数:{}", + "i18n.need_initialize_system.fb62":"需要初始化系统", + "i18n.network_resource_monitoring_error.4ede":"网卡资源监控异常:", + "i18n.new_package_same_as_running_package.e25a":"新包和正在运行的包一致", + "i18n.new_version_exists_download_unavailable.4ba7":"存在新版本,下载地址不可用", + "i18n.nickname_length_limit.6312":"昵称长度只能是2-10", + "i18n.no_any_branch.d042":"没有任何分支", + "i18n.no_asset_machine.c77c":"没有对应的资产机器", + "i18n.no_asset_management_permission.739e":"您没有资产管理权限", + "i18n.no_available_docker_server.6aaa":"{} 没有可用的 docker server", + "i18n.no_available_docker_server.9fc6":" 没有可用的 docker server", + "i18n.no_available_maven_versions.dffe":"maven 镜像库中没有找到任何可用的 maven 版本", + "i18n.no_available_new_version_upgrade.d8f2":"没有可用的新版本升级:-1", + "i18n.no_branch_name.1879":"没有 branch name", + "i18n.no_branch_or_tag_message.8ae3":"没有 {} 分支/标签", + "i18n.no_branches_or_tags_in_repository.76b6":"仓库没有任何分支或者标签", + "i18n.no_build.d163":"没有对应的构建", + "i18n.no_build_history.39f7":"没有对应的构建历史", + "i18n.no_build_id.a0b8":"没有buildId", + "i18n.no_build_record.66a2":"没有对应的构建记录", + "i18n.no_build_record_found.76f4":"还没有对应的构建记录", + "i18n.no_cache_info.fba1":"没有对应的缓存信息", + "i18n.no_cache_info_with_minus_one.52f2":"没有对应的缓存信息:-1", + "i18n.no_certificate_files_found.ff6d":"没有找到任何证书文件", + "i18n.no_changes_in_repository_code.b1aa":"仓库代码没有任何变动终止本次构建:{}", + "i18n.no_changes_in_repository_code_with_details.fd9f":"仓库代码没有任何变动终止本次构建:{} {}", + "i18n.no_cluster_info_found.fb40":"没有找到对应的集群信息", + "i18n.no_command_to_execute.340b":"没有需要执行的命令", + "i18n.no_config_file_found.9720":"没有找到对应配置文件:", + "i18n.no_content_to_execute.66aa":"没有需要执行的内容", + "i18n.no_corresponding_build_record.b3b2":"没有对应构建记录.", + "i18n.no_corresponding_build_record_ignore_deletion.86a0":"没有对应构建记录,忽略删除", + "i18n.no_corresponding_command.165e":"没有对应对命令", + "i18n.no_corresponding_data.4703":"没有对应的数据", + "i18n.no_corresponding_data_or_permission.1291":"没有对应的数据或者没有此数据权限", + "i18n.no_corresponding_distribution_project.6dcd":"没有对应的分发项目", + "i18n.no_corresponding_docker.733e":"没有对应的 docker", + "i18n.no_corresponding_docker_asset.6f06":"没有对应的 docker 资产", + "i18n.no_corresponding_docker_info.c47a":"没有对应的 docker 信息", + "i18n.no_corresponding_execution_log.9545":"没有对应的执行日志", + "i18n.no_corresponding_file.97b5":"没有对应文件", + "i18n.no_corresponding_file_colon.8970":"没有对应文件:", + "i18n.no_corresponding_folder.621f":"没有对应文件夹", + "i18n.no_corresponding_node_info.cd24":"没有对应到节点信息", + "i18n.no_corresponding_repository.dde9":"没有对应的仓库", + "i18n.no_corresponding_script_info_or_global_script_selected.765b":"没有对应到脚本信息或者选择全局脚本", + "i18n.no_corresponding_ssh.aa68":"没有对应的ssh", + "i18n.no_corresponding_ssh_info.d864":"没有对应的ssh信息", + "i18n.no_corresponding_ssh_item.2deb":"没有对应的ssh项", + "i18n.no_corresponding_ssh_script_info.1c12":"没有对应的ssh脚本信息", + "i18n.no_corresponding_task.3be5":"没有对应的任务", + "i18n.no_corresponding_workspace_permission.8402":"没有对应的工作空间权限", + "i18n.no_current_data_permission.17d7":"没有当前数据权限,需要管理员或者数据创建人才操作该数据", + "i18n.no_current_static_directory_permission.ed70":"没有当前静态目录权限", + "i18n.no_data.1ac0":"没有数据", + "i18n.no_data.55a2":"没有任何数据", + "i18n.no_data_found.4ffb":"没有对应数据", + "i18n.no_database_config_header_found.9ee3":"没有找到数据库配置标识头", + "i18n.no_deletion_condition.19d0":"没有删除条件", + "i18n.no_description.c231":" 没有描述", + "i18n.no_distribution_exists.4425":"不存在分发", + "i18n.no_distribution_id_found.8df2":"没有找到对应的分发id", + "i18n.no_distribution_project.d4d1":"没有分发项目", + "i18n.no_distribution_project_found.90b0":"没有找到对应的分发项目", + "i18n.no_docker_details.3343":"没有对应到docker信息", + "i18n.no_docker_info.d685":"没有对应 docker", + "i18n.no_docker_info_found.6d38":"没有找到对应的 docker 信息", + "i18n.no_docker_info_no_need_to_fix_machine_data.f45e":"没有任何 DOCKER 信息,不需要修复机器 DOCKER 数据", + "i18n.no_environment_variable.c79f":"没有对应的环境变量", + "i18n.no_environment_variables_found.46ad":"没有找到任何环境变量", + "i18n.no_error_data_in_table.3092":"当前表没有错误数据", + "i18n.no_execution_id.68dc":"没有执行ID", + "i18n.no_file_found.6f1b":"没有找到 {} 文件", + "i18n.no_file_found.7d40":"没有对应到文件", + "i18n.no_file_info.db01":"没有对应的文件信息", + "i18n.no_files_in_project_directory.108e":"项目目录没有任何文件,请先到项目文件管理中上传文件", + "i18n.no_files_in_zip.1af6":"压缩包里没有任何文件", + "i18n.no_get_id_method.2a65":"没有 getId 方法", + "i18n.no_h2_data_info_for_migration.5799":"没有 h2 数据信息不用迁移", + "i18n.no_implemented_feature.af80":"没有实现该功能", + "i18n.no_implemented_publish_distribution.fcf8":"没有实现的发布分发:{}", + "i18n.no_info.e59e":"没有任何信息", + "i18n.no_jpom_type_config_found.aa57":"没有找到 Jpom 类型配置", + "i18n.no_log_file.bacf":"还没有日志文件", + "i18n.no_log_info.d551":"还没有日志信息", + "i18n.no_log_info_or_log_file_error.2c25":"还没有日志信息或者日志文件错误", + "i18n.no_machine.89ed":"没有对应的机器", + "i18n.no_machine_found.c16c":"没有找到对应的机器", + "i18n.no_main_class_found.b001":"没有找到对应的MainClass:", + "i18n.no_management_permission.fd25":"您没有对应管理权限:-2", + "i18n.no_management_permission2.35d4":"您没有对应管理权限:-3", + "i18n.no_manager_node_found.5934":"当前集群未找到任何管理节点", + "i18n.no_matching_data_found.fe9d":"未找到匹配的数据", + "i18n.no_matching_files.b7a6":"{} 没有匹配到任何文件", + "i18n.no_matching_permission.09cf":"未匹配到合适的权限不足", + "i18n.no_matching_process_type.b468":"未匹配到合适的处理类型", + "i18n.no_menus_contact_admin.cfec":"没有任何菜单,请联系管理员", + "i18n.no_node.2e83":"没有对应的节点", + "i18n.no_node_entry_found.b1ef":"没有找到对应的节点项:{}", + "i18n.no_node_found.6f85":"没有找到对应的节点", + "i18n.no_node_info.6366":"没有任何节点信息", + "i18n.no_node_info_no_need_to_fix_machine_data.562e":"没有任何节点信息,不需要修复机器数据", + "i18n.no_node_specified.fa3d":"没有对应的节点:", + "i18n.no_nodes.17b4":"没有任何节点", + "i18n.no_oauth2_found.ea74":"没有找到对应的 oauth2,", + "i18n.no_online_manager_node_found.05d7":"当前集群未找到在线的管理节点", + "i18n.no_parameters_added.1721":"没有添加任何参数", + "i18n.no_parameters_added_with_minus_one.e47d":"没有添加任何参数:-1", + "i18n.no_parameters_added_with_minus_two.a7cf":"没有添加任何参数:-2", + "i18n.no_permission.e343":"您没有对应权限", + "i18n.no_permission_for_function.b63d":"您没有对应功能【{}】管理权限", + "i18n.no_permission_to_execute_command.04d4":"没有执行相关命令权限", + "i18n.no_project_found.ef5e":"没有找到对应的项目", + "i18n.no_project_id_found.0f21":"没有找到对应的项目id:", + "i18n.no_project_info_found.725a":"没有找到对应的项目信息", + "i18n.no_project_specified.0076":"没有对应的项目:", + "i18n.no_project_specified2.a7f5":"没有对应项目:", + "i18n.no_projects_configured.e873":"没有配置任何项目", + "i18n.no_publish_distribution_related_data_id.a077":"没有发布分发对应关联数据ID", + "i18n.no_record.ff41":"没有对应的记录", + "i18n.no_resource_found.dc22":"没有找到对应的资源", + "i18n.no_run_found_in_steps.a141":"steps 中没有发现任何 run , run 用于执行命令", + "i18n.no_script.93c4":"没有对应的脚本", + "i18n.no_script_template_found.0498":"没有找到对应的脚本模板", + "i18n.no_script_template_specified.7d14":"没有对应脚本模板:", + "i18n.no_server_management_permission.ee19":"您没有服务端管理权限:-2", + "i18n.no_shard_id_info.30f8":"没有分片 id 信息", + "i18n.no_ssh_commands_to_execute_after_publish.89ba":"没有需要执行发布后的ssh命令", + "i18n.no_ssh_entry_found.d0e1":"没有找到对应的ssh项:{}", + "i18n.no_ssh_info.a8ec":"没有对应 SSH 信息", + "i18n.no_ssh_info_no_need_to_fix_machine_data.0946":"没有任何ssh信息,不需要修复机器 SSH 数据", + "i18n.no_static_directory_configured.d3c0":"当前没有配置静态目录,自动取消定时任务", + "i18n.no_tag_name.40ff":"没有 tag name", + "i18n.no_trigger_type_specified.5628":"没有对应的触发器类型:", + "i18n.no_type.9153":"没有对应的类型", + "i18n.no_type_specified.8c65":"没有对应类型:", + "i18n.no_uploaded_file.07ef":"没有上传文件", + "i18n.no_user.3b69":"没有对应的用户", + "i18n.no_user_info.0355":"没有对应的用户信息", + "i18n.no_user_specified.6650":"没有对应的用户:", + "i18n.no_workspace_found_for_data.ac0f":"没有找到数据对应的工作空间,不能进行操作", + "i18n.no_workspace_info.75ae":"没有任何工作空间信息", + "i18n.no_workspace_info_contact_admin_for_authorization.825f":"没有任何工作空间信息,请联系管理授权", + "i18n.no_workspace_selected.33d5":"没有选择任何工作空间", + "i18n.node_account_required.2d90":"请填写节点账号", + "i18n.node_address_not_found.f955":"没有节点地址,不能继续操作", + "i18n.node_address_required.71f1":"节点地址不能为空", + "i18n.node_already_exists.28ea":"对应的节点已经存在啦", + "i18n.node_already_exists_in_workspace.9499":"对应工作空间已经存在该节点啦:", + "i18n.node_and_check_project_failed.ac4b":"节点与检查项目失败", + "i18n.node_authorized_config.f934":"节点授权配置", + "i18n.node_authorized_distribution.c5d7":"节点授权分发", + "i18n.node_cache.d68c":"节点缓存", + "i18n.node_communication_failure.00fb":"节点通讯失败,请优先检查限制上传大小配置是否合理,或者网络连接是否被代理终端、防火墙终端等。", + "i18n.node_communication_failure_signal.5aae":"节点通讯失败,远程地址和端口时发生错误的信号。通常,由于中间的防火墙或中间路由器已关闭,无法访问远程主机。", + "i18n.node_connection_failed.8497":"{} 节点连接失败 {}", + "i18n.node_connection_failure.896d":"节点连接失败,请检查节点是否在线", + "i18n.node_connection_failure_message.aacc":"节点连接失败:", + "i18n.node_connection_lost.b6c7":"节点连接丢失或者还没有连接上", + "i18n.node_delete_project_failed.534c":"节点删除项目失败", + "i18n.node_did_not_pull_anything.8af5":"节点没有拉取到任何", + "i18n.node_distribution.ae68":"节点分发", + "i18n.node_does_not_exist.4ce4":"节点不存在", + "i18n.node_exception.bca7":"节点异常:", + "i18n.node_exception_null_pointer.d408":"节点异常,空指针", + "i18n.node_failed.20d5":"节点失败:", + "i18n.node_has_build_items_cannot_delete.a952":"该节点存在构建项,不能", + "i18n.node_has_distribution_projects_cannot_delete.3987":"该节点存在分发项目,不能", + "i18n.node_has_log_search_projects_cannot_delete.a388":"该节点存在日志搜索(阅读)项目,不能", + "i18n.node_has_monitoring_items_cannot_delete.0304":"该节点存在监控项,不能", + "i18n.node_has_no_workspace.69c0":"节点没有工作空间", + "i18n.node_id.c90a":"节点id", + "i18n.node_id_not_found.2f9e":"没有节点id", + "i18n.node_id_required_and_format.5926":"节点id不能为空并且2-50(英文字母 、数字和下划线)", + "i18n.node_info.2dcf":"节点信息", + "i18n.node_info_incomplete.3b69":"对应的节点信息不完整不能继续", + "i18n.node_info_not_found.2c8c":"没有查询到节点信息:", + "i18n.node_machine_table_exists_no_need_to_fix.2625":"节点机器表已经存在 {} 条数据,不需要修复机器数据", + "i18n.node_management.b26d":"节点管理", + "i18n.node_migration_project_failure.d5ff":"节点迁移项目失败", + "i18n.node_name.b178":"节点名称", + "i18n.node_name_required.5bdf":"请填写节点名称", + "i18n.node_name_required.ac0f":"节点名称 不能为空", + "i18n.node_network_connection_exception_or_timeout.5904":"节点网络连接异常或超时,请优先检查插件端运行状态再检查 IP 地址、", + "i18n.node_no_data_pulled.0dae":"{} 节点没有拉取到任何 {},但是删除了数据:{}", + "i18n.node_not_enabled.10ef":"{} 节点未启用", + "i18n.node_not_enabled.a14d":"节点未启用", + "i18n.node_not_exist.0027":"不存在对应的节点", + "i18n.node_not_exist.760e":"对应的节点不存在", + "i18n.node_null_pointer_exception.76fe":"{}节点,程序空指针异常", + "i18n.node_online_upgrade.f144":"节点在线升级", + "i18n.node_plugin_version_required.2318":"node 插件 version 不能为空", + "i18n.node_response_error.efc6":" 节点响应异常,状态码错误:", + "i18n.node_return_info_exception.0961":"节点返回信息异常,请检查节点地址是否配置正确或者代理配置是否正确", + "i18n.node_running_status_abnormal.3160":"【{}】节点的运行状态异常", + "i18n.node_script_template.be6a":"节点脚本模板", + "i18n.node_script_template_execution_record.704a":"节点脚本模版执行记录", + "i18n.node_script_template_log.85e3":"节点脚本模板日志", + "i18n.node_script_template_title.4e74":"节点脚本模版", + "i18n.node_service_not_running.ad89":"【{}】节点的【{}】项目{}已经没有运行", + "i18n.node_service_resumed_normal_operation.2cbd":"【{}】节点的【{}】项目{}已经恢复正常运行", + "i18n.node_service_stopped_abnormal_restart.a5c0":"【{}】节点的【{}】项目{}已经停止,重启操作异常", + "i18n.node_service_stopped_failed_restart.4307":"【{}】节点的【{}】项目{}已经停止,已经执行重启操作,结果失败", + "i18n.node_service_stopped_successful_restart.603b":"【{}】节点的【{}】项目{}已经停止,已经执行重启操作,结果成功", + "i18n.node_statistics.b4e1":"节点统计", + "i18n.node_status_code_abnormal.4d22":"【{}】节点的状态码异常:{}", + "i18n.node_sync_project_failed.a2a7":"节点同步项目失败", + "i18n.node_system_logs.3ac9":"节点系统日志", + "i18n.node_transfer_info_encoding_exception.12c8":"节点传输信息编码异常:", + "i18n.node_upgrade.3bf3":"节点升级", + "i18n.node_upgrade_failed.4493":"节点升级失败:", + "i18n.non_dsl_project_unsupported_operation.5c09":"非 DSL 项目不支持此操作", + "i18n.non_existent_build_product.1df4":"{} 不存在,处理构建产物失败", + "i18n.non_plaintext_variable_cannot_view.50ca":"非明文变量不能查看", + "i18n.normal_end.3bfe":"正常结束", + "i18n.not_an_enumeration.8244":"不是枚举", + "i18n.not_connected.fa55":"还没有连接上", + "i18n.not_jpom_install_package.2cca":"此文件不是 jpom 安装包", + "i18n.not_jpom_package.ea3e":"此包不是Jpom【{}】包", + "i18n.not_logged_in.6605":"没有登录", + "i18n.not_logged_in.c89f":"当前未登录不能操作此数据", + "i18n.not_running.4f8a":"未运行", + "i18n.not_super_admin.962e":"您不是超级管理员没有权限:-2", + "i18n.notice_script_invocation_error.9002":"noticeScript 调用错误", + "i18n.oauth2_binding_warning.d8f0":"当前权限组被 oauth2[{}] 绑定,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.oauth2_login_failure.3841":"OAuth 2 登录失败,平台账号不符合本系统要求", + "i18n.oauth2_not_configured.9c85":"未配置 oauth2", + "i18n.oauth2_not_enabled.c8b7":"没有开启此 {} oauth2", + "i18n.oauth2_redirect_failed.6dcd":"跳转 oauth2 失败,{} {}", + "i18n.old_and_new_passwords_match.55b4":"新旧密码一致", + "i18n.old_password_incorrect.9cf6":"旧密码不正确!", + "i18n.old_version_project_logs_exist_while_running.75ab":"存在旧版项目日志但项目在运行中需要停止运行后手动迁移:{} {}", + "i18n.online_agent_close_not_supported.d81d":"不支持在线关闭 Agent 进程", + "i18n.online_build.6f7a":"在线构建", + "i18n.online_upgrade.da8c":"在线升级", + "i18n.online_upgrade_cannot_downgrade.d419":"在线升级不能降级操作", + "i18n.only_git_repositories_have_branch_info.d7f7":"只有 GIT 仓库才有分支信息", + "i18n.only_tar_files_supported.dcc4":"只支持tar文件", + "i18n.operation_failed.3d94":"操作失败 ", + "i18n.operation_failed_with_details.7280":"操作失败:", + "i18n.operation_file_permission_exception.5a41":"操作文件权限异常,请手动处理:", + "i18n.operation_ip.cbd4":"操作IP", + "i18n.operation_log.cda8":"操作日志", + "i18n.operation_monitoring.0cd5":"操作监控", + "i18n.operation_monitoring_error.8036":"执行操作监控错误", + "i18n.operation_status_code.8231":"操作状态码", + "i18n.operation_succeeded.3313":"操作成功", + "i18n.operation_succeeded_refresh_backup.54a9":"操作成功,请稍后刷新查看备份状态", + "i18n.operation_succeeded_with_details.c773":"操作成功:", + "i18n.operation_time.7e95":"操作时间", + "i18n.operation_type.de9c":"操作类型", + "i18n.operation_user.4c89":"操作用户", + "i18n.oshi_file_system_monitoring_exception.dc24":"oshi 文件系统资源监控异常", + "i18n.oshi_hard_disk_monitoring_exception.c642":"oshi 硬盘资源监控异常", + "i18n.oshi_network_card_monitoring_exception.6d41":"oshi 网卡资源监控异常", + "i18n.oshi_system_monitoring_exception.5c1c":"oshi 系统监控异常", + "i18n.oshi_system_process_monitoring_exception.a4da":"oshi 系统进程监控异常", + "i18n.package_missing_info.e277":"此包没有版本号、打包时间、最小兼容版本", + "i18n.package_product.bfbb":"打包产物", + "i18n.pagination_error.6759":"筛选的分页有问题,当前页码查询不到任何数据", + "i18n.parameter.3d0a":"参数", + "i18n.parameter_error_id_cannot_be_empty.86cc":"参数错误id不能为空", + "i18n.parameter_error_id_error.58ce":"参数错误id error", + "i18n.parameter_error_path_error.f482":"参数错误path错误", + "i18n.parameter_error_port_error.810d":"参数错误port错误", + "i18n.parameter_error_ssh_name_cannot_be_empty.ff4f":"参数错误ssh名称不能为空", + "i18n.parameter_error_user_cannot_be_empty.9239":"参数错误user不能为空", + "i18n.parameter_parsing_exception.0056":"参数解析异常:{}", + "i18n.parameter_validation_failed.f0a1":"参数验证失败", + "i18n.parameter_value_required.3a29":"请填写参数值", + "i18n.parent_table_info_config_error.2f52":"父级表信息配置错误,", + "i18n.parent_task_not_exist.ca1b":"父任务不存在", + "i18n.parent_task_not_found.bac1":"没有找到父级任务", + "i18n.parse_certificate_exception.3b6c":"解析证书异常", + "i18n.parse_certificate_unknown_error.c43c":"解析证书发生未知错误:", + "i18n.parse_csv_exception.885e":"解析 csv 异常", + "i18n.parse_error.da6d":" 解析错误:", + "i18n.parse_file_exception.374d":"解析文件异常,", + "i18n.parse_jar.a26e":"解析jar", + "i18n.parse_project_csv_exception.ece1":"解析项目 csv 异常", + "i18n.parse_system_start_time_error.112c":" 解析系统启动时间错误:", + "i18n.password_cannot_be_empty.89b5":"密码不能为空", + "i18n.password_change_success.8013":"修改密码成功!", + "i18n.permission_distribution_config_error.e7fb":"权限分发配置错误:{} {}", + "i18n.permission_distribution_config_error_class_not_found.ca67":"权限分发配置错误:{} {} class not find", + "i18n.permission_function_not_configured_correctly.84dd":"权限功能没有配置正确 {}", + "i18n.permission_group.ea59":"权限分组", + "i18n.physical_node_pull.874e":"{} 物理节点拉取到 {} 个{},当前工作空间逻辑节点已经缓存 {} 个{},更新 {} 个{},删除 {} 个缓存", + "i18n.physical_node_pull_records.99df":"{} 物理节点拉取到 {} 个执行记录,更新 {} 个执行记录", + "i18n.please_check_in_time.3b4f":"请及时检查", + "i18n.please_do_not_delete_this_file.0a7f":"请勿删除此文件,删除后关联 id 将失效", + "i18n.please_fill_in_address_of.9e02":"请填写 %s 的 地址", + "i18n.please_fill_in_correct_go_version.44ed":"请填入正确的 go 版本号", + "i18n.please_fill_in_correct_gradle_version.6e19":"请填入正确的 gradle 版本号", + "i18n.please_fill_in_correct_maven_version.468c":"请填入正确的 maven 版本号,可用的版本如下:", + "i18n.please_fill_in_correct_node_version.8483":"请填入正确的 node 版本号", + "i18n.please_fill_in_correct_python3_version.abb1":"请填入正确的 python3 版本号", + "i18n.please_fill_in_from.7268":"请填写from", + "i18n.please_fill_in_host.7922":"请填写host", + "i18n.please_fill_in_information_and_check_validity.771a":"请填写信息,并检查是否填写合法", + "i18n.please_fill_in_ipv4_address.d23a":"请填写 ipv4 地址:", + "i18n.please_fill_in_name.52f3":"请填写 名称", + "i18n.please_fill_in_node_address.e77e":"请填写 节点地址", + "i18n.please_fill_in_password.455f":"请填写pass", + "i18n.please_fill_in_personal_token.970a":"请填写个人令牌", + "i18n.please_fill_in_repository_address.0cf8":"请填写仓库地址", + "i18n.please_fill_in_repository_name.9f0d":"请填写仓库名称", + "i18n.please_fill_in_runs_on.c2ff":"请填写runsOn。", + "i18n.please_fill_in_steps.229d":"请填写 steps", + "i18n.please_fill_in_user.5f52":"请填写user", + "i18n.please_pass_body_parameter.4e5c":"请传入 body 参数", + "i18n.please_pass_parameter.3182":"请传入参数", + "i18n.plugin_connection_failed.02a1":"插件端连接失败", + "i18n.plugin_end_log_connection_successful.9035":"连接成功:插件端日志", + "i18n.plugin_not_found.a6e5":"对应找到对应到插件:", + "i18n.plugin_not_initialized.3ea9":"对应的插件端还没有被初始化", + "i18n.plugin_parameter_incorrect.a355":"插件端使用参数不正确", + "i18n.plugin_system_log.955c":"插件端系统日志", + "i18n.port_configuration_check.d888":"端口号是否配置正确,防火墙规则,", + "i18n.port_error.312e":"端口错误", + "i18n.port_field_required_or_incorrect.8426":"第 {} 行 port 字段不能位空或者不正确", + "i18n.post_distribution_action_required.8cc8":"请选择分发后的操作", + "i18n.post_packaging_action_required.bf66":"请选择打包后的操作", + "i18n.prepare_backup.7970":"开始准备备份项目文件:{} {}", + "i18n.prepare_restart.8251":"开始准备项目重启:{} {}", + "i18n.prepare_rollback.dba6":"开始准备回滚:{} -> {}", + "i18n.prepare_to_build.1830":"准备构建", + "i18n.prepare_to_delete_current_database_file.1e6a":"准备删除当前数据库文件", + "i18n.prepare_to_migrate_data.f251":"准备迁移数据", + "i18n.previous_node_distribution_failure.d556":"前一个节点分发失败,取消分发", + "i18n.privacy_variable_cannot_trigger.dbc9":"隐私变量不能生成触发器", + "i18n.private_file_not_found.ee45":"没有隐私文件", + "i18n.private_key_file_not_exist.49ed":"配置的私钥文件不存在", + "i18n.private_key_file_not_found.4ad9":"私钥文件不存在:", + "i18n.private_key_not_found_in_zip.e103":"压缩包里没有找到私钥文件", + "i18n.process_does_not_exist.4e39":"流程不存在", + "i18n.process_file_deletion_exception.1c6e":"处理文件删除异常", + "i18n.process_file_event_exception.e8e6":"处理文件事件异常", + "i18n.process_killed_successfully.a4c3":"成功kill", + "i18n.product_directory_cannot_skip_levels.3ad4":"产物目录不能越级:", + "i18n.product_file_does_not_exist.ee13":"产物文件不存在", + "i18n.program_already_running.96e1":"当前程序正在运行中,不能重复启动,PID:", + "i18n.program_error_null_pointer.12e1":"程序错误,空指针", + "i18n.project_associated_with_other_workspaces_message.299c":"当前项目已经被其他工作空间关联,请检查确认后再操作或者使用孤独数据修正", + "i18n.project_console.3a94":"项目控制台", + "i18n.project_data_lost.2ae3":"项目数据丢失", + "i18n.project_data_workspace_id_inconsistency.7ed6":"项目数据工作空间ID[{}]查询出节点ID不一致, 旧数据: {}, 新数据: {}", + "i18n.project_disabled.f8b3":"当前项目被禁用", + "i18n.project_does_not_exist.3029":"项目不存在", + "i18n.project_exists.f4e0":"该节点下还存在项目,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.project_file_manager.c8cb":"项目文件管理", + "i18n.project_has_build_items_cannot_delete.c2df":"当前项目存在构建项,不能直接删除", + "i18n.project_has_logs_cannot_delete.1d2a":"当前项目存在日志阅读,不能直接删除", + "i18n.project_has_logs_cannot_migrate.2e0e":"当前项目存在日志阅读,不能直接迁移", + "i18n.project_has_monitoring_items_cannot_delete.c9a3":"当前项目存在监控项,不能直接删除", + "i18n.project_has_monitoring_items_cannot_migrate.c7f6":"当前项目存在监控项,不能直接迁移", + "i18n.project_has_node_distribution_cannot_delete.41b0":"当前项目存在节点分发,不能直接删除", + "i18n.project_has_node_distribution_cannot_migrate.cc0e":"当前项目存在节点分发,不能直接迁移", + "i18n.project_id_cannot_contain_spaces.251d":"项目Id不能包含空格", + "i18n.project_id_does_not_exist.6b9b":"项目id不存在", + "i18n.project_id_in_use.1adb":"当前项目id已经被正在运行的程序占用", + "i18n.project_id_keyword_occupied.1cae":"项目id {} 关键词被系统占用", + "i18n.project_id_length_range.7064":"项目id 长度范围2-20(英文字母 、数字和下划线)", + "i18n.project_id_not_found.b87e":"没有项目id", + "i18n.project_info.6674":"项目信息", + "i18n.project_is_not_node_distribution_project_cannot_delete.2a5a":"该项目不是节点分发项目,不能在此次删除", + "i18n.project_log.2926":"项目日志", + "i18n.project_log_is_existing_folder.a80a":"项目log是一个已经存在的文件夹", + "i18n.project_log_storage_path_required.d0bb":"请填写的项目日志存储路径,或者还没有配置授权", + "i18n.project_management.4363":"项目管理", + "i18n.project_monitor.d2ff":"项目监控", + "i18n.project_name.31ec":"项目", + "i18n.project_operations.03d9":"项目运维", + "i18n.project_path_already_exists_as_file.a900":"项目路径是一个已经存在的文件", + "i18n.project_path_auth_not_under_jpom.0e18":"项目路径授权不能位于Jpom目录下", + "i18n.project_path_auth_required.9e58":"项目路径授权不能为空", + "i18n.project_path_conflict.8c6f":"项目路径和【{}】项目冲突:{}", + "i18n.project_path_no_spaces.263c":"项目路径不能包含空格", + "i18n.project_path_occupied.cddd":"当前项目路径已经被【{}】占用,请检查", + "i18n.project_path_promotion_issue.2250":"项目路径存在提升目录问题", + "i18n.project_soft_linked_by.8556":"项目被{}软链中", + "i18n.project_type_not_supported_for_startup.7bd1":"当前项目类型不支持启动", + "i18n.protocol_field_required.7cc2":"第 {} 行 protocol 字段不能位空", + "i18n.protocol_field_value_error.2b41":"第 {} 行 protocol 字段值错误(http/http/ssh)", + "i18n.protocol_not_supported.b906":"不支持的 protocol", + "i18n.protocol_required.b4f8":"请选择协议", + "i18n.protocol_type_not_supported.7a66":"不支持到协议类型", + "i18n.protocol_type_not_supported2.e519":"不支持的协议类型", + "i18n.public_key_and_private_key_mismatch.4aa2":"公钥和私钥不匹配", + "i18n.public_key_or_private_key_does_not_exist.dc0d":"公钥或者私钥不存在", + "i18n.publish_command_contains_forbidden_command.097d":"发布命令中包含禁止执行的命令", + "i18n.publish_command_length_limit.66b0":"发布命令长度限制在4000字符", + "i18n.publish_command_non_zero_exit_code.ea80":"执行发布命令退出码非0,{}", + "i18n.publish_directory_is_empty.79c6":"发布目录为空", + "i18n.publish_exception.cf0b":"执行发布异常", + "i18n.publish_file_second_level_directory_required.2f65":"请填写发布文件的二级目录", + "i18n.publish_method_format.4622":"发布的方式:{}", + "i18n.publish_product.5925":"发布产物", + "i18n.publish_project_package_failed.9514":"发布项目包失败:", + "i18n.publish_project_package_success.b0ce":"发布项目包成功:{}", + "i18n.publish_script_exit_code.0f69":"执行发布脚本的退出码是:{}", + "i18n.publish_success.2fff":"发布成功", + "i18n.publish_task_execution_exception.c296":"执行发布任务异常", + "i18n.publish_task_execution_failed.b075":"执行发布任务失败", + "i18n.publish_to_ssh_directory_required.56a6":"请输入发布到ssh中的目录", + "i18n.pull_code_exception_with_cleanup.a887":"拉取代码异常,已经主动清理本地仓库缓存内容,请手动重试。", + "i18n.pull_code_failed.70d6":"拉取代码失败:{}", + "i18n.pull_exception.b38d":"拉取异常", + "i18n.pull_log_exception.cc3e":"拉取日志异常", + "i18n.pull_repository_code.3f51":"拉取仓库代码", + "i18n.push_image_container_exception.2090":"推送镜像调用容器异常", + "i18n.push_image_interrupted.6377":"push image 被中断:", + "i18n.push_registration_to_server_failed.5949":"向服务端推送注册失败 {}", + "i18n.python3_plugin_version_required.a0ce":"python3 插件 version 不能为空", + "i18n.query_data_error.45e7":"查询数据错误", + "i18n.query_folder_failed.3f0e":"查询文件夹失败,", + "i18n.query_folder_sftp_failed.9d35":"查询文件夹 SFTP 失败,", + "i18n.query_success.d72b":"查询成功", + "i18n.query_workspace_error.6a0d":"查询错误的工作空间失败", + "i18n.range_format_not_supported.d69e":"不支持的 range 格式 ", + "i18n.read_additional_variables.5eb0":"读取附加变量:{} {}", + "i18n.read_error.7fa5":"读取错误", + "i18n.read_global_script_file_error.0d4c":"读取全局脚本文件失败", + "i18n.read_system_parameter_exception.ee72":"读取系统参数异常", + "i18n.rebuild_success.5938":"重建成功", + "i18n.reconnect_failure.7c01":"重连失败", + "i18n.reconnect_plugin_failure.cc6c":"重连插件端失败", + "i18n.reconnect_plugin_failure_after_upgrade.73e3":"升级后重连插件端失败:", + "i18n.record_operation_log_exception.8012":"记录操作日志异常", + "i18n.recover_abnormal_data.9adf":"{} 恢复 {} 条异常数据", + "i18n.refresh_token_failure.de7f":"刷新token失败", + "i18n.refresh_token_timeout.3291":"刷新token超时", + "i18n.refreshing_cache.c969":"正在刷新缓存中,请勿重复刷新", + "i18n.release_node_project_failed.764e":"释放节点项目失败:", + "i18n.release_successful.f2ca":"释放成功", + "i18n.reload_project_exception.b566":"重载项目异常", + "i18n.remote_addresses_not_configured.275e":"还没有配置允许的远程地址", + "i18n.remote_download_host_cannot_be_empty.cdf5":"运行远程下载的 host 不能配置为空", + "i18n.remote_download_url.011f":"{} 远程下载 url:{}", + "i18n.remote_repository_does_not_exist.7009":"当前地址远程不存在仓库:", + "i18n.rename_failed.0c76":"重命名失败:", + "i18n.repo_already_exists.38a3":"已经存在对应的仓库信息啦", + "i18n.repository_authorization_error.4f50":"仓库授权信息错误", + "i18n.repository_does_not_exist.3cdb":"仓库不存在", + "i18n.repository_id_cannot_be_empty.a42c":"仓库ID不能为空", + "i18n.repository_import_template.5e2d":"仓库信息导入模板.csv", + "i18n.repository_info.22cd":"仓库信息", + "i18n.repository_info_cannot_be_empty.67d2":"仓库信息不能为空", + "i18n.repository_info_does_not_exist.4142":"仓库信息不存在", + "i18n.repository_info_error.5b0a":"第 {} 行 仓库信息有误", + "i18n.repository_key_file_does_not_exist_or_is_abnormal.1d78":"仓库密钥文件不存在或者异常,请检查后操作", + "i18n.repository_password_cannot_be_empty.20b3":"仓库密码不能为空", + "i18n.repository_type_required.9414":"请选择仓库类型", + "i18n.republishing.131d":"重新发布中", + "i18n.request_failed_message.9c71":"请求失败: status: %s body: %s headers: %s", + "i18n.request_needs_decoding.d4d7":"当前请求需要解码:{}", + "i18n.request_type_not_supported_for_decoding.ea2e":"当前请求类型不支持解码:{}", + "i18n.reset_failed.5281":"重置失败:", + "i18n.reset_log_failure.b3d3":"重置日志失败", + "i18n.reset_success.faa3":"重置成功", + "i18n.reset_super_admin_password_success.50c6":"重置超级管理员账号密码成功,登录账号为:{} 新密码为:{}", + "i18n.response_exception_status_code.cbca":"{} 响应异常 状态码错误:{} {}", + "i18n.restart_completed.42b8":"重启完成", + "i18n.restart_failed.f92a":"重启失败", + "i18n.restart_operation.5e3a":"执行重启操作", + "i18n.restart_result.253f":"重启结果:", + "i18n.restart_self_exception.85b7":"重启自身异常", + "i18n.restore_authorization_data_exception.015a":"恢复授权数据异常或者没有选择授权目录", + "i18n.restore_backup_data_failed.58af":"还原备份数据失败", + "i18n.restore_backup_data_success.253a":"还原备份数据成功", + "i18n.restore_project_failed.7f7c":"还原项目失败", + "i18n.restore_success.4c7f":"还原成功", + "i18n.result_dir_file_required.5f02":"resultDirFile 不能为空", + "i18n.retention_days.3c7d":",保留天数:{}", + "i18n.rollback_ended.fb1d":"执行回滚结束:{}", + "i18n.root_path.1396":"根路径", + "i18n.rsa_private_key_file_error.b687":"第 {} 行 rsa 私钥文件不存在或者有误", + "i18n.rsa_private_key_file_invalid.5f12":"rsa 私钥文件不存在或者有误", + "i18n.run_status_not_configured.e959":"没有配置 run.status", + "i18n.running_node_cannot_be_empty.ffc6":"运行节点不能为空", + "i18n.running_project_cannot_change_path.5888":"正在运行的项目不能修改路径", + "i18n.running_status.d679":"运行中", + "i18n.same_distribution_project_exists.ff41":"已经存在相同的分发项目:", + "i18n.save_distribution_project_failed.ceec":"保存分发项目失败", + "i18n.save_node_data_failed.f314":"保存节点数据失败:", + "i18n.save_succeeded.3b10":"保存成功", + "i18n.scan_succeeded.7975":"扫描成功", + "i18n.scanning_in_progress.7444":"当前正在扫描中", + "i18n.scheduled_backup_log_failure.a0d7":"定时备份日志失败", + "i18n.scheduled_task_exception.f077":"定时任务异常 {}", + "i18n.script_cannot_be_empty.f566":"脚本不能为空", + "i18n.script_content_cannot_be_empty.49be":"脚本内容不能为空", + "i18n.script_exit_code.716e":"执行脚本的退出码是:{}", + "i18n.script_info_not_found.bd8d":"找不到对应的脚本信息", + "i18n.script_library.aed1":"脚本库", + "i18n.script_not_bound_to_ssh_node.3459":"当前脚本未绑定 SSH 节点,不能使用触发器执行", + "i18n.script_not_exist.b180":"对应脚本已经不存在啦", + "i18n.script_tag_modification_not_allowed.cb75":"脚本标记不能修改", + "i18n.script_template.1f77":"脚本模版", + "i18n.script_template.54f2":"脚本模板", + "i18n.script_template_execution_record.374b":"脚本模版执行记录", + "i18n.script_template_exists.3f86":"该节点下还存在脚本模版,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.script_template_id_required.f339":"请填写脚本模板id", + "i18n.script_template_log.30cb":"脚本模板日志", + "i18n.script_template_log2.6b2c":"脚本模版日志", + "i18n.script_template_not_exist.1d5b":"对应的脚本模版已经不存在拉", + "i18n.script_template_not_exist.e05f":"脚本模版不存在:", + "i18n.search_project.7e9b":"搜索项目", + "i18n.search_result_display.d2c3":"在 {} 行中搜索到并显示 {} 行", + "i18n.second_level_directory_cannot_skip_levels.c9fb":"二级目录不能越级:", + "i18n.secondary_directory_match.0aec":"{} 二级目录模糊匹配到 {} 个文件, 当前文件保留方式 {}", + "i18n.security_warning_h2_console.4669":"【安全警告】数据库账号密码使用默认的情况下不建议开启 h2 数据 web 控制台", + "i18n.select_alarm_contact.d02a":"请选择报警联系人", + "i18n.select_at_least_one_node_project.637c":"至少选择1个节点项目", + "i18n.select_cluster.f8c3":"请选择集群", + "i18n.select_correct_build_method.84c4":"请选择正确的构建方式", + "i18n.select_correct_log_path_or_no_auth_configured.9a9b":"请选择正确的日志路径,或者还没有配置授权", + "i18n.select_correct_node.1b4e":"请选择正确的节点", + "i18n.select_correct_post_publish_script.49d2":"请选择正确的发布后脚本", + "i18n.select_correct_pre_publish_script.d230":"请选择正确的发布前脚本", + "i18n.select_correct_project_path_or_no_auth_configured.366a":"请选择正确的项目路径,或者还没有配置授权", + "i18n.select_correct_script.ff2d":"请选择正确的脚本", + "i18n.select_correct_ssh.aa93":"请选择正确的ssh", + "i18n.select_file.9feb":"请选择文件", + "i18n.select_file_to_delete.33d6":"请选择要删除的文件", + "i18n.select_folder_to_compress.915f":"请选择文件夹进行压缩", + "i18n.select_monitoring_function.c6e4":"请选择监控的功能", + "i18n.select_monitoring_operation.3057":"请选择监控的操作", + "i18n.select_monitoring_person.0756":"请选择监控人员", + "i18n.select_node.f8a6":"请选择节点", + "i18n.select_node_and_project.6021":"请选择节点和项目", + "i18n.select_node_error.dc0f":"选择节点错误", + "i18n.select_node_to_modify.6617":"请选择要修改的节", + "i18n.select_operation_type.63c6":"请选择操作类型", + "i18n.select_pull_code_protocol.fc24":"请选择拉取代码的协议", + "i18n.select_run_mode.5a5d":"请选择运行模式", + "i18n.select_workspace_error.426e":"选择工作空间错误", + "i18n.select_workspace_to_modify.ac87":"请选择要修改的工作空间", + "i18n.selected_node_required.d65a":"至少选择一个节点", + "i18n.selected_user_status_abnormal.efcf":"选择的用户状态异常", + "i18n.selected_weekday_incorrect.4cd4":"选择的周几不正确", + "i18n.send_alert_error.cd38":"发送报警信息错误", + "i18n.send_alert_notification_exception.6788":"发送报警通知异常", + "i18n.send_email_failure.1ab3":"发送邮件失败:", + "i18n.send_failed.9ca6":"发送失败", + "i18n.send_message_exception.7817":"发消息异常", + "i18n.send_message_failure.9621":"发送消息失败", + "i18n.send_message_failure_prefix.6f8c":"发送消息失败:", + "i18n.send_success.9db9":"发送成功", + "i18n.server_captcha_available.5570":"当前服务器验证码可用", + "i18n.server_captcha_generation_exception.54d0":"当前服务器生成验证码异常,自动禁用验证码", + "i18n.server_exception_occurred.9eb4":"服务端发生异常", + "i18n.server_jps_command_exception.e380":"当前服务器 jps 命令异常,请检查 jdk 是否完整,以及 java 环境变量是否配置正确", + "i18n.server_script_not_exist.de24":"不存在对应的服务端脚本,请重新选择", + "i18n.server_system_config.3181":"服务端系统配置", + "i18n.service_exception.3821":"服务异常:", + "i18n.service_info_incomplete.968d":"服务信息不完整不能操作", + "i18n.service_info_incomplete_with_code1.30f4":"服务信息不完整不能操作:-1", + "i18n.service_info_incomplete_with_code2.e9ca":"服务信息不完整不能操作:-2", + "i18n.service_info_incomplete_with_code3.8612":"服务信息不完整不能操作:-3", + "i18n.service_name_in_cluster_required.5446":"请填写集群中的服务名", + "i18n.session_already_closed.8dcc":"会话已经关闭啦,不能发送消息:{}", + "i18n.session_closed_reason.103a":"会话[{}]关闭原因:{}", + "i18n.socket_error.18c1":"socket 错误", + "i18n.socket_exception.d836":"socket 异常", + "i18n.socket_session_establishment_failed.4924":"socket 会话建立失败,授权信息错误", + "i18n.soft_link_project_department_exists.fa97":"软链的项目部存在", + "i18n.soft_link_project_does_not_exist.4e4f":"软链项目已经不存在啦", + "i18n.soft_link_project_does_not_exist.8ad2":"被软链的项目已经不存在啦,", + "i18n.soft_link_project_mode_error.ffa0":"被软链的项目不能是File或Link模式", + "i18n.sql_statement_too_long.38d6":"sql 语句太长啦", + "i18n.ssh_already_bound_to_other_node.2d4e":"对应的SSH已经被其他节点绑定啦", + "i18n.ssh_already_exists_in_workspace.569e":"对应工作空间已经存在该 ssh 啦:", + "i18n.ssh_already_exists_with_message.d284":"对应的SSH已经存在啦", + "i18n.ssh_asset_management.3b6c":"SSH资产管理", + "i18n.ssh_authorization_directory_cannot_be_root.8125":"ssh 授权目录不能是根目录", + "i18n.ssh_batch_command_execution_exception.029a":"ssh 批量执行命令异常", + "i18n.ssh_bound_to_node_message.7b64":"当前ssh被节点绑定,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.ssh_command_log.7fd1":"SSH命令日志", + "i18n.ssh_command_management.c40a":"SSH命令管理", + "i18n.ssh_connection_failed.4719":"ssh连接失败", + "i18n.ssh_connection_failed.74ab":"ssh连接失败,请检查用户名、密码、host、端口等填写是否正确,超时时间是否合理:", + "i18n.ssh_connections_warning.1ddb":"当前机器SSH还关联{}个ssh,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.ssh_console_connection_timeout.8eb3":"ssh 控制台连接超时", + "i18n.ssh_data_repair_not_needed.203f":"机器 SSH 表已经存在 {} 条数据,不需要修复机器 SSH 数据", + "i18n.ssh_does_not_exist.5bec":"对应的 SSH 不存在", + "i18n.ssh_does_not_exist.88d7":"ssh 不存在", + "i18n.ssh_does_not_exist_with_message.de6c":"对应的 ssh 不存在", + "i18n.ssh_error_or_folder_not_configured.c087":"ssh error 或者 没有配置此文件夹", + "i18n.ssh_error_string.6bdb":"ssh 错误:{}", + "i18n.ssh_file_deletion_exception.5ba5":"ssh删除文件异常", + "i18n.ssh_file_manager.1482":"SSH文件管理", + "i18n.ssh_file_upload_exception.5c1c":"ssh上传文件异常", + "i18n.ssh_folder_creation_exception.6ed2":"ssh创建文件夹异常", + "i18n.ssh_import_template_csv.14fa":"ssh导入模板.csv", + "i18n.ssh_info.ebe6":"SSH 信息", + "i18n.ssh_info_does_not_exist.5ed0":"ssh 信息不存在啦", + "i18n.ssh_item_distribution_required.2884":"请选择分发SSH项", + "i18n.ssh_management.9e0f":"SSH管理", + "i18n.ssh_modify_permission_error.0cd3":"ssh修改文件权限异常...: {} {}", + "i18n.ssh_monitor_execution_error.2d3c":"{} ssh 监控执行存在异常信息:{}", + "i18n.ssh_monitor_info_result.a660":"{} ssh 监控信息结果:{} {}", + "i18n.ssh_node_required.4566":"请选择 ssh 节点", + "i18n.ssh_not_exist.08a2":"SSH不存在", + "i18n.ssh_not_exist.2e40":"不存在对应ssh", + "i18n.ssh_rename_failed_exception.94aa":"ssh重命名失败异常", + "i18n.ssh_script_batch_trigger_exception.70e1":"SSH 脚本批量触发异常", + "i18n.ssh_server_alive_interval_config_error.1f11":"配置 ssh serverAliveInterval 错误", + "i18n.ssh_terminal.ec50":"SSH终端", + "i18n.ssh_terminal_execution_log.58f1":"ssh 终端执行日志", + "i18n.ssh_terminal_log.775f":"SSH终端日志", + "i18n.ssh_unauthorized_directory.df78":"此ssh未授权操作此目录", + "i18n.ssh_with_build_items_message.0f6d":"当前ssh存在构建项,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.ssl_connection_failed.e26c":"SSL 无法连接(请检查证书信任的地址和配置的 docker host 是否一致):", + "i18n.start_async_download.78cc":"开始异步下载", + "i18n.start_building.1039":"开始构建中", + "i18n.start_building_with_number_and_path.c41c":"开始构建 #{} 构建执行路径 : {}", + "i18n.start_building_with_thread_execution.83cd":"开始构建,构建线程执行", + "i18n.start_checking_backup_project_files.baa7":"开始检查备份项目文件:{} {}", + "i18n.start_deleting_files.210c":"开始删除 {} 文件 {}", + "i18n.start_distribution.bce5":"开始分发", + "i18n.start_distribution_exclamation.9fc2":"开始分发!", + "i18n.start_distribution_with_count.cdc7":"开始分发,需要分发 {} 个项目", + "i18n.start_executing.f0b9":"开始执行: {}", + "i18n.start_executing_build_task.a5ac":"开始执行构建任务,任务等待时间:{}", + "i18n.start_executing_database_event.fc57":"开始执行数据库事件:{}", + "i18n.start_executing_distribution_package.a2cc":"开始执行分发包啦,请到分发中查看详情状态", + "i18n.start_executing_event_script.377e":"开始执行事件脚本: {}", + "i18n.start_executing_post_release_command.fd06":"开始执行 {} 发布后命令", + "i18n.start_executing_pre_release_command.6c7e":"开始执行 {} 发布前命令", + "i18n.start_executing_process.9cb8":"开始执行 {}流程", + "i18n.start_executing_publishing_with_file_size.5039":"开始执行发布,需要发布的文件大小:{}", + "i18n.start_executing_upload_post_command.1c1b":"开始执行上传后命令", + "i18n.start_executing_upload_pre_command.fb5c":"开始执行上传前命令", + "i18n.start_execution.00d7":"开始执行", + "i18n.start_migrating.20d6":"开始迁移 {} {}", + "i18n.start_migrating_h2_data_to.f478":"开始迁移 h2 数据到 {}", + "i18n.start_publishing.c0b9":"开始发布中", + "i18n.start_publishing_file.a14e":"开始发布文件", + "i18n.start_pulling.57ab":"开始拉取", + "i18n.start_queuing_for_execution.7417":"开始排队等待执行", + "i18n.start_rolling_back.f020":"开始回滚:{}", + "i18n.start_rolling_back_execution.a019":"开始回滚执行", + "i18n.start_syncing_to_file_management_center.0a03":"开始同步到文件管理中心{}", + "i18n.start_waiting_for_data_migration.e76f":"开始等待数据迁移", + "i18n.static_directory_auth_cannot_be_empty.2cb2":"静态目录授权不能为空", + "i18n.static_directory_auth_cannot_be_under_jpom.8879":"静态目录授权不能位于Jpom目录下", + "i18n.static_directory_cannot_contain_relation.1a90":"静态目录中不能存在包含关系:", + "i18n.static_directory_not_configured.9bd6":"还未配置静态目录", + "i18n.static_directory_not_configured.acbc":"未配置静态目录", + "i18n.static_file_management.6ac2":"静态文件管理", + "i18n.static_file_scanning_disabled.2b2b":"未开启静态文件扫描", + "i18n.static_file_storage.35f6":"静态文件存储", + "i18n.static_file_task_load_failure.b995":"静态文件任务加载失败", + "i18n.status_not_distributing.6298":"当前状态不是分发中", + "i18n.status_not_in_progress.f410":"当前状态不在进行中,", + "i18n.stop_running.1d4e":"停止运行", + "i18n.strict_execution_mode_event_script_error.c82a":"严格执行模式,事件脚本返回状态码异常,", + "i18n.strict_mode_image_build_failure.ecea":"严格模式下镜像构建失败,终止任务", + "i18n.submit_task_queue_success.5f5b":"提交任务队列成功,当前队列数:", + "i18n.subnet_mask_incorrect.6c27":"子掩码不正确:", + "i18n.suffix_cannot_be_empty.ec72":"允许编辑的文件后缀不能为空", + "i18n.super_admin_cannot_reset_password_this_way.0761":"超级管理员不能通过此方式重置密码", + "i18n.super_admin_mfa_verification_disabled.b97d":"成功关闭超级管理员账号 mfa 验证:{} ", + "i18n.supported_comparison_operators_message.6d7a":"表达式目前仅支持 == 和 != 比较", + "i18n.supported_java_plugin_versions.bd70":"目前java 插件支持的版本: %s", + "i18n.synchronization_failed.091a":"{} 同步失败 {}", + "i18n.synchronization_failed.d610":"同步失败", + "i18n.synchronization_node_failure.8a2c":"同步节点 {} 失败 {}", + "i18n.synchronization_node_failure_with_details.8660":"同步节点{}失败:{}", + "i18n.synchronization_script_exception.9c70":"同步脚本异常", + "i18n.synchronize_project_files_failed.6aa4":"同步项目文件失败:", + "i18n.system_IP_authorization.9c08":"系统配置IP授权", + "i18n.system_admin_not_found.6f6c":"没有找到系统管理员", + "i18n.system_administrator.181f":"系统管理员", + "i18n.system_already_initialized.743c":"系统已经初始化过啦,请勿重复初始化", + "i18n.system_cache.c4a8":"系统缓存", + "i18n.system_cancel.3df2":"系统取消", + "i18n.system_configuration_directory.0f82":"系统配置目录", + "i18n.system_error.9417":"系统错误!", + "i18n.system_interruption.e37c":"系统中断异常", + "i18n.system_logs.84aa":"系统日志", + "i18n.system_parameters.c7b0":"系统参数", + "i18n.system_process_monitoring_error.fe1d":"系统进程监控异常:", + "i18n.system_restart_cancel_download.444e":"系统重启取消下载任务", + "i18n.system_task_execution_exception.d559":"执行系统任务异常", + "i18n.table_error_workspace_data.9021":"表 {}[{}] 存在 {} 条错误工作空间数据 -> {}", + "i18n.table_info_configuration_error.b050":"表信息配置错误", + "i18n.table_info_configuration_error_message.6452":"表信息配置错误,", + "i18n.table_without_primary_key.7392":"表没有主键", + "i18n.tag_cannot_contain_colon.f9ae":"标签不能包含 :", + "i18n.target_database_info_not_specified.2ff6":"未指定目标数据库信息", + "i18n.target_workspace_consistency.e04c":"目标工作空间与当前工作空间一致并且目标节点与当前节点一致", + "i18n.task_already_exists.f59a":"任务已经存在啦", + "i18n.task_ended.b341":"任务结束:", + "i18n.task_ended_successfully.e176":"任务正常结束", + "i18n.task_not_exist.47e9":"不存在对应的任务", + "i18n.temporary_result_file_does_not_exist.1c7e":"临时结果文件不存在: {}", + "i18n.test_result.8441":"测试结果:{} {}", + "i18n.token_invalid_or_expired.cb96":"token错误,或者已经失效:-1", + "i18n.token_parse_failed.cadf":"token 解析失败:", + "i18n.too_many_attempts.d88d":"尝试次数太多,请稍后再来", + "i18n.trigger_auto_execute_command_template_exception.4e01":"触发自动执行命令模版异常", + "i18n.trigger_auto_execute_server_script_exception.8e84":"触发自动执行服务器脚本异常", + "i18n.trigger_auto_execute_ssh_command_template_exception.7451":"触发自动执行SSH命令模版异常", + "i18n.trigger_exception.d624":"触发异常", + "i18n.trigger_project_reload_event.a7dc":"触发项目 reload 事件:{}", + "i18n.trigger_result.364e":"[{}]-{}触发器结果:{}", + "i18n.trigger_success.f9d1":"触发成功", + "i18n.trigger_token.abe6":"触发器 token", + "i18n.trigger_token_error_or_expired.8976":"触发token错误,或者已经失效", + "i18n.trigger_token_error_or_expired_with_code.393b":"触发token错误,或者已经失效:-1", + "i18n.trim_completed_with_recovered_space.0463":"修剪完成,总回收空间:", + "i18n.two_step_verification_code_required.7e86":"请输入两步验证码", + "i18n.type_error.395f":"类型错误", + "i18n.type_field_required.7637":"第 {} 行 type 字段不能位空", + "i18n.type_field_value_error.14cf":"第 {} 行 type 字段值错误(Git/Svn)", + "i18n.type_not_exist_error.09de":"ERROR:类型不存在", + "i18n.unable_to_access_node_network.4e09":"无法访问节点网络(未知的名称或服务),请检查主机名或者 DNS 是否可用。", + "i18n.unable_to_connect_to_docker.2bb3":"无法连接 docker 请检查 host 或者 TLS 证书 以及仓库信息配置是否正确。", + "i18n.unable_to_connect_to_repository.52df":"无法连接此仓库,", + "i18n.unable_to_get_container_execution_result_file.7b2c":"无法获取容器执行结果文件", + "i18n.unbind.6633":"解绑", + "i18n.unbind_success.1c43":"解绑成功", + "i18n.unexpected_exception.2b52":"发生异常", + "i18n.unexpected_exception_with_details.247d":"发生异常:{} {}", + "i18n.unknown_data_loss.5a24":"未知(数据丢失)", + "i18n.unknown_database_dialect_type.951b":"未知的数据库方言类型:", + "i18n.unknown_database_mode.f9e5":"当前数据库模式未知", + "i18n.unknown_error.84d3":"未知:", + "i18n.unknown_exception_on_pull_code.2b2e":"拉取代码发生未知异常建议清除构建重新操作:", + "i18n.unknown_jsch_log_level.6a5c":"未知的 jsch 日志级别:{}", + "i18n.unknown_jsch_log_level_with_details.1f9a":"未知的 jsch 日志级别:{} {}", + "i18n.unknown_parameter.96dd":"未知参数", + "i18n.unknown_prune_type.0931":"pruneType 未知", + "i18n.unknown_script_template_or_workspace.27f1":"脚本模板或者工作空间未知", + "i18n.unlock_success.4cea":"解锁成功", + "i18n.unsupported_encoding_with_placeholder.3bd9":"不支持的编码方式:{}", + "i18n.unsupported_item.bcf4":"不支持的:", + "i18n.unsupported_method.a1de":"不支持的方式", + "i18n.unsupported_method_with_colon.eae8":"不支持的方式:", + "i18n.unsupported_mode.501d":"不支持的模式", + "i18n.unsupported_mode.a3d3":"暂不支持的模式:", + "i18n.unsupported_mode_with_colon.c6de":"不支持的模式:", + "i18n.unsupported_mode_with_script_log.6a7a":"不支持的模式,script log", + "i18n.unsupported_plugin_message.2889":"当前还不支持 {} 插件", + "i18n.unsupported_prefix.4f8c":"当前还不支持:", + "i18n.unsupported_request_method.45d7":"不被支持的请求方式", + "i18n.unsupported_system_type_with_placeholder.d5cc":"不支持的系统类型:{}", + "i18n.unsupported_type.7495":"不支持的类型", + "i18n.unsupported_type_with_colon.1050":"不支持的类型:", + "i18n.unsupported_type_with_colon2.7de2":"不支持的类型:", + "i18n.unsupported_type_with_placeholder.71a2":"不支持的类型:{}", + "i18n.unzip_exception.453e":"{} 解压异常 {} {}", + "i18n.unzip_exception.92cc":"解压异常 {} by InputStream {}", + "i18n.update_condition_not_found.0870":"没有更新条件", + "i18n.update_container_service_exception.2249":"更新容器服务调用容器异常", + "i18n.update_docker_machine_id_failed.063d":"更新 DOCKER 表机器id 失败:", + "i18n.update_node_machine_id_failed.51d9":"更新节点表机器 id 失败:", + "i18n.update_operation_log_failed.d348":"更新操作日志失败", + "i18n.update_restore_data.1b0b":"更新还原数据:{}", + "i18n.update_ssh_machine_id_failed.bd24":"更新 SSH 表机器id 失败:", + "i18n.update_success.55aa":"更新成功", + "i18n.upgrade_database_process.e604":"升级数据库流程:", + "i18n.upgrade_duration_message.bab4":"升级(重启)中大约需要30秒~2分钟左右", + "i18n.upgrade_failure.4ae2":"升级失败", + "i18n.upgrade_failure_with_colon.59f1":"升级失败:", + "i18n.upload_action.d5a7":"上传", + "i18n.upload_exception.cd6c":"上传异常:", + "i18n.upload_exception_mismatch.0b25":"上传异常,完成数量不匹配", + "i18n.upload_failed.b019":"上传失败:", + "i18n.upload_failed_no_matching_project.b219":"上传失败,没有找到对应的分发项目", + "i18n.upload_progress_message_format.b91c":"上传文件进度:{}[{}/{}] {}/{} {} ", + "i18n.upload_progress_template.ac3f":"上传文件进度:{}/{} {}", + "i18n.upload_progress_with_colon.dd5b":"上传文件进度:{} {}/{} {}", + "i18n.upload_progress_with_units.44ad":"上传文件进度:{} {}/{} {} ", + "i18n.upload_success.a769":"上传成功", + "i18n.upload_success_and_distribute.f446":"上传成功,开始分发!", + "i18n.upload_success_and_restart.7bc3":"上传成功并重启", + "i18n.url_length_exceeded.ca1c":"url 长度不能超过 200", + "i18n.user_account.cbf7":"用户账号", + "i18n.user_binding_warning.16b0":"当前权限组还绑定用户,不能直接删除(需要提前解绑或者删除关联数据后才能删除)", + "i18n.user_custom_workspace.ef93":"用户自定义工作空间", + "i18n.user_directory_not_found.cfe3":"用户目录没有找到", + "i18n.user_directory_not_found_private_key_info.6ce4":"用户目录没有找到私钥信息", + "i18n.user_does_not_exist.8363":" 用户不存在请联系管理创建", + "i18n.user_field_required.8732":"第 {} 行 user 字段不能位空", + "i18n.user_login_log.0c00":"用户登录日志", + "i18n.user_management.7d94":"用户管理", + "i18n.user_not_exist.4892":"用户不存在", + "i18n.user_not_exist.5387":"不存在对应的用户", + "i18n.user_not_exist_trigger_invalid.f375":"对应的用户不存在,触发器已失效", + "i18n.user_not_select_permission_group.1091":"用户未选择权限组", + "i18n.user_operation_alarm.15b9":"用户操作报警", + "i18n.user_operation_log.2233":"用户操作日志", + "i18n.user_or_group_bindings_exist_in_workspace.d57b":"当前工作空间下还绑定着用户(权限组)信息", + "i18n.user_permission_group.52a4":"用户权限组", + "i18n.user_trigger_unavailable.9866":"当前用户触发器不可用", + "i18n.user_workspace_relation_table.851e":"用户(权限组)工作空间关系表", + "i18n.uses_only_supports_string_type.ac54":"uses 只支持 String 类型", + "i18n.variable_name_already_exists.70f2":"对应的变量名称已经存在啦", + "i18n.variable_name_rules.480a":"变量名称 1-50 英文字母 、数字和下划线", + "i18n.verification_code_disabled.349b":"验证码已禁用", + "i18n.verification_code_incorrect.d8c0":"验证码不正确", + "i18n.verification_code_incorrect_retry.d88d":"验证码不正确,请重新输入", + "i18n.verification_code_is.5af5":"验证码是:{}", + "i18n.verification_method_not_configured.7358":"{}未配置验证方法:{}", + "i18n.wait_for_seconds.ff7b":"执行等待 {} 秒", + "i18n.waiting_to_close_process.3634":"等待关闭[Process]进程:{}", + "i18n.waiting_to_start.b267":"等待开始:", + "i18n.webhooks_invocation_error.9792":"WebHooks 调用错误", + "i18n.websocket_error.2bb4":"websocket出现错误:{}", + "i18n.week_day_range_format.ebec":"周{} 的 {} 至 {}", + "i18n.welcome_join_session.1c16":"欢迎加入:{} 会话id:{} ", + "i18n.workspace_env_vars.f7e8":"工作空间环境变量", + "i18n.workspace_error_or_no_permission.7c8b":"工作空间错误,或者没有权限编辑此数据", + "i18n.workspace_id_required.c967":"工作空间ID不能为空", + "i18n.workspace_label.98d6":"工作空间", + "i18n.workspace_name_already_exists.0f82":"对应的工作空间名称已经存在啦", + "i18n.workspace_not_exist.a6fd":"不存在对应的工作空间", + "i18n.workspace_required.b3bd":"请选择工作空间", + "i18n.workspace_ssh_already_exists.ccc0":"对应的工作空间已经存在当前 SSH 啦", + "i18n.wrong_id.ab4d":"错误的ID", + "i18n.yml_config_format_error_illegal_field.16ea":"yml 配置内容格式有误请检查后重新操作(请检查是否有非法字段):", + "i18n.yml_config_format_error_tab.f629":"yml 配置内容格式有误请检查后重新操作(不要使用 \\\\t(TAB) 缩进):", + "i18n.yml_configuration_content_error.08f8":"yml 配置内容错误" +} \ No newline at end of file diff --git a/modules/common/src/main/resources/logback-spring.xml b/modules/common/src/main/resources/logback-spring.xml deleted file mode 100644 index 0c07464fbb..0000000000 --- a/modules/common/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - INFO - ACCEPT - DENY - - - - - - - - - ERROR - ACCEPT - DENY - - - - - - - - - warn - ACCEPT - DENY - - - - - - - - - DEBUG - ACCEPT - DENY - - - - - - - - ${logPath}/error/systemError.log - - ERROR - ACCEPT - DENY - - - %d{HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n - - - - ${logPath}/error/systemError-%d{yyyy-MM-dd}.%i.log - - 100MB - 10 - 1GB - - - - - ${logPath}/info.log - - info - ACCEPT - DENY - - - %d{HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n - - - - ${logPath}/info-%d{yyyy-MM-dd}.%i.log - - 100MB - 10 - 1GB - - - - - ${logPath}/warn.log - - warn - ACCEPT - DENY - - - %d{HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n - - - - ${logPath}/warn-%d{yyyy-MM-dd}.%i.log - - 100MB - 10 - 1GB - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/modules/common/src/main/resources/privacy/1.0.0.md b/modules/common/src/main/resources/privacy/1.0.0.md new file mode 100644 index 0000000000..2fc60682ab --- /dev/null +++ b/modules/common/src/main/resources/privacy/1.0.0.md @@ -0,0 +1,54 @@ +# Jpom项目运维本地部署版隐私协议 + +**一、引言** + +**1.1** 我们诚挚欢迎并感谢您选用开源软件——Jpom项目运维的本地部署版本(以下简称“本软件”)。本隐私协议旨在全面介绍和阐明我们在您本地部署和使用本软件时,关于数据处理的各种活动规范,覆盖但不限于数据的搜集、运用、存储和防护等各个阶段。敬请您在安装并启用本软件之前,务必详尽阅读并深刻理解本协议各项条款。 + +**二、数据处理与本地化规范** + +**2.1** **本地数据操作** +本软件所有数据处理活动严格限制在您指定和管控的本地服务器或设备上执行,我们不会直接获取或远程访问这些数据资源。为保证软件的正常运行,可能需要处理如配置信息、错误日志、临时文件等本地生成的各类数据。 + +**2.2** **必要数据操作原则** +为确保软件基础功能稳定、性能卓越、技术问题有效解决以及优化用户使用体验,本软件可能会对上述部分或全部数据进行必要的处理。请注意,此类数据操作完全局限于本地环境,并严格遵循软件设计的规范与标准。 + +**三、开源与透明度** + +**3.1** 本软件基于 MulanPSL2 开源许可协议发布,其源代码面向公众开放,意味着您有权审查软件源码以验证其数据处理行为符合您的隐私期待。我们积极倡议用户共同参与和监督,共同营造一个高度尊重数据隐私的应用生态体系。 + +**四、数据主权与控制** + +**4.1** 用户对其在本地部署环境中生成的所有数据享有完整的控制权和所有权,这涵盖了从数据的创建、修改、删除到数据传输方式及存储地点的决定权。 + +**五、数据安全保护** + +**5.1** 尽管本软件自带一定的内在安全机制,但用户仍需自行负责在本地部署环境中实施高效的数据安全措施,包括但不限于数据加密、访问控制、安全审计及数据备份策略等。对于因用户本地部署环境引发的数据安全事件,我们不承担直接法律责任。 + +**六、数据交互与隐私承诺** + +本软件坚守最高的数据隐私保护原则,明确规定未经用户明确授权,本软件决不会主动对外向任何第三方透露您系统内的任何敏感信息。 + +我们尤为重视并全力支持用户对数据安全性的深度审视诉求,确保每位用户有权详尽审查软件源代码,以亲手验证我们在数据处理与安全防护措施层面的严密性和合规性。 +我们通过这种开诚布公、负责任的方式,共同致力于最大程度地保障用户数据安全与隐私不受侵犯。 + +**6.1** 个人信息收集政策 + +我们严格遵循不收集原则,除非必要,否则不涉及收集用户的个人身份识别信息,例如姓名、电子邮件地址、电话号码、身份证件号码等。 + +**6.2** 可能收集的数据类别 + +在您使用我们产品和服务的过程中,为确保正常运行和提供更好的服务体验,我们有可能会收集与产品使用紧密相关的一些必要信息,包括但不限于IP地址、Cookies、页面路由、地理位置信息、设备制造商、设备型号、设备唯一标识符以及设备相关数据等。 + +**6.3** 数据信息处理方式 + +在必须收集相关信息的情况下,我们会在遵循相关法律法规及本隐私协议的基础上,将这些数据传输至我们的官方服务器(https://jpom.top),并在严格的规则约束下进行管理和使用。 + +**七、法律合规与用户责任** + +**7.1** 在本软件的研发和运营过程中,我们严格遵守中国各地关于数据保护和隐私权的各项法律法规。 + +**7.2** 用户在部署和使用本软件时,同样需遵循相关法律法规,承诺在使用过程中秉持合法、合理原则,不得利用本软件从事非法行为、侵犯他人合法权益,也不得在违反我国法律法规的项目或平台上部署和使用本软件。 + +**八、隐私政策更新** + +**8.1** 我们保留在必要时适时更新和完善本隐私协议的权利,并将通过官方网站、软件更新公告或其他醒目位置公布修订内容。对于重要的政策变更,我们将提前通知用户,用户在变更后继续使用本软件即视作已同意并接受新的隐私协议。 \ No newline at end of file diff --git a/modules/common/src/main/resources/robots.txt b/modules/common/src/main/resources/robots.txt new file mode 100644 index 0000000000..77470cb39f --- /dev/null +++ b/modules/common/src/main/resources/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/modules/common/src/test/java/cn/ContiPerTest.java b/modules/common/src/test/java/cn/ContiPerTest.java index 89fa10ebbf..c11e429433 100644 --- a/modules/common/src/test/java/cn/ContiPerTest.java +++ b/modules/common/src/test/java/cn/ContiPerTest.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package cn; @@ -33,13 +20,14 @@ /** * @author bwcx_jzy - * @date 2019/9/3 + * @since 2019/9/3 */ public class ContiPerTest { @Rule public ContiPerfRule i = new ContiPerfRule(); // @Test + @Test @PerfTest(invocations = 200000000, threads = 16) public void test1() throws Exception { IdUtil.fastSimpleUUID(); diff --git a/modules/common/src/test/java/cn/TestApacheExec.java b/modules/common/src/test/java/cn/TestApacheExec.java new file mode 100644 index 0000000000..be77c0120a --- /dev/null +++ b/modules/common/src/test/java/cn/TestApacheExec.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cn; + +import org.apache.commons.exec.*; +import org.junit.Test; + +import java.io.IOException; +import java.time.Duration; + +/** + * https://blog.51cto.com/u_75269/7968284 + */ +public class TestApacheExec { + private ShutdownHookProcessDestroyer shutdownHookProcessDestroyer = new ShutdownHookProcessDestroyer(); + + + @Test + public void test() throws IOException { + + + CommandLine commandLine = CommandLine.parse("ping baidu.com"); + + // 重定向stdout和stderr到文件 + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(); + + // 超时终止:1秒 + ExecuteWatchdog executeWatchdog = ExecuteWatchdog.builder().setTimeout(Duration.ofSeconds(10)).get(); + + // 创建执行器 + DefaultExecutor executor = DefaultExecutor.builder() + .setExecuteStreamHandler(pumpStreamHandler) + .get(); + executor.setProcessDestroyer(new ProcessDestroyer() { + @Override + public boolean add(Process process) { + return shutdownHookProcessDestroyer.add(process); + } + + @Override + public boolean remove(Process process) { + return shutdownHookProcessDestroyer.remove(process); + } + + @Override + public int size() { + return shutdownHookProcessDestroyer.size(); + } + }); + executor.setWatchdog(executeWatchdog); + + // 执行,打印退出码 + int exitValue = executor.execute(commandLine); + System.out.println(exitValue); + } +} diff --git a/modules/common/src/test/java/cn/TestCompression.java b/modules/common/src/test/java/cn/TestCompression.java new file mode 100644 index 0000000000..e7ce0b2c8d --- /dev/null +++ b/modules/common/src/test/java/cn/TestCompression.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cn; + +import cn.hutool.core.io.FileUtil; +import org.dromara.jpom.util.CompressionFileUtil; +import org.junit.Test; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2023/2/13 + */ +public class TestCompression { + + @Test + public void test() { + File file = new File("D:\\System-Data\\Documents\\WeChat Files\\A22838106\\FileStorage\\File\\2023-02\\$R7JOOR8.tar.gz"); + File dir = FileUtil.file("D:\\temp\\unc"); + CompressionFileUtil.unCompress(file, dir); + } + + @Test + public void test2(){ + File file = new File("/Users/user/压缩文件.tbz"); + File dir = FileUtil.file("/Users/user/unc"); + CompressionFileUtil.unCompress(file, dir); + } +} diff --git a/modules/common/src/test/java/cn/TestT.java b/modules/common/src/test/java/cn/TestT.java deleted file mode 100644 index 364941623a..0000000000 --- a/modules/common/src/test/java/cn/TestT.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 cn; - -import java.time.Duration; - -/** - * @author bwcx_jzy - * @date 2019/9/4 - */ -public class TestT { - public static void main(String[] args) { - Duration parse = Duration.parse("1H"); - System.out.println(parse.getSeconds()); - } -} diff --git a/modules/common/src/test/java/cn/TestTailer.java b/modules/common/src/test/java/cn/TestTailer.java index 41c4695529..d3ced3b06a 100644 --- a/modules/common/src/test/java/cn/TestTailer.java +++ b/modules/common/src/test/java/cn/TestTailer.java @@ -1,31 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package cn; +import cn.hutool.core.collection.BoundedPriorityQueue; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.LineHandler; import cn.hutool.core.io.file.Tailer; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.util.LimitQueue; +import org.junit.Test; import java.io.File; +import java.util.Comparator; public class TestTailer { public static void main(String[] args) { @@ -39,4 +32,26 @@ public void handle(String line) { tailer.start(true); System.out.println("12"); } + + @Test + public void testLimitQueue() { + LimitQueue limitQueue = new LimitQueue<>(5); + for (int i = 0; i < 20; i++) { + limitQueue.offer(i + ""); + System.out.println((i + 1) + " " + CollUtil.join(limitQueue, StrUtil.SPACE)); + } + // + System.out.println("-------"); + BoundedPriorityQueue boundedPriorityQueue = new BoundedPriorityQueue<>(5, Comparator.reverseOrder()); + for (int i = 0; i < 20; i++) { + boundedPriorityQueue.offer(i); + System.out.println((i + 1) + " " + CollUtil.join(boundedPriorityQueue, StrUtil.SPACE)); + } + } + + @Test + public void testLine() { + + } + } diff --git a/modules/common/src/test/java/cn/TestVersions.java b/modules/common/src/test/java/cn/TestVersions.java new file mode 100644 index 0000000000..20e33f8548 --- /dev/null +++ b/modules/common/src/test/java/cn/TestVersions.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cn; + +import cn.hutool.http.HttpUtil; +import org.junit.Test; + +/** + * @author bwcx_jzy + * @since 2021/12/16 + */ +public class TestVersions { + + @Test + public void test() { + String version = VersionUtils.getVersion(HttpUtil.class, null); + System.out.println(version); + } +} diff --git a/modules/common/src/test/java/cn/TestYml.java b/modules/common/src/test/java/cn/TestYml.java new file mode 100644 index 0000000000..63796115d7 --- /dev/null +++ b/modules/common/src/test/java/cn/TestYml.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cn; + +import cn.hutool.core.io.resource.ResourceUtil; +import org.junit.Test; +import org.yaml.snakeyaml.Yaml; + +import java.net.URL; + +/** + * @author bwcx_jzy + * @since 2022/1/12 + */ +public class TestYml { + + @Test + public void test() { + URL resource = ResourceUtil.getResource("test.yml"); + final Yaml yaml = new Yaml(); + Object load = yaml.load(ResourceUtil.getStream("test.yml")); + System.out.println(load); + +// Dict dict = YamlUtil.loadByPath(resource.getPath()); +// System.out.println(dict); + } +} diff --git a/modules/common/src/test/java/cn/VersionUtils.java b/modules/common/src/test/java/cn/VersionUtils.java new file mode 100644 index 0000000000..209e01a769 --- /dev/null +++ b/modules/common/src/test/java/cn/VersionUtils.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cn; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; + +import java.security.CodeSource; + +/** + * VersionUtils. + * Just read MANIFEST.MF file and find Implementation-Version property + * + * @author Hotstrip + */ +@Slf4j +public final class VersionUtils { + + private static final String VERSION = getVersion(VersionUtils.class, "1.0.0"); + + private VersionUtils() { + } + + /** + * Gets version. + * + * @return the version + */ + public static String getVersion() { + return VERSION; + } + + /** + * Gets version. + * + * @param cls the cls + * @param defaultVersion the default version + * @return the version + */ + public static String getVersion(final Class cls, final String defaultVersion) { + // find version info from MANIFEST.MF first + String version = cls.getPackage().getImplementationVersion(); + if (StrUtil.isEmpty(version)) { + version = cls.getPackage().getSpecificationVersion(); + } + if (!StrUtil.isEmpty(version)) { + return version; + } + // guess version fro jar file name if nothing's found from MANIFEST.MF + CodeSource codeSource = cls.getProtectionDomain().getCodeSource(); + + if (codeSource == null) { + log.warn("No codeSource for class {} when getVersion, use default version {}", cls.getName(), defaultVersion); + return defaultVersion; + } + String file = codeSource.getLocation().getFile(); + if (file != null && file.endsWith(FileNameUtil.EXT_JAR)) { + file = file.substring(0, file.length() - 4); + int i = file.lastIndexOf('/'); + if (i >= 0) { + file = file.substring(i + 1); + } + i = file.indexOf("-"); + if (i >= 0) { + file = file.substring(i + 1); + } + while (file.length() > 0 && !Character.isDigit(file.charAt(0))) { + i = file.indexOf("-"); + if (i < 0) { + break; + } + file = file.substring(i + 1); + } + version = file; + } + // return default version if no version info is found + return StrUtil.isEmpty(version) ? defaultVersion : version; + } +} + + diff --git a/modules/common/src/test/java/cn/myroute/mbean/AbstractJmxCommand.java b/modules/common/src/test/java/cn/myroute/mbean/AbstractJmxCommand.java deleted file mode 100644 index 1a0a921aa8..0000000000 --- a/modules/common/src/test/java/cn/myroute/mbean/AbstractJmxCommand.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 码之科技工作室 -// * -// * 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 cn.myroute.mbean; -// -// -//import cn.hutool.system.SystemUtil; -//import com.sun.tools.attach.VirtualMachine; -//import com.sun.tools.attach.VirtualMachineDescriptor; -// -//import java.io.File; -//import java.io.IOException; -//import java.lang.reflect.Method; -//import java.net.URL; -//import java.net.URLClassLoader; -//import java.util.List; -//import java.util.Properties; -//import java.util.function.Consumer; -// -///** -// * @author bwcx_jzy -// * @date 2019/8/5 -// */ -//public class AbstractJmxCommand { -// private static final String CONNECTOR_ADDRESS = -// "com.sun.management.jmxremote.localConnectorAddress"; -// -// public static String getJVM() { -// return System.getProperty("java.vm.specification.vendor"); -// } -// -// public static boolean isSunJVM() { -// // need to check for Oracle as that is the name for Java7 onwards. -// return getJVM().equals("Sun Microsystems Inc.") || getJVM().startsWith("Oracle"); -// } -// -// public static void main(String[] args) { -// List list = VirtualMachine.list(); -// list.forEach(new Consumer() { -// @Override -// public void accept(VirtualMachineDescriptor virtualMachineDescriptor) { -// try { -// -// int pid = Integer.parseInt(virtualMachineDescriptor.id()); -// System.out.println(new AbstractJmxCommand().findJMXUrlByProcessId(pid)); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -// }); -// -// -// } -// -// /** -// * Finds the JMX Url for a VM by its process id -// * -// * @param pid The process id value of the VM to search for. -// * @return the JMX Url of the VM with the given pid or null if not found. -// */ -// // @SuppressWarnings({ "rawtypes", "unchecked" }) -// protected String findJMXUrlByProcessId(int pid) { -// -// if (isSunJVM()) { -// try { -// // Classes are all dynamically loaded, since they are specific to Sun VM -// // if it fails for any reason default jmx url will be used -// -// // tools.jar are not always included used by default class loader, so we -// // will try to use custom loader that will try to load tools.jar -// -// String javaHome = System.getProperty("java.home"); -// String tools = javaHome + File.separator + -// ".." + File.separator + "lib" + File.separator + "tools.jar"; -// URLClassLoader loader = new URLClassLoader(new URL[]{new File(tools).toURI().toURL()}); -// -// Class virtualMachine = Class.forName("com.sun.tools.attach.VirtualMachine", true, loader); -// Class virtualMachineDescriptor = Class.forName("com.sun.tools.attach.VirtualMachineDescriptor", true, loader); -// -// Method getVMList = virtualMachine.getMethod("list", (Class[]) null); -// Method attachToVM = virtualMachine.getMethod("attach", String.class); -// Method getAgentProperties = virtualMachine.getMethod("getAgentProperties", (Class[]) null); -// Method getVMId = virtualMachineDescriptor.getMethod("id", (Class[]) null); -// -// List allVMs = (List) getVMList.invoke(null, (Object[]) null); -// -// for (Object vmInstance : allVMs) { -// String id = (String) getVMId.invoke(vmInstance, (Object[]) null); -// if (id.equals(Integer.toString(pid))) { -// -// Object vm = attachToVM.invoke(null, id); -// -// Properties agentProperties = (Properties) getAgentProperties.invoke(vm, (Object[]) null); -// String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS); -// -// if (connectorAddress != null) { -// return connectorAddress; -// } else { -// break; -// } -// } -// } -// -// //上面的尝试都不成功,则尝试让agent加载management-agent.jar -// Method getSystemProperties = virtualMachine.getMethod("getSystemProperties", (Class[]) null); -// Method loadAgent = virtualMachine.getMethod("loadAgent", String.class, String.class); -// Method detach = virtualMachine.getMethod("detach", (Class[]) null); -// for (Object vmInstance : allVMs) { -// String id = (String) getVMId.invoke(vmInstance, (Object[]) null); -// if (id.equals(Integer.toString(pid))) { -// -// Object vm = attachToVM.invoke(null, id); -// -// Properties systemProperties = (Properties) getSystemProperties.invoke(vm, (Object[]) null); -// String home = SystemUtil.getJavaRuntimeInfo().getHomeDir(); -// // Normally in ${java.home}/jre/lib/management-agent.jar but might -// // be in ${java.home}/lib in build environments. -// -// String agent = home + File.separator + "jre" + File.separator + -// "lib" + File.separator + "management-agent.jar"; -// File f = new File(agent); -// if (!f.exists()) { -// agent = home + File.separator + "lib" + File.separator + -// "management-agent.jar"; -// f = new File(agent); -// if (!f.exists()) { -// throw new IOException("Management agent not found"); -// } -// } -// -// agent = f.getCanonicalPath(); -// -// loadAgent.invoke(vm, agent, "com.sun.management.jmxremote"); -// -// Properties agentProperties = (Properties) getAgentProperties.invoke(vm, (Object[]) null); -// String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS); -// -// //detach 这个vm -// detach.invoke(vm, (Object[]) null); -// -// if (connectorAddress != null) { -// return connectorAddress; -// } else { -// break; -// } -// } -// } -// } catch (Exception ignore) { -// ignore.printStackTrace(); -// } -// } -// -// return null; -// } -//} diff --git a/modules/common/src/test/java/i8n/ExtractI18nTest.java b/modules/common/src/test/java/i8n/ExtractI18nTest.java new file mode 100644 index 0000000000..b3712c1ea7 --- /dev/null +++ b/modules/common/src/test/java/i8n/ExtractI18nTest.java @@ -0,0 +1,670 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package i8n; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.PageUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import i8n.api.BaiduBceRpcTexttransTest; +import i8n.api.VolcTranslateApiTest; +import lombok.Lombok; +import org.junit.Assert; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.*; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + *

Jpom 后端 i18n 工具自动化流程

+ *

+ *

    + *
  1. 提取代码中的所有关联的中文
  2. + *
  3. 将提取到的中文使用千帆大模型(百度)进行语意化翻译
  4. + *
  5. 将翻译结果进行拼接成 key = i18n.xxxx.md5('原始中文')[4+]
  6. + *
  7. 将结果缓存至 words.json (如果缓存中存在不会进行重复翻译)
  8. + *
  9. ----------------------------
  10. + *
  11. 将 words.json 转换为 zh_CN.properties
  12. + *
  13. 扫描代码中所有关联中文并根据 zh_CN.properties 进行替换
  14. + *
  15. 对比扫描结果中使用到的 i18n key,删除 zh_CN.properties 、words.json 中未关联的 key
  16. + *
  17. 对应扫描结果中的 i18n key 但是 zh_CN.properties 中不存在的 key,进行提示
  18. + *
  19. ----------------------------
  20. + *
  21. 将 zh_CN.properties 使用火山翻译(字节跳动)转换为 en_US.properties
  22. + *
  23. ----------------------------
  24. + *
  25. 使用 idea 优化 import 实现自动导包
  26. + *
+ *

+ * ====================================== + *

+ * 扫描代码中的中文关键词:\"[\u4e00-\u9fa5]+\" + * + * @author bwcx_jzy + * @since 2024/6/11 + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class ExtractI18nTest { + + /** + * 项目根路径 + */ + private File rootFile; + + private File zhPropertiesFile; + private final Charset charset = CharsetUtil.CHARSET_UTF_8; + /** + * 匹配中文字符的正则表达式 + */ + private final Pattern[] chinesePatterns = new Pattern[]{ + // 中文开头 + Pattern.compile("\"[\\u4e00-\\u9fa5][\\u4e00-\\u9fa5\\w.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。~!=/|]*\""), + // 序号开头 + Pattern.compile("\"\\d+\\..*[\\u4e00-\\u9fa5][\\u4e00-\\u9fa5\\w.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + // 符合开头 + Pattern.compile("\"[,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。].*[\\u4e00-\\u9fa5][\\u4e00-\\u9fa5\\w.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + // 空格开头 + Pattern.compile("\"[\\s+][\\u4e00-\\u9fa5][\\u4e00-\\u9fa5\\w.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + Pattern.compile("\"[a-zA-Z.·\\d][\\u4e00-\\u9fa5]*[\\u4e00-\\u9fa5.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + Pattern.compile("\"[\\d.]\\s[\\u4e00-\\u9fa5]*[\\u4e00-\\u9fa5.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + Pattern.compile("\"[\\u4e00-\\u9fa5]+[a-zA-Z]\""), + // 字母开头 + Pattern.compile("\"[a-zA-Z{} ].*[\\u4e00-\\u9fa5][\\u4e00-\\u9fa5\\w.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + Pattern.compile("\"[a-zA-Z{} ].*[\\d\\s].*[\\u4e00-\\u9fa5][\\u4e00-\\u9fa5\\w.,;:'!?()~,><#@$%{}【】、():\\[\\]+\" \\-。]*\""), + }; + /** + * 代码中关联(引用) key 的正则 + */ + public static final Pattern[] messageKeyPatterns = new Pattern[]{ + // 优先匹配(避免后续替换异常) + Pattern.compile("TransportI18nMessageUtil\\.get\\(\"(.*?)\"\\)"), + Pattern.compile("I18nMessageUtil\\.get\\(\"(.*?)\"\\)"), + Pattern.compile("@ValidatorItem\\(.*?msg\\s*=\\s*\"([^\"]*)\".*?\\)"), + Pattern.compile("nameKey\\s*=\\s*\"([^\"]*)\".*?\\)"), + }; + + public final static String[] JpomAnnotation = { + "@ValidatorItem", "nameKey = \"" + }; + + @Before + public void before() throws Exception { + File file = new File(""); + String rootPath = file.getAbsolutePath(); + rootFile = new File(rootPath).getParentFile(); + // + zhPropertiesFile = FileUtil.file(rootFile, "common/src/main/resources/i18n/messages_zh_CN.properties"); + } + + /** + * 提取代码中的中文并使用大模型进行语意化翻译 key + */ + @Test + public void extractJavaZh_First1() throws Exception { + // 中文字符串 + Set wordsSet = new LinkedHashSet<>(); + // 将已经存在的合并使用, 中文资源文件存储路径 + Properties zhProperties = new Properties(); + try (BufferedReader inputStream = FileUtil.getReader(zhPropertiesFile, charset)) { + zhProperties.load(inputStream); + } + zhProperties.values().forEach(o -> wordsSet.add(o.toString())); + // 提取中文 + walkFile(rootFile, file1 -> { + try { + for (Pattern chinesePattern : chinesePatterns) { + verifyDuplicates(file1, chinesePattern); + extractFile(file1, chinesePattern, wordsSet); + } + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + // 检查去除前后空格后是否重复 + Map collect = wordsSet.stream() + .map(StrUtil::trim) + .collect(Collectors.groupingBy(e -> e, Collectors.counting())); + for (Map.Entry entry : collect.entrySet()) { + long value = entry.getValue(); + Assert.assertEquals("[" + entry.getKey() + "]出现去重空格后重复", 1L, value); + } + // 语意化中文存储为 key(排序) + Collection wordsSetSort = CollUtil.sort(wordsSet, String::compareTo); + // + JSONObject cacheWords = this.loadCacheWords(); + + int pageSize = 50; + int total = CollUtil.size(wordsSet); + int page = PageUtil.totalPage(total, pageSize); + JSONObject allResult = new JSONObject(); + // + for (int i = PageUtil.getFirstPageNo(); i <= page; i++) { + int start = PageUtil.getStart(i, pageSize); + int end = PageUtil.getEnd(i, pageSize); + Collection sub = CollUtil.sub(wordsSetSort, start, end); + Collection subAfter = filterExists(sub, cacheWords, allResult); + if (CollUtil.isNotEmpty(subAfter)) { + while (true) out:{ + BaiduBceRpcTexttransTest bceRpcTexttrans = new BaiduBceRpcTexttransTest(); + System.out.println("等待翻译:" + subAfter); + JSONObject jsonObject = bceRpcTexttrans.doTranslate(subAfter); + System.out.println("翻译结果:" + jsonObject); + // 转换为可用 key + for (Map.Entry entry : jsonObject.entrySet()) { + String key = entry.getKey(); + String value = (String) entry.getValue(); + String originalValue = findOriginal(subAfter, value); + if (originalValue == null) { + System.err.println("翻译后的中文和翻译前的中文不一致(需要重试):" + value); + break out; + } + String buildKey = this.buildKey(key, originalValue, allResult); + allResult.put(buildKey, originalValue); + } + break; + } + } + saveWords(allResult, true); + } + } + + /** + * 根据提取出的中文生成 i18n 中文配置文件、并替换代码中的关键词 + *

+ * 1. 自动对比未关联 + * 2. 自动删除未使用 + * + * @throws Exception 异常 + */ + @Test + public void generateZhPropertiesAndReplace_Second2() throws Exception { + JSONObject cacheWords = this.loadCacheWords(); + TreeMap sort = MapUtil.sort(cacheWords); + Properties zhProperties = new Properties(); + zhProperties.putAll(sort); + { + Properties zhExitsProperties = new Properties(); + try (BufferedReader inputStream = FileUtil.getReader(zhPropertiesFile, charset)) { + zhExitsProperties.load(inputStream); + } + if (!ObjectUtil.equals(zhExitsProperties, zhProperties)) { + // 不一致才重新存储 + try (BufferedWriter writer = FileUtil.getWriter(zhPropertiesFile, charset, false)) { + zhProperties.store(writer, "i18n zh"); + } + } + } + // 将配置按照中文转 map + /* + 中文对应的 key map +

+ key:中文 + value:随机key + */ + Map chineseMap = new HashMap<>(); + for (Map.Entry entry : zhProperties.entrySet()) { + chineseMap.put(StrUtil.toStringOrNull(entry.getValue()), StrUtil.toStringOrNull(entry.getKey())); + } + // 代码中已经使用到的 key + Collection useKeys = new HashSet<>(); + // 替换中文 + walkFile(rootFile, file1 -> { + try { + for (Pattern chinesePattern : chinesePatterns) { + replaceQuotedChineseInFile(file1, chinesePattern, chineseMap, useKeys); + } + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + // + boolean isChange = false; + for (Object keyObj : CollUtil.newArrayList(zhProperties.keySet())) { + String key = StrUtil.toStringOrNull(keyObj); + if (useKeys.contains(key)) { + continue; + } + System.err.println("配置中存在未关联的key(将自动删除 zhProperties、words.json):" + key); + zhProperties.remove(key); + cacheWords.remove(key); + isChange = true; + } + if (isChange) { + saveWords(cacheWords, false); + try (BufferedWriter writer = FileUtil.getWriter(zhPropertiesFile, charset, false)) { + zhProperties.store(writer, "i18n zh"); + } + } + List notUseKeys = useKeys.stream().filter(o -> !zhProperties.containsKey(o)).collect(Collectors.toList()); + Assert.assertTrue("存在未使用的 key:" + CollUtil.join(notUseKeys, StrUtil.COMMA), CollUtil.isEmpty(notUseKeys)); + } + + /** + * https://www.volcengine.com/docs/4640/65067 + * + * @throws IOException io 异常 + */ + @Test + public void syncZhToEnProperties_Third3() throws Exception { + // 加载中文配置 + Properties zhProperties = new Properties(); + try (BufferedReader inputStream = FileUtil.getReader(zhPropertiesFile, charset)) { + zhProperties.load(inputStream); + } + Map map = new HashMap<>(); + map.put("en_US", "en"); + map.put("zh_HK", "zh-Hant-hk"); + map.put("zh_TW", "zh-Hant-tw"); + for (Map.Entry entry : map.entrySet()) { + + this.syncZhToProperties(entry.getKey(), entry.getValue()); + } + } + + private void syncZhToProperties(String language, String volcLang) throws Exception { + // 加载中文配置 + Properties zhProperties = new Properties(); + try (BufferedReader inputStream = FileUtil.getReader(zhPropertiesFile, charset)) { + zhProperties.load(inputStream); + } + + // 加载配置 + File enPropertiesFile = FileUtil.file(rootFile, "common/src/main/resources/i18n/messages_" + language + ".properties"); + Properties enEexitsProperties = new Properties(); + try (BufferedReader inputStream = FileUtil.getReader(enPropertiesFile, charset)) { + enEexitsProperties.load(inputStream); + } + // 翻译成目标语言 + VolcTranslateApiTest translateApi = new VolcTranslateApiTest(); + Set keySets = zhProperties.keySet(); + // 接口限制、不能超过 16 + int pageSize = 16; + int total = CollUtil.size(keySets); + int page = PageUtil.totalPage(total, pageSize); + // + Properties enProperties = new Properties(); + for (int i = PageUtil.getFirstPageNo(); i <= page; i++) { + int start = PageUtil.getStart(i, pageSize); + int end = PageUtil.getEnd(i, pageSize); + + List keys = CollUtil.sub(keySets, start, end); + List values = new ArrayList<>(); + List useKeys = new ArrayList<>(); + for (Object object : keys) { + String key = (String) object; + String value1 = (String) zhProperties.get(key); + String existsValue = (String) enEexitsProperties.get(key); + if (existsValue != null) { + // 已经存在 + enProperties.put(key, existsValue); + continue; + } + values.add(value1); + useKeys.add(key); + } + if (CollUtil.isNotEmpty(values)) { + JSONArray translateText = translateApi.translate("zh", volcLang, values); + System.out.println(values); + System.out.println(translateText); + System.out.println("================="); + for (int i1 = 0; i1 < values.size(); i1++) { + enProperties.put(useKeys.get(i1), translateText.getJSONObject(i1).getString("Translation")); + } + } + } + if (!ObjectUtil.equals(enEexitsProperties, enProperties)) { + try (BufferedWriter writer = FileUtil.getWriter(enPropertiesFile, charset, false)) { + enProperties.store(writer, "i18n " + language); + } + } + } + + /** + * 过滤已经存在的语意 key 的中文 + * + * @param wordsSet 中文数组 + * @param cacheWords 已经存在的语意 key + * @param allResult 新的结果 + * @return 过滤后的中文 + */ + private Collection filterExists(Collection wordsSet, JSONObject cacheWords, JSONObject allResult) { + return wordsSet.stream() + .filter(s -> { + String existKey = checkExists(cacheWords, s); + if (existKey != null) { + //System.out.println("key 已经存在:" + existKey); + allResult.put(existKey, s); + return false; + } + return true; + }) + .collect(Collectors.toList()); + } + + /** + * 判断对应的中文是否存在语意 key + * + * @param jsonObject 已经存在的语意 key + * @param value 中文 + * @return 语意 key + */ + private String checkExists(JSONObject jsonObject, String value) { + for (Map.Entry entry : jsonObject.entrySet()) { + String entryValue = (String) entry.getValue(); + if (StrUtil.equals(entryValue, value)) { + // 值相同 + String key = entry.getKey(); + List split = StrUtil.split(key, StrUtil.DOT); + String last = CollUtil.getLast(split); + String md5 = SecureUtil.md5(value); + if (StrUtil.startWith(md5, last)) { + // 可以的后缀也等于值的 md5 前缀 + return key; + } + System.err.println("翻译前后的值相等但是 md5 不一致:" + md5 + "," + last); + } + } + return null; + } + + /** + * 增量更新缓存的 words + * + * @param jsonObject 需要更新的信息 + * @param append 是否增量更新 + */ + private void saveWords(JSONObject jsonObject, boolean append) { + File wordsFile = FileUtil.file(rootFile, "common/src/main/resources/i18n/words.json"); + if (append) { + JSONObject cacheWords = this.loadCacheWords(); + JSONObject updateAfter = cacheWords.clone(); + updateAfter.putAll(jsonObject); + if (!ObjectUtil.equals(MapUtil.sort(updateAfter), MapUtil.sort(cacheWords))) { + // 变动才保存 + // 根据 key 排序 + TreeMap sort = MapUtil.sort(updateAfter); + FileUtil.writeString(JSONArray.toJSONString(sort, JSONWriter.Feature.PrettyFormat), wordsFile, charset); + } + } else { + TreeMap sort = MapUtil.sort(jsonObject); + FileUtil.writeString(JSONArray.toJSONString(sort, JSONWriter.Feature.PrettyFormat), wordsFile, charset); + } + } + + private JSONObject loadCacheWords() { + File wordsFile = FileUtil.file(rootFile, "common/src/main/resources/i18n/words.json"); + if (wordsFile.exists()) { + return JSONObject.parseObject(FileUtil.readUtf8String(wordsFile)); + } + return new JSONObject(); + } + + /** + * 生成 key + * + * @param key 翻译后的key + * @param value 原始中文 + * @param jsonObject 所以的key + * @return i18n.{}.{} + */ + private String buildKey(String key, String value, JSONObject jsonObject) { + int md5IdLen = 4; + while (true) { + String md5 = SecureUtil.md5(value); + Assert.assertTrue("截取中文 md5 key 超范围:" + value, md5.length() >= md5IdLen); + md5 = md5.substring(0, md5IdLen); + String newKey = StrUtil.format("i18n.{}.{}", StrUtil.toUnderlineCase(key), md5); + if (jsonObject.containsKey(newKey)) { + md5IdLen += 2; + continue; + } + return newKey; + } + } + + /** + * 找到原始的中文字符串(大模型处理后面前后空格可能不存在) + * + * @param list list(翻译前) + * @param value value(翻译后) + * @return 原始的中文字符串 ,null 不存在 + */ + private String findOriginal(Collection list, String value) { + for (String s : list) { + if (StrUtil.equals(s, value) || StrUtil.equals(StrUtil.trim(s), value)) { + // 需要返回原始值,不能返回翻译后的值 + return s; + } + } + return null; + } + + /** + * 扫描指定目录下所有 java 文件(忽略 test、i18n-temp 目录) + * + * @param file 目录 + * @param consumer java 文件 + */ + public static void walkFile(File file, Consumer consumer) { + FileUtil.walkFiles(file, file1 -> { + if (FileUtil.isDirectory(file1)) { + return; + } + String path = FileUtil.getAbsolutePath(file1); + if (StrUtil.containsAny(path, "/test/", "\\test\\")) { + return; + } + if (StrUtil.equals("java", FileUtil.extName(file1))) { + consumer.accept(file1); + } + }); + } + + + /** + * 替换代码中的中文为方法调用 + * + * @param file java 文件 + * @param pattern 当前匹配的正则 + * @throws IOException io 异常 + */ + private void replaceQuotedChineseInFile(File file, Pattern pattern, Map chineseMap, Collection useKeys) throws Exception { + //String subPath = FileUtil.subPath(rootFile.getAbsolutePath(), file); + // 先存储于临时文件 + boolean modified = false; + StringWriter writer = new StringWriter(); + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), charset)) { + String line; + while ((line = reader.readLine()) != null) { + if (canIgnore(line)) { + writer.write(line); + } else { + // 匹配已经使用到的 key + for (Pattern messageKeyPattern : messageKeyPatterns) { + Matcher matcher = messageKeyPattern.matcher(line); + while (matcher.find()) { + String key = matcher.group(1); + if (!needIgnoreCase(key, line)) { + continue; + } + useKeys.add(key); + } + } + // 替换为 i18n key 或者方法 + StringBuffer modifiedLine = new StringBuffer(); + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String chineseText = matcher.group(); + if (needIgnoreCase(chineseText, line)) { + continue; + } + String unWrap = StrUtil.unWrap(chineseText, '\"'); + String key = chineseMap.get(unWrap); + if (key == null) { + throw new IllegalArgumentException("找不到 key:" + unWrap); + } + if (StrUtil.containsAny(line, JpomAnnotation)) { + //System.out.println("需要单独处理的:" + line); + matcher.appendReplacement(modifiedLine, String.format("\"%s\"", key)); + } else { + String path = FileUtil.getAbsolutePath(file); + if (StrUtil.containsAny(path, "/agent-transport/", "\\agent-transport\\")) { + matcher.appendReplacement(modifiedLine, String.format("TransportI18nMessageUtil.get(\"%s\")", key)); + } else { + matcher.appendReplacement(modifiedLine, String.format("I18nMessageUtil.get(\"%s\")", key)); + } + } + useKeys.add(key); + } + matcher.appendTail(modifiedLine); + String lineString = modifiedLine.toString(); + if (canIgnore(lineString)) { + throw new IllegalStateException("替换后成为忽略行:" + line + " \n" + lineString); + } + writer.write(lineString); + if (!modified) { + modified = !StrUtil.equals(line, lineString); + } + } + writer.write(FileUtil.getLineSeparator()); + } + } + if (modified) { + // 移动到原路径 + FileUtil.writeString(writer.toString(), file, charset); + } + } + + /** + * 验证拼接字符串 + *

+ * "aa"+abc+"xxxx" + * + * @param file java 文件 + * @param pattern 匹配的正则 + * @throws IOException io 异常 + */ + private void verifyDuplicates(File file, Pattern pattern) throws Exception { + try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { + String line; + while ((line = reader.readLine()) != null) { + if (canIgnore(line)) { + continue; + } + // + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String chineseText = matcher.group(); + if (needIgnoreCase(chineseText, line)) { + continue; + } + int count = StrUtil.count(chineseText, '\"'); + if (count > 2) { + System.err.println(line); + throw new IllegalArgumentException("重复的 key:" + chineseText); + } + } + } + } + } + + /** + * 提取文件中的中文 + * + * @param file java 文件 + * @param pattern 匹配的正则 + * @throws IOException io 异常 + */ + private void extractFile(File file, Pattern pattern, Collection wordsSet) throws Exception { + try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { + String line; + while ((line = reader.readLine()) != null) { + if (canIgnore(line)) { + continue; + } + // + { + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String chineseText = matcher.group(); + if (needIgnoreCase(chineseText, line)) { + continue; + } + wordsSet.add(StrUtil.unWrap(chineseText, '\"')); + System.out.println("匹配到的内容:" + chineseText + " -> " + line.trim()); + } + } + } + } + } + + /** + * 匹配到的结果是否需要忽略 + *

+ * 可能匹配到单字母(没有任何中文) + * + * @param text 匹配到的结果 + * @param line 整行 + * @return 是否需要忽略 (不是 中文 true) + */ + public static boolean needIgnoreCase(String text, String line) { + Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]"); + Matcher matcher = pattern.matcher(text); + boolean b = matcher.find(); + if (!b) { + //System.out.println("不包含汉字需要忽略:" + text + " ======" + line); + return true; + } + return false; + } + + /** + * 是否需要忽略 + * + * @param line 代码行 + * @return 是否需要忽略 + */ + public static boolean canIgnore(String line) throws ClassNotFoundException { + String trimLin = line.trim(); + if (StrUtil.startWithAny(trimLin, JpomAnnotation)) { + // jpom 特有注解 + return false; + } + if (StrUtil.startWithAny(trimLin, "@", "*", "//", "public static final String")) { + // 注解、注释、枚举、产量 + return true; + } + if (StrUtil.endWithAny(trimLin, "),")) { + // 枚举通用代码格式 + if (StrUtil.containsAny(trimLin, "() -> ")) { + // 枚举实现了 Supplier + return false; + } + // 假定枚举通用代码格式 + // System.out.println(trimLin); + return true; + } + return false; + } +} diff --git a/modules/common/src/test/java/i8n/RestoreI18nTest.java b/modules/common/src/test/java/i8n/RestoreI18nTest.java new file mode 100644 index 0000000000..8c6f1c12c0 --- /dev/null +++ b/modules/common/src/test/java/i8n/RestoreI18nTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package i8n; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Lombok; +import org.junit.Before; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 还原 i18n + * + * @author bwcx_jzy + * @since 2024/6/13 + */ +public class RestoreI18nTest { + private final Charset charset = CharsetUtil.CHARSET_UTF_8; + private File rootFile; + private File zhPropertiesFile; + + @Before + public void before() throws Exception { + File file = new File(""); + String rootPath = file.getAbsolutePath(); + rootFile = new File(rootPath).getParentFile(); + // + zhPropertiesFile = FileUtil.file(rootFile, "common/src/main/resources/i18n/messages_zh_CN.properties"); + } + + @Test + public void test() throws IOException { + Properties zhProperties = new Properties(); + try (BufferedReader inputStream = FileUtil.getReader(zhPropertiesFile, charset)) { + zhProperties.load(inputStream); + } + // 提取中文 + ExtractI18nTest.walkFile(rootFile, file1 -> { + try { + for (Pattern pattern : ExtractI18nTest.messageKeyPatterns) { + restoreChineseInFile(file1, pattern, zhProperties); + } + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + } + + private void restoreChineseInFile(File file, Pattern pattern, Properties zhProperties) throws Exception { + StringWriter writer = new StringWriter(); + boolean modified = false; + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), charset)) { + String line; + while ((line = reader.readLine()) != null) { + if (ExtractI18nTest.canIgnore(line)) { + writer.write(line); + } else { + // 将 i18n key 替换为中文 + StringBuffer modifiedLine = new StringBuffer(); + Matcher matcher = pattern.matcher(line); + if (StrUtil.containsAny(line, ExtractI18nTest.JpomAnnotation)) { + if (matcher.find()) { + String key = matcher.group(1); + if (ExtractI18nTest.needIgnoreCase(key, line)) { + String chineseText = (String) zhProperties.get(key); + if (chineseText == null) { + throw new IllegalArgumentException("找不到对应的中文:" + key); + } + // 完整替换 + modifiedLine.append(StrUtil.replace(line, String.format("\"%s\"", key), String.format("\"%s\"", chineseText))); + } else { + modifiedLine.append(line); + } + } else { + modifiedLine.append(line); + } + } else { + while (matcher.find()) { + String key = matcher.group(1); + if (!ExtractI18nTest.needIgnoreCase(key, line)) { + continue; + } + String chineseText = (String) zhProperties.get(key); + if (chineseText == null) { + throw new IllegalArgumentException("找不到对应的中文:" + key); + } + // 正则关键词替换 + matcher.appendReplacement(modifiedLine, String.format("\"%s\"", chineseText)); + } + matcher.appendTail(modifiedLine); + } + String lineString = modifiedLine.toString(); + writer.write(lineString); + if (!modified) { + modified = !StrUtil.equals(line, lineString); + } + } + writer.write(FileUtil.getLineSeparator()); + } + } + if (modified) { + // 移动到原路径 + FileUtil.writeString(writer.toString(), file, charset); + } + } +} diff --git a/modules/common/src/test/java/i8n/ScanOmissionsTest.java b/modules/common/src/test/java/i8n/ScanOmissionsTest.java new file mode 100644 index 0000000000..07076b2dd7 --- /dev/null +++ b/modules/common/src/test/java/i8n/ScanOmissionsTest.java @@ -0,0 +1,50 @@ +package i8n; + +import lombok.Lombok; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.nio.file.Files; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bwcx_jzy + * @since 2024/6/14 + */ +public class ScanOmissionsTest { + + @Test + public void test() { + File file = new File(""); + String rootPath = file.getAbsolutePath(); + File rootFile = new File(rootPath).getParentFile(); + + Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]"); + + ExtractI18nTest.walkFile(rootFile, file1 -> { + try { + omissions(file1, pattern); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + } + + private void omissions(File file, Pattern pattern) throws Exception { + try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { + String line; + while ((line = reader.readLine()) != null) { + if (ExtractI18nTest.canIgnore(line)) { + continue; + } + // + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + System.err.println(line); + } + } + } + } +} diff --git a/modules/common/src/test/java/i8n/api/BaiduBceRpcTexttransTest.java b/modules/common/src/test/java/i8n/api/BaiduBceRpcTexttransTest.java new file mode 100644 index 0000000000..3aa3fcf3fd --- /dev/null +++ b/modules/common/src/test/java/i8n/api/BaiduBceRpcTexttransTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package i8n.api; + +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.io.resource.ResourceUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.*; +import cn.hutool.system.SystemUtil; +import com.alibaba.fastjson2.JSONObject; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bwcx_jzy1 + * @since 2024/6/12 + */ +public class BaiduBceRpcTexttransTest { + + @Test + public void testTranslate() { + ArrayList strings = CollUtil.newArrayList("请输入正确的验证码", "请传入 body 参数", "开始准备项目重启:{} {}"); + JSONObject jsonObject = this.doTranslate(strings); + System.out.println(jsonObject); + } + + private boolean checkHasI18nKey(JSONObject jsonObject) { + Set keyed = jsonObject.keySet(); + for (String s : keyed) { + if (StrUtil.startWith(s, "i18n.")) { + // 提前失败 或者翻译失败 + //System.err.println("翻译失败或者提取失败," + s + "=" + jsonObject.get(s)); + return true; + } + } + return false; + } + + public JSONObject doTranslate(Collection words) { + while (true) { + JSONObject jsonObject = this.doTranslate2(words); + if (checkHasI18nKey(jsonObject)) { + System.err.println("翻译失败或者提取失败,自动重试," + jsonObject); + } else { + return jsonObject; + } + } + } + + + private JSONObject doTranslate2(Collection words) { + String token = this.getToken(); + UrlBuilder urlBuilder = UrlBuilder.of("https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"); + urlBuilder.addQuery("access_token", token); + + HttpRequest httpRequest = HttpUtil.createPost(urlBuilder.build()); + httpRequest.header(Header.CONTENT_TYPE, ContentType.JSON.getValue()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("model", "moonshot-v1-8k"); + jsonObject.put("temperature", 0.3); + JSONObject message = new JSONObject(); + message.put("role", "user"); + // + InputStream inputStream = ResourceUtil.getStream("baidubce_translate.txt"); + String string = IoUtil.readUtf8(inputStream); + // + JSONObject from = new JSONObject(); + for (String value : words) { + String key; + do { + key = StrUtil.format("i18n.{}", RandomUtil.randomStringUpper(6)); + } while (from.containsKey(key)); + from.put(key, value); + } + string = StrUtil.format(string, MapUtil.of("REQUEST_STR", from.toString())); + //System.out.println(string); + message.put("content", string); + jsonObject.put("messages", CollUtil.newArrayList(message)); + // + httpRequest.body(jsonObject.toString()); + String result = httpRequest.thenFunction(httpResponse -> { + String body = httpResponse.body(); + JSONObject jsonObject1 = JSONObject.parseObject(body); + if (jsonObject1.getIntValue("error_code") != 0) { + Assert.fail(jsonObject1.getString("error_msg")); + } + return jsonObject1.getString("result"); + }); + String patternString = "(?s)```json\\s*([^`]*?)\\s*```"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(result); + // + JSONObject jsonObject1 = null; + while (matcher.find()) { + //System.out.println(result); + String jsonContent = matcher.group(1); + jsonObject1 = JSONObject.parseObject(jsonContent); + if (!this.checkHasI18nKey(jsonObject1)) { + return jsonObject1; + } + } + Assert.assertNotNull("翻译失败或者提取失败", jsonObject1); + return jsonObject1; + } + + private String getToken() { + File file = new File(""); + String absolutePath = FileUtil.getAbsolutePath(file); + File tokenCache = FileUtil.file(absolutePath, ".baidubce.token"); + if (tokenCache.exists()) { + JSONObject cacheData = JSONObject.parseObject(FileUtil.readUtf8String(tokenCache)); + int expiresIn = cacheData.getIntValue("expires_in"); + if (SystemClock.now() / 1000L < expiresIn) { + //System.out.println("token 缓存有效,直接使用"); + return cacheData.getString("access_token"); + } + } + JSONObject cacheData = this.doTokenByApi(tokenCache); + return cacheData.getString("access_token"); + } + + /** + * https://cloud.baidu.com/doc/WENXINWORKSHOP/s/7lpch74jm + * + * @return token + */ + private JSONObject doTokenByApi(File file) { + String bceCi = SystemUtil.get("JPOM_TRANSLATE_BAIDUBCE_CI", StrUtil.EMPTY); + String bceCs = SystemUtil.get("JPOM_TRANSLATE_BAIDUBCE_CS", StrUtil.EMPTY); + Assert.assertNotEquals("请配置百度千帆大模型 client_id[JPOM_TRANSLATE_BAIDUBCE_CI]", bceCi, StrUtil.EMPTY); + Assert.assertNotEquals("请配置百度千帆大模型 client_secret[JPOM_TRANSLATE_BAIDUBCE_CS]", bceCs, StrUtil.EMPTY); + + HttpRequest httpRequest = HttpUtil.createPost("https://aip.baidubce.com/oauth/2.0/token"); + httpRequest.form("grant_type", "client_credentials") + .form("client_id", bceCi) + .form("client_secret", bceCs); + httpRequest.header(Header.CONTENT_TYPE, ContentType.JSON.getValue()); + httpRequest.header(Header.ACCEPT, ContentType.JSON.getValue()); + JSONObject json = httpRequest.thenFunction(httpResponse -> { + int status = httpResponse.getStatus(); + String body = httpResponse.body(); + Assert.assertEquals("token 生成异常," + body, HttpStatus.HTTP_OK, status); + return JSONObject.parse(body); + }); + String token = json.getString("access_token"); + int expiresIn = json.getIntValue("expires_in"); + System.out.println("获取最新的 token"); + JSONObject cacheData = new JSONObject(); + cacheData.put("access_token", token); + cacheData.put("expires_in", expiresIn + SystemClock.now() / 1000L); + // + FileUtil.writeUtf8String(cacheData.toString(), file); + return cacheData; + } + + @Test + public void doToken() { + System.out.println(this.getToken()); + } +} diff --git a/modules/common/src/test/java/i8n/api/VolcTranslateApiTest.java b/modules/common/src/test/java/i8n/api/VolcTranslateApiTest.java new file mode 100644 index 0000000000..4ec675ca06 --- /dev/null +++ b/modules/common/src/test/java/i8n/api/VolcTranslateApiTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package i8n.api; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.net.URLEncodeUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.system.SystemUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; + +/** + * https://www.volcengine.com/docs/4640/65067 + * + * @author bwcx_jzy + * @since 2024/6/11 + */ +public class VolcTranslateApiTest { + + private final String region; + private final String service; + private final String schema; + private final String host; + private final String path; + private final String ak; + private final String sk; + + + private VolcTranslateApiTest(String region, String service, String schema, String host, String path, String ak, String sk) { + this.region = region; + this.service = service; + this.host = host; + this.schema = schema; + this.path = path; + this.ak = ak; + this.sk = sk; + } + + public VolcTranslateApiTest() { + String volcSk = SystemUtil.get("JPOM_TRANSLATE_VOLC_SK", StrUtil.EMPTY); + String volcAk = SystemUtil.get("JPOM_TRANSLATE_VOLC_AK", StrUtil.EMPTY); + Assert.assertNotEquals("请配置火山翻译 SecretAccessKey[JPOM_TRANSLATE_VOLC_SK]", volcSk, StrUtil.EMPTY); + Assert.assertNotEquals("请配置火山翻译 AccessKeyID[JPOM_TRANSLATE_VOLC_AK]", volcAk, StrUtil.EMPTY); + + this.region = "cn-north-1"; + this.service = "translate"; + this.host = "translate.volcengineapi.com"; + this.schema = "https"; + this.path = "/"; + this.ak = volcAk; + this.sk = volcSk; + } + + + @Test + public void test2() throws Exception { + VolcTranslateApiTest translateApi = new VolcTranslateApiTest(); + JSONArray translateText = translateApi.translate("zh", "en", CollUtil.newArrayList("你好", "世界")); + System.out.println(translateText); + } + + public JSONArray translate(String source, String target, List textList) throws Exception { + + + String action = "TranslateText"; + String version = "2020-06-01"; + + HashMap queryMap = new HashMap<>(0); + + HashMap query2Map = new HashMap<>(3); + query2Map.put("SourceLanguage", source); + query2Map.put("TargetLanguage", target); + query2Map.put("TextList", textList); + + String jsonStr = JSONObject.toJSONString(query2Map); + String request = this.doRequest("POST", queryMap, jsonStr.getBytes(), action, version); + JSONObject jsonObject = JSONObject.parse(request); + Object error = jsonObject.getByPath("ResponseMetadata.Error"); + if (error != null) { + throw new IllegalStateException("翻译异常:" + error); + } + return jsonObject.getJSONArray("TranslationList"); + } + + private String doRequest(String method, Map queryList, byte[] body, String action, String version) throws Exception { + if (body == null) { + body = new byte[0]; + } + String xContentSha256 = SecureUtil.sha256().digestHex(body); + + DateTime dateTime = DateTime.now().setTimeZone(TimeZone.getTimeZone("GMT")); + String xDate = dateTime.toString("yyyyMMdd'T'HHmmss'Z'"); + + String shortXDate = dateTime.toString(DatePattern.PURE_DATE_FORMAT); + + String contentType = "application/json"; + + String signHeader = "host;x-date;x-content-sha256;content-type"; + + + SortedMap realQueryList = new TreeMap<>(queryList); + realQueryList.put("Action", action); + realQueryList.put("Version", version); + StringBuilder querySB = new StringBuilder(); + for (String key : realQueryList.keySet()) { + querySB.append(signStringEncoder(key)).append("=").append(signStringEncoder(realQueryList.get(key))).append("&"); + } + querySB.deleteCharAt(querySB.length() - 1); + + String canonicalStringBuilder = method + "\n" + path + "\n" + querySB + "\n" + + "host:" + host + "\n" + + "x-date:" + xDate + "\n" + + "x-content-sha256:" + xContentSha256 + "\n" + + "content-type:" + contentType + "\n" + + "\n" + + signHeader + "\n" + + xContentSha256; + + //System.out.println(canonicalStringBuilder); + + String hashcanonicalString = SecureUtil.sha256().digestHex(canonicalStringBuilder.getBytes()); + String credentialScope = shortXDate + "/" + region + "/" + service + "/request"; + String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString; + + byte[] signKey = genSigningSecretKeyV4(sk, shortXDate, region, service); + String signature = HexUtil.encodeHexStr(hmacSHA256(signKey, signString)); + + Method method1 = Method.valueOf(method); + HttpRequest request = HttpUtil.createRequest(method1, schema + "://" + host + path + "?" + querySB); + + + request.header("Host", host); + request.header("X-Date", xDate); + request.header("X-Content-Sha256", xContentSha256); + request.header("Content-Type", contentType); + request.header("Authorization", "HMAC-SHA256" + + " Credential=" + ak + "/" + credentialScope + + ", SignedHeaders=" + signHeader + + ", Signature=" + signature); + if (!Objects.equals(method, "GET")) { + request.body(body); + } + return request.thenFunction(HttpResponse::body); + } + + private String signStringEncoder(String source) { + return URLEncodeUtil.encodeQuery(source); + } + + public static byte[] hmacSHA256(byte[] key, String content) throws Exception { + return SecureUtil.hmacSha256(key).digest(content); + } + + private byte[] genSigningSecretKeyV4(String secretKey, String date, String region, String service) throws Exception { + byte[] kDate = hmacSHA256((secretKey).getBytes(), date); + byte[] kRegion = hmacSHA256(kDate, region); + byte[] kService = hmacSHA256(kRegion, service); + return hmacSHA256(kService, "request"); + } +} diff --git a/modules/common/src/test/java/i8n/web/CheckWebI18nKeyTest.java b/modules/common/src/test/java/i8n/web/CheckWebI18nKeyTest.java new file mode 100644 index 0000000000..9511a89c44 --- /dev/null +++ b/modules/common/src/test/java/i8n/web/CheckWebI18nKeyTest.java @@ -0,0 +1,96 @@ +package i8n.web; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.Lombok; +import org.junit.Before; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bwcx_jzy + * @since 2024/6/18 + */ +public class CheckWebI18nKeyTest { + + private File rootFile; + + @Before + public void beforeTest() { + File file = new File(""); + String rootPath = file.getAbsolutePath(); + rootFile = new File(rootPath).getParentFile().getParentFile(); + rootFile = FileUtil.file(rootFile, "web-vue"); + } + + @Test + public void test() { + JSONObject zhCn = DiffWebI18nTest.loadJson(rootFile, "zh_cn"); + for (Map.Entry entry : zhCn.entrySet()) { + Object value = entry.getValue(); + if (value.toString().length() == 1) { + System.out.println(StrUtil.format("错误的值:{}={}", entry.getKey(), value)); + } + } + } + + @Test + public void test2() { + Set keys = new HashSet<>(); + DiffWebI18nTest.walkFile(rootFile, file1 -> { + try { + keys.addAll(matchFile(file1, DiffWebI18nTest.pattern)); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + JSONObject zhCn = DiffWebI18nTest.loadJson(rootFile, "zh_cn"); + JSONObject zhCn2 = DiffWebI18nTest.loadJson(rootFile, "zh_cn2"); + for (String key : keys) { + if (zhCn.containsKey(key)) { + continue; + } + if (zhCn2.containsKey(key)) { + zhCn.put(key, zhCn2.get(key)); + continue; + } + System.err.println(StrUtil.format("缺少:{}", key)); + } + //File file1 = FileUtil.file(rootFile, "/src/i18n/locales/zh_cn.json"); + //FileUtil.writeString(JSONArray.toJSONString(zhCn, JSONWriter.Feature.PrettyFormat), file1, CharsetUtil.UTF_8); + } + + private Set matchFile(File file, Pattern pattern) throws Exception { + Charset charset = CharsetUtil.CHARSET_UTF_8; + Set keys = new HashSet<>(); + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), charset)) { + String line; + while ((line = reader.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String key = matcher.group(1); + int start = matcher.start(1); + String subPre = StrUtil.subPre(line, start); + if (StrUtil.endWithAny(subPre, "split('", "import('", "emit('", "onSubmit('", "mount('", "reject('", "component('", "recoverNet('", "executionRequest('", "commit('")) { + // 特殊方法 + continue; + } + keys.add(key); + } + } + } + return keys; + } + +} diff --git a/modules/common/src/test/java/i8n/web/DiffWebI18nTest.java b/modules/common/src/test/java/i8n/web/DiffWebI18nTest.java new file mode 100644 index 0000000000..8cace5463e --- /dev/null +++ b/modules/common/src/test/java/i8n/web/DiffWebI18nTest.java @@ -0,0 +1,191 @@ +package i8n.web; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import lombok.Lombok; +import org.junit.Assert; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.*; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bwcx_jzy + * @since 2024/6/18 + */ +public class DiffWebI18nTest { + + public static final Pattern pattern = Pattern.compile("t\\('(.*?)'.*?\\)"); + + @Test + public void test() throws Exception { + File file = new File(""); + String rootPath = file.getAbsolutePath(); + File rootFile = new File(rootPath).getParentFile().getParentFile(); + rootFile = FileUtil.file(rootFile, "web-vue"); + JSONObject zhCn = loadJson(rootFile, "zh_cn2"); + Map cacheKey = new HashMap<>(); + this.generateWaitMap(zhCn, "", cacheKey); + Collection values = cacheKey.values(); + HashSet set = new HashSet<>(values); + Map chinese = new HashMap<>(); + for (String s : set) { + int len = 8; + String md5 = SecureUtil.md5(s); + while (true) { + String newKey = StrUtil.format("i18n.{}", StrUtil.sub(md5, 0, len += 2)); + String existsKey = chinese.get(s); + if (existsKey == null) { + chinese.put(s, newKey); + break; + } + if (StrUtil.equals(newKey, existsKey)) { + break; + } + Assert.assertTrue("截取中文 md5 key 超范围:" + s, md5.length() >= len); + } + } + // + Map newKeyMap = new HashMap<>(chinese.size()); + cacheKey.forEach((key, value) -> { + String newKey = chinese.get(value); + newKeyMap.put(key, newKey); + }); + walkFile(rootFile, file1 -> { + try { + matchFile(file1, newKeyMap); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + // + Map newMap = new TreeMap<>(); + chinese.forEach((key, value) -> { + newMap.put(value, key); + }); + File file1 = FileUtil.file(rootFile, "/src/i18n/locales/zh_cn.json"); + FileUtil.writeString(JSONArray.toJSONString(newMap, JSONWriter.Feature.PrettyFormat), file1, CharsetUtil.UTF_8); + } + + + private void generateWaitMap(JSONObject jsonObject, String rootPath, Map cache) throws Exception { + for (Map.Entry entry : jsonObject.entrySet()) { + String key = entry.getKey(); + String keyPath = rootPath.isEmpty() ? key : rootPath + "." + key; + Object value = entry.getValue(); + if (value instanceof JSONObject) { + generateWaitMap((JSONObject) value, keyPath, cache); + } else if (value instanceof String) { + cache.put(keyPath, (String) value); + } else { + System.err.println("不支持的数据格式:" + keyPath); + } + } + } + + public static JSONObject loadJson(File rootFile, String tag) { + File file1 = FileUtil.file(rootFile, "/src/i18n/locales/" + tag + ".json"); + if (!file1.exists()) { + return new JSONObject(); + } + return JSONObject.parseObject(FileUtil.readUtf8String(file1)); + } + + + /** + * 扫描指定目录下所有 java 文件(忽略 test、i18n-temp 目录) + * + * @param file 目录 + * @param consumer java 文件 + */ + public static void walkFile(File file, Consumer consumer) { + FileUtil.walkFiles(file, file1 -> { + if (FileUtil.isDirectory(file1)) { + return; + } + String path = FileUtil.getAbsolutePath(file1); + String normalize = FileUtil.normalize(path); + if (StrUtil.containsAny(normalize, "/node_modules/")) { + return; + } + if (StrUtil.equalsAny(FileUtil.extName(file1), "ts", "js", "vue")) { + consumer.accept(file1); + } + }); + } + + private void matchFile(File file, Map newKeyMap) throws Exception { + StringWriter writer = new StringWriter(); + boolean modified = false; + Charset charset = CharsetUtil.CHARSET_UTF_8; + try (BufferedReader reader = Files.newBufferedReader(file.toPath(), charset)) { + String line; + while ((line = reader.readLine()) != null) { + Matcher matcher = DiffWebI18nTest.pattern.matcher(line); + String newLine = line; + while (matcher.find()) { + String key = matcher.group(1); + int start = matcher.start(1); + String subPre = StrUtil.subPre(line, start); + if (StrUtil.endWithAny(subPre, "split('", "import('", "emit('", "onSubmit('", "mount('", "reject('", "component('", "recoverNet('", "executionRequest('", "commit('")) { + // 特殊方法 + continue; + } + if (StrUtil.startWith(key, "i18n.")) { + // 忽略 i18n.{} + continue; + } + String newKey = newKeyMap.get(key); + if (newKey == null) { + throw new IllegalStateException("没有找到对应的 key:\n" + key + "\n" + line); + } + newLine = StrUtil.replace(line, String.format("'%s'", key), String.format("'%s'", newKey)); + System.out.println(key + " " + newKey); + } + // + writer.write(newLine); + if (!modified) { + modified = !StrUtil.equals(line, newLine); + } + writer.write(FileUtil.getLineSeparator()); + } + } + if (modified) { + // 移动到原路径 + FileUtil.writeString(writer.toString(), file, charset); + } + } + + //@Test + public void test2() { + String line = "{{ text === 'GLOBAL' ? $t('i18n.8DBEBAAE.1') : $t('pages.system.workspace-env.919267cc') }}"; + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String key = matcher.group(1); + System.out.println(key + " "); + int start = matcher.start(1); + String subPre = StrUtil.subPre(line, start); + if (StrUtil.endWithAny(subPre, "split('", "import('", "emit('", "onSubmit('", "mount('", "reject('", "component('", "recoverNet('", "executionRequest('", "commit('")) { + // 特殊方法 + continue; + } + if (StrUtil.startWith(key, "i18n.")) { + // 忽略 i18n.{} + continue; + } + + } + } +} diff --git a/modules/common/src/test/java/i8n/web/TranslateWebI18nTest.java b/modules/common/src/test/java/i8n/web/TranslateWebI18nTest.java new file mode 100644 index 0000000000..d5384e1139 --- /dev/null +++ b/modules/common/src/test/java/i8n/web/TranslateWebI18nTest.java @@ -0,0 +1,179 @@ +package i8n.web; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.PageUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import i8n.api.VolcTranslateApiTest; +import org.junit.Test; + +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2024/6/17 + */ +public class TranslateWebI18nTest { + + Map waitMap = new HashMap<>(); + + // 接口限制、不能超过 16 + int pageSize = 16; + + + @Test + public void test() throws Exception { + File file = new File(""); + String rootPath = file.getAbsolutePath(); + File rootFile = new File(rootPath).getParentFile().getParentFile(); + + JSONObject zhCn = this.loadJson(rootFile, "zh_cn"); + + + Map map = new HashMap<>(); + map.put("en_us", "en"); + map.put("zh_hk", "zh-Hant-hk"); + map.put("zh_tw", "zh-Hant-tw"); + for (Map.Entry entry : map.entrySet()) { + + this.syncZhToJson(rootFile, zhCn, entry.getKey(), entry.getValue()); + } + } + + private void syncZhToJson(File rootFile, JSONObject zhCn, String tag, String toLanguage) throws Exception { + waitMap.clear(); + JSONObject cache = this.loadJson(rootFile, tag); + // + JSONObject jsonObject1 = new JSONObject(); + this.generateWaitMap(zhCn, "", jsonObject1, cache); + // + this.doTranslate(jsonObject1, toLanguage); + jsonObject1 = this.sort(jsonObject1); + File file1 = FileUtil.file(rootFile, "web-vue/src/i18n/locales/" + tag + ".json"); + FileUtil.writeString(JSONArray.toJSONString(jsonObject1, JSONWriter.Feature.PrettyFormat), file1, CharsetUtil.CHARSET_UTF_8); + } + + private JSONObject sort(JSONObject jsonObject) { + Set> entries = jsonObject.entrySet(); + for (Map.Entry entry : entries) { + Object value = entry.getValue(); + if (value instanceof String) { + // + } else if (value instanceof JSONObject) { + value = this.sort((JSONObject) value); + entry.setValue(value); + } + } + + return new JSONObject(MapUtil.sort(jsonObject)); + } + + private void doTranslate(JSONObject result, String toLanguage) throws Exception { + Set> entries = waitMap.entrySet(); + VolcTranslateApiTest translateApi = new VolcTranslateApiTest(); + int total = CollUtil.size(entries); + int page = PageUtil.totalPage(total, pageSize); + for (int i = PageUtil.getFirstPageNo(); i <= page; i++) { + int start = PageUtil.getStart(i, pageSize); + int end = PageUtil.getEnd(i, pageSize); + + List> values2 = CollUtil.sub(entries, start, end); + if (CollUtil.isEmpty(values2)) { + continue; + } + List collected = values2.stream().map(entry -> (String) entry.getValue()).collect(Collectors.toList()); + + JSONArray translateText = translateApi.translate("zh", toLanguage, collected); + System.out.println(collected); + System.out.println(translateText); + System.out.println("================="); + for (int i1 = 0; i1 < collected.size(); i1++) { + String keyPath = values2.get(i1).getKey(); + String translation = translateText.getJSONObject(i1).getString("Translation"); + this.setData(result, keyPath, translation); + } + } + } + + private void setData(JSONObject result, String keyPath, Object data) { + List split = StrUtil.splitTrim(keyPath, "."); + JSONObject groupValue = result; + for (int i = 0; i < split.size() - 1; i++) { + String key = split.get(i); + JSONObject groupValue2 = groupValue.getJSONObject(key); + if (groupValue2 == null) { + groupValue2 = new JSONObject(); + groupValue.put(key, groupValue2); + } + groupValue = groupValue2; + } + groupValue.put(CollUtil.getLast(split), data); + } + + private JSONObject loadJson(File rootFile, String tag) { + File file1 = FileUtil.file(rootFile, "web-vue/src/i18n/locales/" + tag + ".json"); + if (!file1.exists()) { + return new JSONObject(); + } + return JSONObject.parseObject(FileUtil.readUtf8String(file1)); + } + + private void generateWaitMap(JSONObject jsonObject, String rootPath, JSONObject result, JSONObject cache) throws Exception { + for (Map.Entry entry : jsonObject.entrySet()) { + String key = entry.getKey(); + String keyPath = rootPath.isEmpty() ? key : rootPath + "." + key; + Object value = entry.getValue(); + if (value instanceof JSONObject) { + generateWaitMap((JSONObject) value, keyPath, result, cache); + } else if (value instanceof String) { + doGenerateWaitJson(jsonObject, rootPath, result, cache); + } else { + System.err.println("不支持的数据格式:" + keyPath); + } + } + } + + private void doGenerateWaitJson(JSONObject jsonObject, String rootPath, JSONObject result, JSONObject cache) throws Exception { + Set keySet = jsonObject.keySet(); + Collection values = jsonObject.values(); + + int total = CollUtil.size(keySet); + int page = PageUtil.totalPage(total, pageSize); + for (int i = PageUtil.getFirstPageNo(); i <= page; i++) { + int start = PageUtil.getStart(i, pageSize); + int end = PageUtil.getEnd(i, pageSize); + + List values2 = CollUtil.sub(values, start, end); + List keySet2 = CollUtil.sub(keySet, start, end); + + for (int i1 = 0; i1 < keySet2.size(); i1++) { + String key = keySet2.get(i1); + String keyPath = rootPath + "." + key; + Object propertyVal = BeanUtil.getProperty(cache, keyPath); + if (propertyVal instanceof String) { + // 已经存在 + setData(result, keyPath, propertyVal); + continue; + } else { + //System.err.println("数据类型不正确:" + keyPath + " " + propertyVal); + } + Object value = values2.get(i1); + if ((value instanceof String)) { + waitMap.put(keyPath, value); + } else if (value instanceof JSONObject) { + generateWaitMap((JSONObject) value, rootPath, result, cache); + } else { + throw new Exception("不支持的数据格式:" + keyPath + " " + value); + } + } + } + } +} diff --git a/modules/common/src/test/resources/baidubce_translate.txt b/modules/common/src/test/resources/baidubce_translate.txt new file mode 100644 index 0000000000..7cde723071 --- /dev/null +++ b/modules/common/src/test/resources/baidubce_translate.txt @@ -0,0 +1,15 @@ +You are the service that converts a user request JSON into a new (user-expected) JSON object based on the following JavaScript-defined JSON object: + +``` +// 将下面json中的 根据`值`的含义将 `key` 转为语义化且简短的首字母为小写的小驼峰英文变量名替换无意义字符串 +// 此处进行替换,禁止出现 k1 k2 k3 +const template = { + "key1": "string1", + "key2": "string2", +} +``` + +The following is a user request: +``` +const template = {REQUEST_STR} +``` diff --git a/modules/common/src/test/resources/test.bat b/modules/common/src/test/resources/test.bat index 4d413bf42c..ae99a07328 100644 --- a/modules/common/src/test/resources/test.bat +++ b/modules/common/src/test/resources/test.bat @@ -1,24 +1,11 @@ @REM -@REM The MIT License (MIT) -@REM -@REM Copyright (c) 2019 码之科技工作室 -@REM -@REM Permission is hereby granted, free of charge, to any person obtaining a copy of -@REM this software and associated documentation files (the "Software"), to deal in -@REM the Software without restriction, including without limitation the rights to -@REM use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -@REM the Software, and to permit persons to whom the Software is furnished to do so, -@REM subject to the following conditions: -@REM -@REM The above copyright notice and this permission notice shall be included in all -@REM copies or substantial portions of the Software. -@REM -@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -@REM FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -@REM COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -@REM IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -@REM CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +@REM Copyright (c) 2019 Of Him Code Technology Studio +@REM Jpom is licensed under Mulan PSL v2. +@REM You can use this software according to the terms and conditions of the Mulan PSL v2. +@REM You may obtain a copy of Mulan PSL v2 at: +@REM http://license.coscl.org.cn/MulanPSL2 +@REM THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +@REM See the Mulan PSL v2 for more details. @REM :: ���û������� diff --git a/modules/common/src/test/resources/test.yml b/modules/common/src/test/resources/test.yml new file mode 100644 index 0000000000..09e1b66577 --- /dev/null +++ b/modules/common/src/test/resources/test.yml @@ -0,0 +1,13 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# test +a: + b: t diff --git a/modules/server/Dockerfile b/modules/server/Dockerfile index f21a296cf7..2039150e03 100644 --- a/modules/server/Dockerfile +++ b/modules/server/Dockerfile @@ -1,52 +1,87 @@ # -# The MIT License (MIT) -# -# Copyright (c) 2019 码之科技工作室 -# -# 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. +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. # -FROM centos:7 +# syntax = docker/dockerfile:experimental + +FROM maven:3.9.6-eclipse-temurin-8 as builder +WORKDIR /target/dependency +COPY . . + +VOLUME ["/root/.m2","/target/dependency/web-vue/node_modules"] +# 多次 builder 不同的版本号 +ARG TEMP_VERSION="" +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 18.19.0 + +RUN set -eux; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + BINARY_ARCH='arm64'; \ + ;; \ + amd64|x86_64) \ + BINARY_ARCH='x64'; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + curl -LfsSo /opt/node-v${NODE_VERSION}-linux-${BINARY_ARCH}.tar.gz https://npmmirror.com/mirrors/node/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${BINARY_ARCH}.tar.gz \ + && tar -zxvf /opt/node-v${NODE_VERSION}-linux-${BINARY_ARCH}.tar.gz -C /usr/local --strip-components=1 \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ + && npm config set registry https://registry.npmmirror.com/ -ENV LANG en_US.utf8 +RUN --mount=type=cache,target=/target/dependency/web-vue/node_modules cd web-vue && npm install && npm run build -ENV JPOM_HOME /usr/local/jpom-server -ENV JPOM_PKG server-2.8.0-release.zip +RUN --mount=type=cache,target=/root/.m2 mvn -B -e -T 1C clean package -pl modules/server -am -Dmaven.test.skip=true -Dmaven.compile.fork=true -s script/settings.xml -ADD jdk-8u202-linux-x64.tar.gz /usr/local/java/ -ENV JAVA_HOME /usr/local/java/jdk1.8.0_202 -ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar -ENV PATH $JAVA_HOME/bin:$PATH +FROM maven:3.9.6-eclipse-temurin-8 -ADD apache-maven-3.8.4-bin.tar.gz /usr/local/maven/ -ENV MAVEN_HOME /usr/local/maven/apache-maven-3.8.4 -ENV PATH $MAVEN_HOME/bin:$PATH +ARG BUILD_DATE +ARG JPOM_VERSION +ARG TEMP_VERSION="" +ARG DEPENDENCY=/target/dependency -RUN yum install -y unzip +LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}" +LABEL maintainer="bwcx-jzy " +LABEL documentation="https://jpom.top" -RUN mkdir -p $JPOM_HOME -COPY $JPOM_PKG $JPOM_HOME -RUN unzip -o $JPOM_HOME/$JPOM_PKG -d $JPOM_HOME -RUN chmod +x $JPOM_HOME/Server.sh -RUN rm -rf $JPOM_HOME/$JPOM_PKG +ENV JPOM_HOME /usr/local/jpom-server +ENV JPOM_PKG_VERSION ${JPOM_VERSION}${TEMP_VERSION} +ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release +ENV JPOM_DATA_PATH ${JPOM_HOME}/data +ENV JPOM_LOG_PATH ${JPOM_HOME}/logs WORKDIR $JPOM_HOME +COPY --from=builder ${DEPENDENCY}/modules/server/target/${JPOM_PKG} ${JPOM_HOME} + +RUN apt-get install -y git + +# 时区 +ENV TZ Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# 数据目录 +ENV jpom.path ${JPOM_DATA_PATH} + +VOLUME $JPOM_DATA_PATH $JPOM_LOG_PATH + EXPOSE 2122 -ENTRYPOINT ["/bin/sh", "Server.sh", "start"] +HEALTHCHECK CMD curl -X POST -f http://localhost:2122/check-system || exit 1 + +ENTRYPOINT ["/bin/bash", "./bin/BlockListener.sh"] + + diff --git a/modules/server/DockerfileBeta b/modules/server/DockerfileBeta new file mode 100644 index 0000000000..0195e8f235 --- /dev/null +++ b/modules/server/DockerfileBeta @@ -0,0 +1,56 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +FROM maven:3.9.6-eclipse-temurin-8 + +ARG BUILD_DATE +LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}" +LABEL maintainer="bwcx-jzy " +LABEL documentation="https://jpom.top" + +ENV JPOM_HOME /usr/local/jpom-server +ENV JPOM_PKG_VERSION 2.11.6.6 +ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz +ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1 + +ENV JPOM_DATA_PATH ${JPOM_HOME}/data +ENV JPOM_LOG_PATH ${JPOM_HOME}/logs +# 数据目录 +ENV jpom.path ${JPOM_DATA_PATH} + +WORKDIR ${JPOM_HOME} + +RUN apt-get install -y git + +# 时区 +ENV TZ Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone + +RUN mkdir -p ${JPOM_HOME} +# +RUN curl -LfSo ${JPOM_HOME}/${JPOM_PKG} https://download.jpom.top/beta/${JPOM_PKG_VERSION}/${JPOM_PKG} +RUN curl -LfsSo ${JPOM_HOME}/${SHA1_NAME} https://download.jpom.top/beta/${JPOM_PKG_VERSION}/${SHA1_NAME} && \ + ESUM=`cat ${JPOM_HOME}/${SHA1_NAME}` && \ + echo "${ESUM} ${JPOM_HOME}/${JPOM_PKG}" | sha1sum -c -; +RUN tar -zxvf ${JPOM_HOME}/${JPOM_PKG} -C ${JPOM_HOME} +RUN rm -rf ${JPOM_HOME}/${JPOM_PKG} + +# 将配置文件暂存默认目录,避免无法挂载 +RUN mv $JPOM_HOME/conf $JPOM_HOME/conf_default + +# 健康检查 +HEALTHCHECK CMD curl -X POST -f http://localhost:2122/check-system || exit 1 + +EXPOSE 2122 + +ENTRYPOINT ["/bin/bash", "./bin/BlockListener.sh"] + + + diff --git a/modules/server/DockerfileBetaJdk17 b/modules/server/DockerfileBetaJdk17 new file mode 100644 index 0000000000..228d9faec3 --- /dev/null +++ b/modules/server/DockerfileBetaJdk17 @@ -0,0 +1,58 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +FROM maven:3.9.6-sapmachine-17 + +ARG BUILD_DATE +LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}" +LABEL maintainer="bwcx-jzy " +LABEL documentation="https://jpom.top" + +ENV JPOM_HOME /usr/local/jpom-server +ENV JPOM_PKG_VERSION 2.11.6.6 +ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz +ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1 + +ENV JPOM_DATA_PATH ${JPOM_HOME}/data +ENV JPOM_LOG_PATH ${JPOM_HOME}/logs +# 数据目录 +ENV jpom.path ${JPOM_DATA_PATH} + +WORKDIR ${JPOM_HOME} + +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends git; + +# 时区 +ENV TZ Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone + +RUN mkdir -p ${JPOM_HOME} +# +RUN curl -LfSo ${JPOM_HOME}/${JPOM_PKG} https://download.jpom.top/beta/${JPOM_PKG_VERSION}/${JPOM_PKG} +RUN curl -LfsSo ${JPOM_HOME}/${SHA1_NAME} https://download.jpom.top/beta/${JPOM_PKG_VERSION}/${SHA1_NAME} && \ + ESUM=`cat ${JPOM_HOME}/${SHA1_NAME}` && \ + echo "${ESUM} ${JPOM_HOME}/${JPOM_PKG}" | sha1sum -c -; +RUN tar -zxvf ${JPOM_HOME}/${JPOM_PKG} -C ${JPOM_HOME} +RUN rm -rf ${JPOM_HOME}/${JPOM_PKG} + +# 将配置文件暂存默认目录,避免无法挂载 +RUN mv $JPOM_HOME/conf $JPOM_HOME/conf_default + +# 健康检查 +HEALTHCHECK CMD curl -X POST -f http://localhost:2122/check-system || exit 1 + +EXPOSE 2122 + +ENTRYPOINT ["/bin/bash", "./bin/BlockListener.sh"] + + + diff --git a/modules/server/DockerfileRelease b/modules/server/DockerfileRelease new file mode 100644 index 0000000000..9f34fad1ca --- /dev/null +++ b/modules/server/DockerfileRelease @@ -0,0 +1,56 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +FROM maven:3.9.6-eclipse-temurin-8 + +ARG BUILD_DATE +LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}" +LABEL maintainer="bwcx-jzy " +LABEL documentation="https://jpom.top" + +ENV JPOM_HOME /usr/local/jpom-server +ENV JPOM_PKG_VERSION 2.11.6 +ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz +ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1 + +ENV JPOM_DATA_PATH ${JPOM_HOME}/data +ENV JPOM_LOG_PATH ${JPOM_HOME}/logs +# 数据目录 +ENV jpom.path ${JPOM_DATA_PATH} + +WORKDIR ${JPOM_HOME} + +RUN apt-get install -y git + +# 时区 +ENV TZ Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone + +RUN mkdir -p ${JPOM_HOME} +# +RUN curl -LfSo ${JPOM_HOME}/${JPOM_PKG} https://download.jpom.top/release/${JPOM_PKG_VERSION}/${JPOM_PKG} +RUN curl -LfsSo ${JPOM_HOME}/${SHA1_NAME} https://download.jpom.top/release/${JPOM_PKG_VERSION}/${SHA1_NAME} && \ + ESUM=`cat ${JPOM_HOME}/${SHA1_NAME}` && \ + echo "${ESUM} ${JPOM_HOME}/${JPOM_PKG}" | sha1sum -c -; +RUN tar -zxvf ${JPOM_HOME}/${JPOM_PKG} -C ${JPOM_HOME} +RUN rm -rf ${JPOM_HOME}/${JPOM_PKG} + +# 将配置文件暂存默认目录,避免无法挂载 +RUN mv $JPOM_HOME/conf $JPOM_HOME/conf_default + +# 健康检查 +HEALTHCHECK CMD curl -X POST -f http://localhost:2122/check-system || exit 1 + +EXPOSE 2122 + +ENTRYPOINT ["/bin/bash", "./bin/BlockListener.sh"] + + + diff --git a/modules/server/DockerfileReleaseJdk17 b/modules/server/DockerfileReleaseJdk17 new file mode 100644 index 0000000000..670be6e89b --- /dev/null +++ b/modules/server/DockerfileReleaseJdk17 @@ -0,0 +1,58 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +FROM maven:3.9.6-sapmachine-17 + +ARG BUILD_DATE +LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}" +LABEL maintainer="bwcx-jzy " +LABEL documentation="https://jpom.top" + +ENV JPOM_HOME /usr/local/jpom-server +ENV JPOM_PKG_VERSION 2.11.6 +ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz +ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1 + +ENV JPOM_DATA_PATH ${JPOM_HOME}/data +ENV JPOM_LOG_PATH ${JPOM_HOME}/logs +# 数据目录 +ENV jpom.path ${JPOM_DATA_PATH} + +WORKDIR ${JPOM_HOME} + +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends git; + +# 时区 +ENV TZ Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone + +RUN mkdir -p ${JPOM_HOME} +# +RUN curl -LfSo ${JPOM_HOME}/${JPOM_PKG} https://download.jpom.top/release/${JPOM_PKG_VERSION}/${JPOM_PKG} +RUN curl -LfsSo ${JPOM_HOME}/${SHA1_NAME} https://download.jpom.top/release/${JPOM_PKG_VERSION}/${SHA1_NAME} && \ + ESUM=`cat ${JPOM_HOME}/${SHA1_NAME}` && \ + echo "${ESUM} ${JPOM_HOME}/${JPOM_PKG}" | sha1sum -c -; +RUN tar -zxvf ${JPOM_HOME}/${JPOM_PKG} -C ${JPOM_HOME} +RUN rm -rf ${JPOM_HOME}/${JPOM_PKG} + +# 将配置文件暂存默认目录,避免无法挂载 +RUN mv $JPOM_HOME/conf $JPOM_HOME/conf_default + +# 健康检查 +HEALTHCHECK CMD curl -X POST -f http://localhost:2122/check-system || exit 1 + +EXPOSE 2122 + +ENTRYPOINT ["/bin/bash", "./bin/BlockListener.sh"] + + + diff --git a/modules/server/pom.xml b/modules/server/pom.xml index f9c67fbfef..e4dae67807 100644 --- a/modules/server/pom.xml +++ b/modules/server/pom.xml @@ -1,189 +1,314 @@ + - - jpom-parent - io.jpom - 2.8.0 - ../../pom.xml - - 4.0.0 - Jpom 服务端 - server - 2.8.0 - - io.jpom.JpomServerApplication - - - - - - io.jpom - common - ${pom.version} - - - - - org.java-websocket - Java-WebSocket - 1.5.2 - - - - - com.h2database - h2 - - - - org.eclipse.jgit - org.eclipse.jgit.ssh.jsch - 5.12.0.202106070339-r - - - - com.jcraft - jsch - 0.1.55 - - - - org.tmatesoft.svnkit - svnkit - 1.10.3 - - - - - com.sun.mail - javax.mail - 1.6.2 - - - - javax.xml.bind - jaxb-api - 2.4.0-b180830.0359 - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.1 - - - - ${start-class} - - true - - ./ - - - - ${project.version} - - ${maven.build.timestamp} - ${project.artifactId} - https://gitee.com/dromara/Jpom - - - - - - - - - false - ../../ - - CHANGELOG.md - LICENSE - - - - src/main/resources - false - - - - - - - server-default-profile - - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - true - ${start-class} - -Dfile.encoding=UTF-8 - - - - - repackage - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.1 - - ${project.build.sourceEncoding} - - script/release.xml - - target - - - - make-assembly - package - - single - - - - - - - - - - install-plugin-profile - - - release-plugin-profile - - - - - develop-plugin-profile - - - io.jpom.jpom-plugin - netty - LATEST - - - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + jpom-parent + org.dromara.jpom + 2.11.6.6 + ../../pom.xml + + 4.0.0 + Jpom Server + server + 2.11.6.6 + + org.dromara.jpom.JpomServerApplication + + + + + org.dromara.jpom + common + ${project.version} + + + + org.dromara.jpom.plugins + webhook + ${project.version} + + + + org.dromara.jpom.plugins + git-clone + ${project.version} + + + + org.dromara.jpom.plugins + ssh-jsch + ${project.version} + + + + org.dromara.jpom.plugins + svn-clone + ${project.version} + + + + org.dromara.jpom.plugins + docker-cli + ${project.version} + + + + + org.dromara.jpom.plugins + email + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + snakeyaml + org.yaml + + + + + + cn.hutool + hutool-jwt + + + + cn.hutool + hutool-captcha + + + + org.bouncycastle + bcprov-jdk18on + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.dromara.jpom.agent-transport + agent-transport-http + ${project.version} + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.dromara.jpom.storage-module + storage-module-h2 + ${project.version} + + + + org.dromara.jpom.storage-module + storage-module-mysql + ${project.version} + + + + org.dromara.jpom.storage-module + storage-module-postgresql + ${project.version} + + + + org.dromara.jpom.storage-module + storage-module-mariadb + ${project.version} + + + + me.zhyd.oauth + JustAuth + 1.16.6 + + + com.alibaba + fastjson + + + + + + com.alibaba + fastjson + ${fastjson-version} + + + + cn.hutool + hutool-cache + + + + + + + false + ../../ + + CHANGELOG.md + CHANGELOG-BETA.md + LICENSE + + + + src/main/resources + false + + + + + + + server-default-profile + + true + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + ${start-class} + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + ${jpom-min-version} + + true + + + logback.xml + application*.yml + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + ${start-class} + -Dfile.encoding=UTF-8 + + + + org.projectlombok + lombok + + + + + + + repackage + + + + + + + + + + + install-assembly + + true + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + ${project.build.sourceEncoding} + + ${basedir}/src/main/assembly/release.xml + + ${project.build.directory} + + + + make-assembly + package + + single + + + + + + + net.nicoulaj.maven.plugins + checksum-maven-plugin + 1.11 + + + checksum-maven-plugin-files + package + + files + + + + + + + ${project.build.directory} + + *.jar + *.zip + *.tar.gz + + + + + SHA-1 + + + + + + + diff --git a/modules/server/script/Server.bat b/modules/server/script/Server.bat deleted file mode 100644 index 45e5f06509..0000000000 --- a/modules/server/script/Server.bat +++ /dev/null @@ -1,142 +0,0 @@ -@REM The MIT License (MIT) -@REM -@REM Copyright (c) 2019 码之科技工作室 -@REM -@REM Permission is hereby granted, free of charge, to any person obtaining a copy of -@REM this software and associated documentation files (the "Software"), to deal in -@REM the Software without restriction, including without limitation the rights to -@REM use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -@REM the Software, and to permit persons to whom the Software is furnished to do so, -@REM subject to the following conditions: -@REM -@REM The above copyright notice and this permission notice shall be included in all -@REM copies or substantial portions of the Software. -@REM -@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -@REM FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -@REM COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -@REM IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -@REM CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -@REM - -@echo off -CHCP 65001 -setlocal enabledelayedexpansion - -@REM 设置环境变量,避免部分服务器没有 taskkill -set PATH = %PATH%;C:\Windows\system32;C:\Windows;C:\Windows\system32\Wbem - -set Tag=KeepBx-System-JpomServerApplication -set MainClass=org.springframework.boot.loader.JarLauncher -set basePath=%~dp0 -set Lib=%basePath%lib\ -@REM 请勿修改----------------------------------↓ -set LogName=server.log -@REM 在线升级会自动修改此属性 -set RUNJAR= -@REM 请勿修改----------------------------------↑ -@REM 是否开启控制台日志文件备份 -set LogBack=true -set JVM=-server -Xms254m -Xmx1024m -set ARGS= --jpom.applicationTag=%Tag% --spring.profiles.active=pro --jpom.log=%basePath%log --server.port=2122 - -@REM 读取jar -call:listDir - -if "%1"=="" ( - color 0a - TITLE Jpom管理系统BAT控制台 - echo. ***** Jpom管理系统BAT控制台 ***** - ::************************************************************************************************************* - echo. - echo. [1] 启动 start - echo. [2] 关闭 stop - echo. [3] 查看运行状态 status - echo. [4] 重启 restart - echo. [5] 帮助 use - echo. [6] 清除 IP 白名单配置 - echo. [0] 退 出 0 - echo. - @REM 输入 - echo.请输入选择的序号: - set /p ID= - IF "!ID!"=="1" call:start - IF "!ID!"=="2" call:stop - IF "!ID!"=="3" call:status - IF "!ID!"=="4" call:restart - IF "!ID!"=="5" call:use - IF "!ID!"=="6" call:restart --rest:ip_config - IF "!ID!"=="0" EXIT -)else ( - if "%1"=="restart" ( - call:restart - )else ( - call:use - ) -) -if "%2" NEQ "upgrade" ( - PAUSE -)else ( - @REM 升级直接结束 -) -EXIT 0 - -@REM 启动 -:start - if "%JAVA_HOME%"=="" ( - echo 请配置【JAVA_HOME】环境变量 - PAUSE - EXIT 2 - ) - - echo 启动中.....启动成功后关闭窗口不影响运行 - echo 启动详情请查看:%LogName% - javaw %JVM% -Djava.class.path="%RUNJAR%" -Dapplication=%Tag% -Dbasedir=%basePath% %MainClass% %ARGS% %1 >> %basePath%%LogName% - timeout 3 -goto:eof - - -@REM 获取jar -:listDir - if "%RUNJAR%"=="" ( - for /f "delims=" %%I in ('dir /B %Lib%') do ( - if exist %Lib%%%I if not exist %Lib%%%I\nul ( - if "%%~xI" ==".jar" ( - if "%RUNJAR%"=="" ( - set RUNJAR=%Lib%%%I - ) - ) - ) - ) - )else ( - set RUNJAR=%Lib%%RUNJAR% - ) - echo 运行:%RUNJAR% -goto:eof - -@REM 关闭Jpom -:stop - java -Djava.class.path="%JAVA_HOME%/lib/tools.jar;%RUNJAR%" %MainClass% %ARGS% --event=stop -goto:eof - -@REM 查看Jpom运行状态 -:status - java -Djava.class.path="%JAVA_HOME%/lib/tools.jar;%RUNJAR%" %MainClass% %ARGS% --event=status -goto:eof - -@REM 重启Jpom -:restart - echo 停止中.... - call:stop - timeout 3 - echo 启动中.... - call:start %1 -goto:eof - -@REM 提示用法 -:use - echo please use (start、stop、restart、status) -goto:eof - - diff --git a/modules/server/script/Server.sh b/modules/server/script/Server.sh deleted file mode 100644 index d27173e0ea..0000000000 --- a/modules/server/script/Server.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/bin/bash -# The MIT License (MIT) -# -# Copyright (c) 2019 码之科技工作室 -# -# 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. -# 支持读取环境变量 -if [ -f /etc/profile ]; then - . /etc/profile -fi -if [ -f /etc/bashrc ]; then - . /etc/bashrc -fi -if [ -f ~/.bash_profile ]; then - . ~/.bash_profile -fi -if [ -f ~/.bashrc ]; then - . ~/.bashrc -fi -# 请不要修改 tag 属性的值,修改后会影响程序的停止、查看状态 -Tag="KeepBx-System-JpomServerApplication" -# 自动获取当前路径 -Path=$(cd `dirname $0`; pwd)"/" -Lib="${Path}lib/" -RUNJAR="" -Log="${Path}server.log" -LogBack="${Path}log/" -JVM="-server -Xms254m -Xmx1024m" -# 修改项目端口号 日志路径 -ARGS="--jpom.applicationTag=${Tag} --spring.profiles.active=pro --server.port=2122 --jpom.log=${Path}log $@" - -echo ${Tag} -echo ${Path} -RETVAL="0" -# 升级执行命令标识 -upgrade="$2" - -# now set the path to java -if [[ -x "${JAVA_HOME}/bin/java" ]]; then - JAVA="${JAVA_HOME}/bin/java" - NOW_JAVA_HOME="${JAVA_HOME}" -else - set +e - JAVA=`which java` - NOW_JAVA_HOME="${JAVA}/../" - set -e -fi - -if [[ ! -x "$JAVA" ]]; then - echo "没有找到JAVA 文件,请配置【JAVA_HOME】环境变量" - exit 1 -fi - -# 启动程序 -function start() { - pid=`getPid` - if [[ "$pid" != "" ]]; then - echo "程序正在运行中:${pid}" - exit 2 - fi - echo ${Log} - # 备份日志 - if [[ -f ${Log} ]]; then - if [[ ! -d ${LogBack} ]];then - mkdir ${LogBack} - fi - cur_dateTime="`date +%Y-%m-%d_%H:%M:%S`.log" - mv ${Log} ${LogBack}${cur_dateTime} - echo "mv to $LogBack$cur_dateTime" - touch ${Log} - fi - # jar - if [[ -z "${RUNJAR}" ]] ; then - RUNJAR=`listDir ${Lib}` - echo "自动运行:${RUNJAR}" - fi - # error - if [[ -z "${RUNJAR}" ]] ; then - echo "没有找到jar" - exit 2 - fi - - nohup ${JAVA} ${JVM} -jar ${Lib}${RUNJAR} -Dapplication=${Tag} -Dbasedir=${Path} ${ARGS} >> ${Log} 2>&1 & - # 升级不执行查看日志 - if [[ ${upgrade} == "upgrade" ]] ; then - exit 0 - fi - if [[ -f ${Log} ]]; then - tail -f ${Log} - else - sleep 3 - if [[ -f ${Log} ]]; then - tail -f ${Log} - else - echo "还没有生成日志文件:${Log}" - fi - fi -} - -# 找出第一个jar包 -function listDir() -{ - ALL="" - for file in `ls $1` - do - if [[ -f "${1}/${file}" ]] && [[ "${file##*.}"x = "jar"x ]] ; then - #得到文件的完整的目录 - ALL="${file}" - break - fi - done - echo ${ALL} -} - -# 停止程序 -function stop() { - pid=`getPid` - if [[ "$pid" != "" ]]; then - echo -n "boot ( pid $pid) is running" - echo - echo -n $"Shutting down boot: wait" - kill $(pgrep -f ${Tag}) 2>/dev/null - sleep 3 - pid=`getPid` - if [[ "$pid" != "" ]]; then - echo "kill boot process" - kill -9 "$pid" - fi - else - echo "boot is stopped" - fi - - status -} - -# 获取程序状态 -function status() -{ - pid=`getPid` - #echo "$pid" - if [[ "$pid" != "" ]]; then - echo "boot is running,pid is $pid" - else - echo "boot is stopped" - fi -} - -function getPid(){ - pid=$(ps -ef | grep -v 'grep' | egrep ${Tag}| awk '{printf $2 " "}') - echo ${pid} -} - -# 提示使用语法 -function usage() -{ - echo "Usage: $0 {start|stop|restart|status|create}" - RETVAL="2" -} - - -# 创建自启动服务文件 -function create() { - yum install -y wget && wget -O jpom-server https://dromara.gitee.io/jpom/docs/jpom-service.sh - #判断当前脚本是否为绝对路径,匹配以/开头下的所有 - if [[ $0 =~ ^\/.* ]] - then - selfpath=$0 - else - selfpath=$(pwd)/$0 - fi - #获取文件的真实路径 - selfpath=`readlink -f $selfpath` - # 替换路径 - sed -i "s|JPOM_RUN_PATH|${selfpath}|g" jpom-server - echo 'create jpom-server file done' - mv -f jpom-server /etc/init.d/jpom-server - chmod +x /etc/init.d/jpom-server - chkconfig --add jpom-server - echo 'create jpom-server success' -} - -# See how we were called. -RETVAL="0" -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - status) - status - ;; - create) - create - ;; - *) - usage - ;; -esac - -exit $RETVAL diff --git a/modules/server/script/release.xml b/modules/server/script/release.xml deleted file mode 100644 index 68a00f14f2..0000000000 --- a/modules/server/script/release.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - release - false - - dir - zip - - - - - - script/ - / - - Server.sh - Server.bat - - - - - src/main/resources/bin/ - / - - extConfig.yml - - - - - ../../ - / - - LICENSE - - - - - - - - lib - - io.jpom:server - - - - - diff --git a/modules/server/src/main/assembly/release.xml b/modules/server/src/main/assembly/release.xml new file mode 100644 index 0000000000..9574c92e4f --- /dev/null +++ b/modules/server/src/main/assembly/release.xml @@ -0,0 +1,85 @@ + + + + release + false + + dir + zip + tar.gz + + + + + + ./src/main/bin + bin + + *.sh + + unix + + + ./src/main/bin + bin + + *.bat + + dos + + + + ./src/main/resources/config_default/ + /conf + + logback.xml + application.yml + + + + + + + + + + + + + ../../ + / + + LICENSE + + + + + + + + lib + + org.dromara.jpom:server + + + + + + + + + + + diff --git a/modules/server/src/main/bin/BlockListener.sh b/modules/server/src/main/bin/BlockListener.sh new file mode 100644 index 0000000000..07dab43680 --- /dev/null +++ b/modules/server/src/main/bin/BlockListener.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +case "$(uname)" in +Linux) + bin_abs_path=$(readlink -f "$(dirname "$0")") + ;; +*) + bin_abs_path=$( + cd "$(dirname "$0")" || exit + pwd + ) + ;; +esac + +base=${bin_abs_path}/.. +pidfile="$base/bin/server.pid" +LogPath="${base}/logs/" +stdout_log="${LogPath}/stdout.log" +LOOPS=0 + +# wait_term_pid "${PIDFILE}". +# monitor process by pidfile && wait TERM/INT signal. +# if the process disappeared, return 1, means exit with ERROR. +# if TERM or INT signal received, return 0, means OK to exit. +function wait_term_pid() { + local PIDFILE PID do_run error + PIDFILE="${1?}" + PID="$(cat "${PIDFILE}")" + do_run=true + error=0 + trap "do_run=false" TERM INT + while "${do_run}"; do + PID="$(cat "${PIDFILE}")" + if ! ps -p "${PID}" >/dev/null 2>&1; then + do_run=false + error=1 + else + # rest loops + LOOPS=0 + tail_log + fi + done + trap - TERM INT + return "${error}" +} + +function tail_log() { + if [ -f "$stdout_log" ]; then + PID="$(cat "${pidfile}")" + tail -fn 0 --pid="$PID" "$stdout_log" + else + echo "stdout_log not found $stdout_log" + fi +} + +function check_conf() { + + conf_path=$base/conf + conf_default_path=$base/conf_default + conf_array=(application.yml logback.xml) + + if [[ ! -d "$conf_path" ]]; then + mkdir -p "${conf_path}" + fi + for element in "${conf_array[@]}"; do + if [[ ! -f "$conf_path/$element" ]]; then + if [[ ! -f "$conf_default_path/$element" ]]; then + echo "Cannot find $conf_path/$element && not found default conf in : $conf_default_path/$element" 2>&2 + exit 1 + else + echo "copy default conf to $conf_path/$element" + cp -r "$conf_default_path/$element" "$conf_path/$element" + fi + fi + done +} + +check_conf + +bash "$bin_abs_path/Server.sh" start -s + +while (true); do + if [ -f "$pidfile" ]; then + tail_log + wait_term_pid "$pidfile" + else + echo "pidfile not found $pidfile" + fi + + if [ $LOOPS -gt 120 ]; then + echo "wait timeout $LOOPS" 2>&2 + break + fi + LOOPS=$((LOOPS + 1)) + sleep 1 +done + +echo "edit" + +exit 1 diff --git a/modules/server/src/main/bin/Server.bat b/modules/server/src/main/bin/Server.bat new file mode 100644 index 0000000000..b1cf5cf94a --- /dev/null +++ b/modules/server/src/main/bin/Server.bat @@ -0,0 +1,171 @@ +@REM +@REM Copyright (c) 2019 Of Him Code Technology Studio +@REM Jpom is licensed under Mulan PSL v2. +@REM You can use this software according to the terms and conditions of the Mulan PSL v2. +@REM You may obtain a copy of Mulan PSL v2 at: +@REM http://license.coscl.org.cn/MulanPSL2 +@REM THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +@REM See the Mulan PSL v2 for more details. +@REM + +@echo off +@if not "%ECHO%" == "" echo %ECHO% +setlocal enabledelayedexpansion +set ENV_PATH=.\ +if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% + +@REM Set environment variables to prevent some servers from failing to taskkill +set PATH = %PATH%;C:\Windows\system32;C:\Windows;C:\Windows\system32\Wbem + +if "%JAVA_HOME%"=="" ( + echo please configure [JAVA_HOME] environment variable + PAUSE + EXIT 2 +) + +set PID_TAG="JPOM_SERVER_APPLICATION" +set conf_dir="%ENV_PATH%/../conf/" +set tmpdir="%ENV_PATH%/../tmp/" +if not exist %tmpdir% md %tmpdir% + +@REM see org.springframework.util.StringUtils.cleanPath +@REM set org.springframework.boot.context.config.StandardConfigDataLocationResolver.getResourceLocation +cd %conf_dir% +set conf_dir=%cd% +cd %tmpdir% +set tmpdir=%cd% +cd %ENV_PATH% + +set log_dir=%ENV_PATH%\..\logs +set logback_configurationFile=%conf_dir%\logback.xml +set application_conf=%conf_dir%\application.yml + +set Lib=%ENV_PATH%\..\lib\ +set "RUN_JAR=" +set "JAR_MSG=" +set server_log="%log_dir%\server.log" +set stdout_log="%log_dir%\stdout.log" + +set JAVA_MEM_OPTS= -Xms1024m -Xmx2048m -XX:+UseG1GC +set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 -Djava.io.tmpdir="%tmpdir%" +set APP_OPTS= -Djpom.application.tag="%PID_TAG%" -Dlogging.config="%logback_configurationFile%" -Dspring.config.location="%application_conf%" +set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %APP_OPTS% + +set ARGS=%* +set JPOM_LOG=%log_dir% +if not exist %log_dir% md %log_dir% + +@REM get list jar +call:listDir + +if "%1"=="" ( + color 0a + TITLE Jpom management system BAT console + echo. ***** Jpom management system BAT console ***** + echo. !JAR_MSG! + ::************************************************************************************************************* + echo. + echo. [1] start + echo. [2] status + echo. [3] restart + echo. [4] stop + echo. [0] exit 0 + echo. + @REM enter + for /l %%i in (1,1,10000) do ( + echo. Please enter the selected serial number: + set /p ID= + IF "!ID!"=="1" call:start + IF "!ID!"=="2" call:status + IF "!ID!"=="3" call:restart + IF "!ID!"=="4" call:stop + IF "!ID!"=="0" EXIT + ) +)else ( + if "%1"=="restart" ( + call:restart + )else if "%1"=="start" ( + call:start + )else if "%1"=="status" ( + call:status + )else if "%1"=="stop" ( + call:stop + )else ( + call:use + ) +) +if "%2" == "upgrade" ( + @REM The upgrade ends directly + EXIT 0 +) + +:end +goto:eof + +@REM start +:start + echo Starting..... Closing the window after a successful start does not affect the operation + echo Please check for startup details:%server_log% or !stdout_log%! + start /b javaw %JAVA_OPTS% -jar %Lib%!RUN_JAR! %ARGS% > "!stdout_log!" 2>&1 + @REM timeout 3 > NUL + ping 127.0.0.1 -n 3 > nul +goto:eof + + +@REM get jar +:listDir + if "%RUN_JAR%"=="" ( + if exist "%Lib%\run.bin" ( + set /P RUN_JAR=<"%Lib%\run.bin" + set JAR_MSG=specify running !RUN_JAR! + )else ( + for /f "delims=" %%I in ('dir /B %Lib%') do ( + if exist %Lib%%%I if not exist %Lib%%%I\nul ( + if "%%~xI" ==".jar" ( + if "%RUN_JAR%"=="" ( + set "RUN_JAR=%%I" + ) + ) + ) + ) + set JAR_MSG=auto running !RUN_JAR! + ) + )else ( + set JAR_MSG=specify2 running %RUN_JAR% + ) + if not exist %Lib%!RUN_JAR! ( + echo %JAR_MSG% + echo file not exist %Lib%!RUN_JAR! + PAUSE + EXIT -1 + ) + @REM stdout_log + if exist "%Lib%\run.bin" ( + set /P RUN_LOG=<"%Lib%\run.log" + set stdout_log="%log_dir%\!RUN_LOG!" + ) +goto:eof + +@REM stop Jpom +:stop + echo "jpom server stop " + for /f "tokens=1 delims= " %%I in ('jps -v ^| findstr "%PID_TAG%"') do taskkill /F /PID %%I +goto:eof + +@REM view Jpom status +:status + echo "jpom server status " + set pid= + for /f "tokens=1 delims= " %%I in ('jps -v ^| findstr "%PID_TAG%"') do set pid=%%I + echo "running: %pid%" +goto:eof + +@REM restart Jpom +:restart + echo Stopping.... + call:stop + @REM timeout 3 > NUL + ping 127.0.0.1 -n 3 > nul + echo starting.... + call:start %1 +goto:eof diff --git a/modules/server/src/main/bin/Server.sh b/modules/server/src/main/bin/Server.sh new file mode 100644 index 0000000000..9af50dc889 --- /dev/null +++ b/modules/server/src/main/bin/Server.sh @@ -0,0 +1,278 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# description: Auto-starts jpom server + +function absPath() { + dir="$1" + case "$(uname)" in + Linux) + abs_path=$(readlink -f "$dir") + ;; + *) + abs_path=$( + cd "$dir" || exit + pwd + ) + ;; + esac + # + echo "$abs_path" +} + +function errorExit() { + echo "$1" 2>&2 + if [ "${mode}" == "-s" ]; then + logStdout "$1" + fi + exit 1 +} + +function logStdout() { + # out stdout + if [ ! -f "$Log" ]; then + touch "$Log" + fi + echo "$1" >"$Log" +} + +command_exists() { + command -v "$@" >/dev/null 2>&1 +} + +bin_abs_path=$(absPath "$(dirname "$0")") +base=$(absPath "$bin_abs_path/../") + +conf_path="${base}/conf" +Lib="${base}/lib/" +LogPath="${base}/logs/" +tmpdir="${base}/tmp/" +Log="${LogPath}/stdout.log" +logback_configurationFile="${conf_path}/logback.xml" +application_conf="${conf_path}/application.yml" +pidfile="$base/bin/server.pid" + +PID_TAG="JPOM_SERVER_APPLICATION" +server_log="${LogPath}/server.log" + +## set java path +if [ -z "$JAVA" ]; then + JAVA=$(which java) +fi +if [ -z "$JAVA" ]; then + if command_exists java; then + JAVA="java" + fi +fi +if [ -z "$JAVA" ]; then + errorExit "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.8) in your PATH." +fi + +JavaVersion=$($JAVA -version 2>&1 | awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}') +Java64Str=$($JAVA -version 2>&1 | grep -E '64-bit|64-Bit') + +JAVA_OPTS="$JAVA_OPTS -Xss1024k -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LogPath" + +if [ "${JavaVersion}" -ge 11 ]; then + JAVA_OPTS="$JAVA_OPTS" +else + JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution" +fi + +#-Xms1g -Xmx2g +if [[ -z "${USR_JVM_SIZE}" ]]; then + if [ -n "$Java64Str" ]; then + USR_JVM_SIZE="-Xms1g -Xmx2g" + else + USR_JVM_SIZE="-Xms1024m -Xmx2024m" + fi +fi + +if [ -n "$Java64Str" ]; then + # For G1 + JAVA_OPTS="-server ${USR_JVM_SIZE} -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS" +else + JAVA_OPTS="-server ${USR_JVM_SIZE} -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS" +fi +JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" +JAVA_OPTS="$JAVA_OPTS -Dlogging.config=$logback_configurationFile -Dspring.config.location=$application_conf" +JAVA_OPTS="$JAVA_OPTS -Djava.io.tmpdir=$tmpdir" + +MAIN_ARGS="$*" + +# mode -s -9 +mode="$2" + +RUN_JAR="" + +function checkConfig() { + if [ ! -d "$LogPath" ]; then + mkdir -p "$LogPath" + fi + if [[ ! -f "$logback_configurationFile" ]] || [[ ! -f "$application_conf" ]]; then + errorExit "Cannot find $application_conf or $logback_configurationFile" + fi + + if [[ -z "${RUN_JAR}" ]]; then + if [ -f "$Lib/run.bin" ]; then + RUN_JAR=$(cat "$Lib/run.bin") + if [ ! -f "$Lib/$RUN_JAR" ]; then + errorExit "Cannot find $Lib/$RUN_JAR jar" + fi + echo "specify running:${RUN_JAR}" + else + RUN_JAR=$(find "${Lib}" -type f -name "*.jar" -exec ls -t {} + | head -1 | sed 's#.*/##') + # error + if [[ -z "${RUN_JAR}" ]]; then + errorExit "Jar not found" + fi + echo "automatic running:${RUN_JAR}" + fi + fi + + mkdir -p "$tmpdir" + + export JPOM_LOG=${LogPath} +} + +function getPid() { + cygwin=false + linux=false + case "$(uname)" in + CYGWIN*) + cygwin=true + ;; + Linux*) + linux=true + ;; + esac + if $cygwin; then + JAVA_CMD="$JAVA_HOME\bin\java" + JAVA_CMD=$(cygpath --path --unix "$JAVA_CMD") + JAVA_PID=$(ps | grep "$JAVA_CMD" | awk '{print $1}') + else + if $linux; then + JAVA_PID=$(ps -C java -f --width 1000 | grep "$PID_TAG" | grep -v grep | awk '{print $2}') + else + JAVA_PID=$(ps aux | grep "$PID_TAG" | grep -v grep | awk '{print $2}') + fi + fi + echo "$JAVA_PID" +} + +# See how we were called. +function start() { + echo $PID_TAG + # check running + pid=$(getPid) + #echo "$pid" + if [ "$pid" != "" ]; then + echo "Running, please do not run repeatedly:$pid" + exit 0 + fi + checkConfig + + if [ ! -f "$server_log" ]; then + touch "$server_log" + fi + # start + command="${JAVA} -Djpom.application.tag=${PID_TAG} ${JAVA_OPTS} -jar ${Lib}${RUN_JAR} ${MAIN_ARGS}" + echo "$command" >"$Log" + + eval "nohup $command >>$Log 2>&1 &" + + echo $! >"$pidfile" + + pid=$(cat "$pidfile") + + if [ "${mode}" == "-s" ] || [ "${mode}" == "upgrade" ]; then + echo "silence auto exit 0,${pid}" + exit 0 + fi + echo "Jpom server starting:$pid" + pid=$(getPid) + if [ "$pid" == "" ]; then + echo "Please check the $Log for failure details" + errorExit "Jpom server Startup failed" + fi + tail -fn 0 --pid="$pid" "$server_log" +} + +function stop() { + pid=$(getPid) + killMode="" + if [ "${mode}" == "-s" ] || [ "${mode}" == "upgrade" ]; then + # Compatible with online upgrade ./Server.sh restart upgrade or ./Server.sh restart -s + killMode="" + else + killMode=${mode} + fi + if [ "$pid" != "" ]; then + echo -n "jpom server ( pid $pid) is running" + echo + echo -n $"Shutting down (kill $killMode $pid) jpom server: " + if [ "$killMode" == "" ]; then + kill "$pid" + else + kill "$killMode" "$pid" + fi + LOOPS=0 + while (true); do + pid=$(getPid) + if [ "$pid" == "" ]; then + echo "Stop and end, in $LOOPS seconds" + break + fi + ((LOOPS++)) || true + sleep 1 + done + else + echo "jpom server is stopped" + fi + eval "$(rm -f "$pidfile")" +} + +function status() { + pid=$(getPid) + #echo "$pid" + if [ "$pid" != "" ]; then + echo "jpom server running:$pid" + else + echo "jpom server is stopped" + fi +} + +function usage() { + echo "Usage: $0 {start|stop|restart|status}" 2>&2 + RETVAL="2" +} + +# See how we were called. +RETVAL="0" +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + stop + start + ;; +status) + status + ;; +*) + usage + ;; +esac +exit $RETVAL diff --git a/modules/server/src/main/bin/Service.sh b/modules/server/src/main/bin/Service.sh new file mode 100644 index 0000000000..c4f1397d14 --- /dev/null +++ b/modules/server/src/main/bin/Service.sh @@ -0,0 +1,185 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# description: manage jpom server Service + +function absPath() { + dir="$1" + case "$(uname)" in + Linux) + abs_path=$(readlink -f "$dir") + ;; + *) + abs_path=$( + cd "$dir" || exit + pwd + ) + ;; + esac + # + echo "$abs_path" +} + +command_exists() { + command -v "$@" >/dev/null 2>&1 +} + +binAbsPath=$(absPath "$(dirname "$0")") +serviceName="jpom-server.service" +serviceFile="/etc/systemd/system/$serviceName" +binAbsName=$(absPath "$binAbsPath/Server.sh") +pidfile="$binAbsPath/server.pid" + +# +user="$(id -un 2>/dev/null || true)" +user_group="$(id -gn 2>/dev/null || true)" + +sh_c='sh -c' +exec_user="" +if [ "$user" != 'root' ]; then + if command_exists sudo; then + sh_c='sudo -E sh -c' + elif command_exists su; then + sh_c='su -c' + else + cat >&2 <<-EOF + Error: this installer needs the ability to run commands as root. + We are unable to find either "sudo" or "su" available to make this happen. + EOF + exit 1 + fi + exec_user="$user" +fi + +function install() { + + if [ -f "$serviceFile" ]; then + echo "service file already exists" 2>&2 + exit 2 + fi + if [ ! -f "$binAbsName" ]; then + echo "$binAbsName not found" 2>&2 + exit 2 + fi + if [ -z "$JAVA_HOME" ]; then + echo "JAVA_HOME variable not found" 2>&2 + exit 2 + fi + if [ -z "$CLASSPATH" ]; then + echo "CLASSPATH variable not found" 2>&2 + exit 2 + fi + + $sh_c "cat >$serviceFile" <&2 <<-EOF + ERROR: $serviceName write failed Installing the service requires the ability to run commands as root. + EOF + exit 1 + fi + + echo "$serviceName write success :$serviceFile" + + $sh_c 'systemctl daemon-reload' + + cat >&2 <<-EOF + INFO: You can execute the following commands to manage $serviceName. + INFO: systemctl start $serviceName (Start the service ) + INFO: systemctl stop $serviceName (Stop the service) + INFO: systemctl enable $serviceName (Set up autostart) + INFO: systemctl disable $serviceName (stop autostart) + INFO: systemctl status $serviceName (View the current status of the service) + INFO: systemctl restart $serviceName (Restart the service) + EOF +} + +function uninstall() { + if [ -f "$serviceFile" ]; then + $sh_c "systemctl disable $serviceName" + $sh_c "systemctl stop $serviceName" + $sh_c "rm -f $serviceFile" + if [ -f "$serviceFile" ]; then + cat >&2 <<-EOF + ERROR: $serviceName write uninstall . + EOF + exit 1 + fi + echo "$serviceName uninstalled successfully" + $sh_c 'systemctl daemon-reload' + else + echo "$serviceFile not found" + fi +} + +function enable() { + if [ -f "$serviceFile" ]; then + $sh_c "systemctl enable $serviceName" + else + echo "$serviceFile not found" 2>&2 + echo "Usage: $0 install" 2>&2 + fi +} + +function action() { + case "$1" in + install) + install + ;; + uninstall) + uninstall + ;; + reinstall) + uninstall + echo "--------------------------------------" + install + ;; + enable) + enable + ;; + *) + echo "Usage: $0 {install|uninstall|reinstall|enable}" 2>&2 + exit 1 + ;; + esac + +} + +if [ -z "$1" ]; then + echo "Usage: $0 {install|uninstall|reinstall|enable}" 2>&2 + exit 1 +fi + +for i in "$@"; do + action "$i" +done diff --git a/modules/server/src/main/java/io/jpom/JpomServerApplication.java b/modules/server/src/main/java/io/jpom/JpomServerApplication.java index fb8203a48b..608bf89b9b 100644 --- a/modules/server/src/main/java/io/jpom/JpomServerApplication.java +++ b/modules/server/src/main/java/io/jpom/JpomServerApplication.java @@ -1,111 +1,23 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package io.jpom; -import cn.hutool.core.date.BetweenFormatter; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.ArrayUtil; -import cn.jiangzeyin.common.EnableCommonBoot; -import cn.jiangzeyin.common.spring.SpringUtil; -import cn.jiangzeyin.common.spring.event.ApplicationEventLoad; -import io.jpom.common.Type; -import io.jpom.common.interceptor.IpInterceptor; -import io.jpom.common.interceptor.LoginInterceptor; -import io.jpom.common.interceptor.OpenApiInterceptor; -import io.jpom.common.interceptor.PermissionInterceptor; -import io.jpom.model.data.SystemIpConfigModel; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.service.user.UserService; -import io.jpom.system.db.DbConfig; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.ServletComponentScan; - /** - * jpom 启动类 + * 兼容低版本包检查是否为 jpom 对应类型的程序 * - * @author jiangzeyin - * @date 2017/9/14 + * @author bwcx_jzy + * @since 2023/3/31 */ -@SpringBootApplication -@ServletComponentScan -@EnableCommonBoot -public class JpomServerApplication implements ApplicationEventLoad { - - /** - * 重新执行数据库初始化操作,一般用于手动修改数据库字段错误后,恢复默认的字段 - */ - private static boolean load_init_db = false; - - /** - * 启动执行 - * --rest:ip_config 重置 IP 白名单配置 - * --rest:load_init_db 重新加载数据库初始化操作 - * --rest:super_user_pwd 重置超级管理员密码 - * - * @param args 参数 - * @throws Exception 异常 - */ - public static void main(String[] args) throws Exception { - long time = SystemClock.now(); - if (ArrayUtil.containsIgnoreCase(args, "--rest:load_init_db")) { - load_init_db = true; - } - // - JpomApplication jpomApplication = new JpomApplication(Type.Server, JpomServerApplication.class, args); - jpomApplication - // 拦截器 - .addInterceptor(IpInterceptor.class) - .addInterceptor(LoginInterceptor.class) - .addInterceptor(OpenApiInterceptor.class) - .addInterceptor(PermissionInterceptor.class) - .run(args); - // - if (ArrayUtil.containsIgnoreCase(args, "--rest:ip_config")) { - // 重置 ip 白名单配置 - SystemParametersServer parametersServer = SpringUtil.getBean(SystemParametersServer.class); - parametersServer.delByKey(SystemIpConfigModel.ID); - Console.log("清除 IP 白名单配置成功"); - } - if (ArrayUtil.containsIgnoreCase(args, "--rest:super_user_pwd")) { - UserService userService = SpringUtil.getBean(UserService.class); - String restResult = userService.restSuperUserPwd(); - if (restResult != null) { - Console.log(restResult); - } else { - Console.log("系统中还没有超级管理员账号"); - } - } - // - Console.log("本次启动耗时:{}", DateUtil.formatBetween(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND)); - } - - - @Override - public void applicationLoad() { - if (load_init_db) { - DbConfig.getInstance().clearExecuteSqlLog(); - } - } +@Deprecated +public class JpomServerApplication { + public static void main(String[] args) throws Exception { + org.dromara.jpom.JpomServerApplication.main(args); + } } diff --git a/modules/server/src/main/java/io/jpom/build/BaseBuild.java b/modules/server/src/main/java/io/jpom/build/BaseBuild.java deleted file mode 100644 index 0ead6eac20..0000000000 --- a/modules/server/src/main/java/io/jpom/build/BaseBuild.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.build; - -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.file.FileWriter; -import cn.hutool.core.util.CharsetUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.enums.BuildStatus; -import io.jpom.service.dblog.BuildInfoService; - -import java.io.File; -import java.io.PrintWriter; - -/** - * 构建的基础类 - * - * @author bwcx_jzy - * @date 2019/7/19 - */ -public abstract class BaseBuild { - - /** - * 日志文件 - */ - protected final File logFile; - /** - * 构建ID - */ - protected final String buildModelId; - - BaseBuild(File logFile, String buildModelId) { - this.logFile = logFile; - this.buildModelId = buildModelId; - } - - protected void log(String title, Throwable throwable, BuildStatus status) { - DefaultSystemLog.getLog().error(title, throwable); - FileUtil.appendLines(CollectionUtil.toList(title), this.logFile, CharsetUtil.CHARSET_UTF_8); - String s = ExceptionUtil.stacktraceToString(throwable); - FileUtil.appendLines(CollectionUtil.toList(s), this.logFile, CharsetUtil.CHARSET_UTF_8); - updateStatus(status); - } - - protected void log(String info) { - FileUtil.appendLines(CollectionUtil.toList(info), this.logFile, CharsetUtil.CHARSET_UTF_8); - } - - protected PrintWriter getPrintWriter() { - return FileWriter.create(this.logFile, CharsetUtil.CHARSET_UTF_8).getPrintWriter(true); - } - - protected boolean updateStatus(BuildStatus status) { - BuildInfoService buildService = SpringUtil.getBean(BuildInfoService.class); - BuildInfoModel item = buildService.getByKey(this.buildModelId); - item.setStatus(status.getCode()); - buildService.update(item); - return true; - } -} diff --git a/modules/server/src/main/java/io/jpom/build/BuildExtraModule.java b/modules/server/src/main/java/io/jpom/build/BuildExtraModule.java deleted file mode 100644 index 832279cb21..0000000000 --- a/modules/server/src/main/java/io/jpom/build/BuildExtraModule.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.build; - -import cn.hutool.core.io.FileUtil; -import io.jpom.model.BaseModel; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.model.log.BuildHistoryLog; - -/** - * 构建物基类 - * - * @author bwcx_jzy - * @date 2019/7/19 - */ -public class BuildExtraModule extends BaseModel { - /** - * 发布方式 - * - * @see BuildReleaseMethod - * @see BuildInfoModel#getReleaseMethod() - */ - private int releaseMethod; - /** - * 发布方法的数据id - * - * @see BuildInfoModel#getReleaseMethodDataId() - */ - private String releaseMethodDataId; - /** - * 分发后的操作 - * 仅在项目发布类型生效 - * - * @see io.jpom.model.AfterOpt - * @see BuildInfoModel#getExtraData() - */ - private int afterOpt; - /** - * 是否清空旧包发布 - */ - private boolean clearOld; - /** - * 构建产物目录 - */ - private String resultDirFile; - /** - * 发布命令 ssh 才能用上 - */ - private String releaseCommand; - /** - * 发布到ssh中的目录 - */ - private String releasePath; - - /** - * 工作空间 ID - */ - private String workspaceId; - - public String getReleasePath() { - return releasePath; - } - - public void setReleasePath(String releasePath) { - this.releasePath = releasePath; - } - - public String getReleaseCommand() { - return releaseCommand; - } - - public void setReleaseCommand(String releaseCommand) { - this.releaseCommand = releaseCommand; - } - - public boolean isClearOld() { - return clearOld; - } - - public void setClearOld(boolean clearOld) { - this.clearOld = clearOld; - } - - public int getReleaseMethod() { - return releaseMethod; - } - - public void setReleaseMethod(int releaseMethod) { - this.releaseMethod = releaseMethod; - } - - public String getReleaseMethodDataId() { - return releaseMethodDataId; - } - - public void setReleaseMethodDataId(String releaseMethodDataId) { - this.releaseMethodDataId = releaseMethodDataId; - } - - public int getAfterOpt() { - return afterOpt; - } - - public void setAfterOpt(int afterOpt) { - this.afterOpt = afterOpt; - } - - public String getResultDirFile() { - if (resultDirFile == null) { - return null; - } - return FileUtil.normalize(this.resultDirFile.trim()); - } - - public void setResultDirFile(String resultDirFile) { - this.resultDirFile = resultDirFile; - } - - public String getWorkspaceId() { - return workspaceId; - } - - public void setWorkspaceId(String workspaceId) { - this.workspaceId = workspaceId; - } - - /** - * 更新 字段值 - * - * @param buildInfoModel 构建对象 - */ - public void updateValue(BuildInfoModel buildInfoModel) { - this.setId(buildInfoModel.getId()); - this.setName(buildInfoModel.getName()); - this.setReleaseMethod(buildInfoModel.getReleaseMethod()); - this.setResultDirFile(buildInfoModel.getResultDirFile()); - this.setWorkspaceId(buildInfoModel.getWorkspaceId()); - } - - public void updateValue(BuildHistoryLog buildHistoryLog) { - // - this.setAfterOpt(buildHistoryLog.getAfterOpt()); - this.setReleaseMethod(buildHistoryLog.getReleaseMethod()); - this.setReleaseCommand(buildHistoryLog.getReleaseCommand()); - this.setReleasePath(buildHistoryLog.getReleasePath()); - this.setReleaseMethodDataId(buildHistoryLog.getReleaseMethodDataId()); - this.setClearOld(buildHistoryLog.getClearOld()); - this.setResultDirFile(buildHistoryLog.getResultDirFile()); - this.setName(buildHistoryLog.getBuildName()); - this.setId(buildHistoryLog.getBuildDataId()); - this.setWorkspaceId(buildHistoryLog.getWorkspaceId()); - } -} diff --git a/modules/server/src/main/java/io/jpom/build/BuildInfoManage.java b/modules/server/src/main/java/io/jpom/build/BuildInfoManage.java deleted file mode 100644 index fd30e57010..0000000000 --- a/modules/server/src/main/java/io/jpom/build/BuildInfoManage.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.build; - -import cn.hutool.core.bean.BeanUtil; -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.io.LineHandler; -import cn.hutool.core.io.file.FileCopier; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.JpomApplication; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.model.enums.BuildStatus; -import io.jpom.model.log.BuildHistoryLog; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.DbBuildHistoryLogService; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.CommandUtil; -import io.jpom.util.GitUtil; -import io.jpom.util.StringUtil; -import io.jpom.util.SvnKitUtil; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.Assert; - -import java.io.*; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -/** - * new build info manage runnable - * - * @author Hotstrip - * @since 20210-08-23 - */ -public class BuildInfoManage extends BaseBuild implements Runnable { - /** - * 缓存构建中 - */ - private static final Map BUILD_MANAGE_MAP = new ConcurrentHashMap<>(); - private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher(); - - private final BuildInfoModel buildInfoModel; - private final RepositoryModel repositoryModel; - private final File gitFile; - private Process process; - private String logId; - private final UserModel userModel; - private final BuildExtraModule buildExtraModule; - /** - * 延迟执行的时间(单位秒) - */ - private Integer delay; - - private BuildInfoManage(final BuildInfoModel buildInfoModel, final RepositoryModel repositoryModel, final UserModel userModel) { - super(BuildUtil.getLogFile(buildInfoModel.getId(), buildInfoModel.getBuildId()), - buildInfoModel.getId()); - this.buildInfoModel = buildInfoModel; - this.repositoryModel = repositoryModel; - this.gitFile = BuildUtil.getSourceById(buildInfoModel.getId()); - this.userModel = userModel; - // 解析 其他配置信息 - BuildExtraModule buildExtraModule = StringUtil.jsonConvert(this.buildInfoModel.getExtraData(), BuildExtraModule.class); - Assert.notNull(buildExtraModule, "构建信息缺失"); - // update value - buildExtraModule.updateValue(this.buildInfoModel); - this.buildExtraModule = buildExtraModule; - } - - /** - * 取消构建 - * - * @param id id - * @return bool - */ - public static boolean cancel(String id) { - BuildInfoManage buildInfoManage = BUILD_MANAGE_MAP.get(id); - if (buildInfoManage == null) { - return false; - } - if (buildInfoManage.process != null) { - try { - buildInfoManage.process.destroy(); - } catch (Exception ignored) { - } - } - buildInfoManage.updateStatus(BuildStatus.Cancel); - BUILD_MANAGE_MAP.remove(id); - return true; - } - - /** - * 创建构建 - * - * @param buildInfoModel 构建项 - * @param userModel 操作人 - * @param repositoryModel 仓库信息 - * @param delay 延迟执行的时间 单位秒 - * @return this - */ - public static BuildInfoManage create(final BuildInfoModel buildInfoModel, - final RepositoryModel repositoryModel, - final UserModel userModel, - Integer delay) { - if (BUILD_MANAGE_MAP.containsKey(buildInfoModel.getId())) { - throw new JpomRuntimeException("当前构建还在进行中"); - } - BuildInfoManage manage = new BuildInfoManage(buildInfoModel, repositoryModel, userModel); - BUILD_MANAGE_MAP.put(buildInfoModel.getId(), manage); - manage.delay = delay; - // - ThreadUtil.execute(manage); - return manage; - } - - @Override - protected boolean updateStatus(BuildStatus status) { - try { - //super.updateStatus(status); - BuildInfoService buildService = SpringUtil.getBean(BuildInfoService.class); - BuildInfoModel item = buildService.getByKey(this.buildModelId); - item.setStatus(status.getCode()); - buildService.update(item); - // - if (status == BuildStatus.Ing) { - this.insertLog(); - } else { - DbBuildHistoryLogService dbBuildHistoryLogService = SpringUtil.getBean(DbBuildHistoryLogService.class); - dbBuildHistoryLogService.updateLog(this.logId, status); - } - return true; - } catch (Exception e) { - DefaultSystemLog.getLog().error("构建状态变更失败", e); - return false; - } - } - - - /** - * 插入记录 - */ - private void insertLog() { - this.logId = IdUtil.fastSimpleUUID(); - BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); - // 更新其他配置字段 - buildHistoryLog.setResultDirFile(buildExtraModule.getResultDirFile()); - buildHistoryLog.setReleaseMethod(buildExtraModule.getReleaseMethod()); - buildHistoryLog.setReleaseMethodDataId(buildExtraModule.getReleaseMethodDataId()); - buildHistoryLog.setAfterOpt(buildExtraModule.getAfterOpt()); - buildHistoryLog.setWorkspaceId(this.buildInfoModel.getWorkspaceId()); - buildHistoryLog.setReleaseCommand(buildExtraModule.getReleaseCommand()); - BeanUtil.copyProperties(this.buildInfoModel, buildHistoryLog); - - buildHistoryLog.setId(this.logId); - buildHistoryLog.setBuildDataId(buildInfoModel.getId()); - buildHistoryLog.setStatus(BuildStatus.Ing.getCode()); - buildHistoryLog.setStartTime(System.currentTimeMillis()); - buildHistoryLog.setBuildNumberId(buildInfoModel.getBuildId()); - buildHistoryLog.setBuildName(buildInfoModel.getName()); - - DbBuildHistoryLogService dbBuildHistoryLogService = SpringUtil.getBean(DbBuildHistoryLogService.class); - dbBuildHistoryLogService.insert(buildHistoryLog); - } - - /** - * 打包构建产物 - */ - private boolean packageFile() { - ThreadUtil.sleep(2, TimeUnit.SECONDS); - String resultDirFile = buildInfoModel.getResultDirFile(); - File rootFile = this.gitFile; - boolean updateDirFile = false; - if (ANT_PATH_MATCHER.isPattern(resultDirFile)) { - // 通配模式 - String matchStr = FileUtil.normalize(StrUtil.SLASH + resultDirFile); - List paths = new ArrayList<>(); - // - FileUtil.walkFiles(this.gitFile.toPath(), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - return this.test(file); - } - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes exc) throws IOException { - return this.test(dir); - } - - private FileVisitResult test(Path path) { - String subPath = FileUtil.subPath(FileUtil.getAbsolutePath(rootFile), path.toFile()); - subPath = FileUtil.normalize(StrUtil.SLASH + subPath); - if (ANT_PATH_MATCHER.match(matchStr, subPath)) { - paths.add(subPath); - return FileVisitResult.TERMINATE; - } - return FileVisitResult.CONTINUE; - } - }); - String first = CollUtil.getFirst(paths); - if (StrUtil.isEmpty(first)) { - this.log(resultDirFile + " 没有匹配到任何文件"); - return false; - } - // 切换到匹配到到文件 - this.log(StrUtil.format("match {} {}", resultDirFile, first)); - resultDirFile = first; - updateDirFile = true; - } - File file = FileUtil.file(this.gitFile, resultDirFile); - if (!file.exists()) { - this.log(resultDirFile + "不存在,处理构建产物失败"); - return false; - } - File toFile = BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId(), resultDirFile); - FileCopier.create(file, toFile) - .setCopyContentIfDir(true) - .setOverride(true) - .setCopyAttributes(true) - .setCopyFilter(file1 -> !file1.isHidden()) - .copy(); - this.log(StrUtil.format("mv {} {}", resultDirFile, buildInfoModel.getBuildId())); - // 修改构建产物目录 - if (updateDirFile) { - DbBuildHistoryLogService dbBuildHistoryLogService = SpringUtil.getBean(DbBuildHistoryLogService.class); - dbBuildHistoryLogService.updateResultDirFile(this.logId, resultDirFile); - // - this.buildInfoModel.setResultDirFile(resultDirFile); - this.buildExtraModule.setResultDirFile(resultDirFile); - } - return true; - } - - /** - * 准备构建 - * - * @return false 执行异常需要结束 - */ - private boolean startReady() { - if (!updateStatus(BuildStatus.Ing)) { - BuildInfoManage.this.log("初始化构建记录失败,异常结束"); - return false; - } - this.log("#" + this.buildInfoModel.getBuildId() + " start build in file : " + FileUtil.getAbsolutePath(this.gitFile)); - if (delay != null && delay > 0) { - // 延迟执行 - this.log("Execution delayed by " + delay + " seconds"); - ThreadUtil.sleep(delay, TimeUnit.SECONDS); - } - return true; - } - - /** - * 拉取代码 - * - * @return false 执行异常需要结束 - */ - private boolean pull() { - try { - String msg = "error"; - if (repositoryModel.getRepoType() == RepositoryModel.RepoType.Git.getCode()) { - // git with password - Tuple tuple = GitUtil.getBranchAndTagListChek(repositoryModel); - String branchName = buildInfoModel.getBranchName(); - // 模糊匹配分支 - String newBranchName = GitUtil.fuzzyMatch(tuple.get(0), branchName); - if (StrUtil.isEmpty(newBranchName)) { - BuildInfoManage.this.log(branchName + " Did not match the corresponding branch"); - BuildInfoManage.this.updateStatus(BuildStatus.Error); - return false; - } - // 模糊匹配 标签 - String branchTagName = buildInfoModel.getBranchTagName(); - if (StrUtil.isNotEmpty(branchTagName)) { - String newBranchTagName = GitUtil.fuzzyMatch(tuple.get(1), branchTagName); - if (StrUtil.isEmpty(newBranchTagName)) { - BuildInfoManage.this.log(branchTagName + " Did not match the corresponding tag"); - BuildInfoManage.this.updateStatus(BuildStatus.Error); - return false; - } - // 标签拉取模式 - BuildInfoManage.this.log("repository [" + branchName + "] [" + branchTagName + "] clone pull from " + newBranchName + " " + newBranchTagName); - msg = GitUtil.checkoutPullTag(repositoryModel, gitFile, newBranchName, newBranchTagName, BuildInfoManage.this.getPrintWriter()); - } else { - // 分支模式 - BuildInfoManage.this.log("repository [" + branchName + "] clone pull from " + newBranchName); - msg = GitUtil.checkoutPull(repositoryModel, gitFile, newBranchName, BuildInfoManage.this.getPrintWriter()); - } - } else if (repositoryModel.getRepoType() == RepositoryModel.RepoType.Svn.getCode()) { - // svn - msg = SvnKitUtil.checkOut(repositoryModel, gitFile); - } - BuildInfoManage.this.log(msg); - } catch (Exception e) { - throw new RuntimeException(e); - } - return true; - } - - /** - * 执行构建命令 - * - * @return false 执行异常需要结束 - */ - private boolean executeCommand() { - String[] commands = CharSequenceUtil.splitToArray(buildInfoModel.getScript(), StrUtil.LF); - if (commands == null || commands.length <= 0) { - this.log("没有需要执行的命令"); - this.updateStatus(BuildStatus.Error); - return false; - } - for (String item : commands) { - try { - boolean s = runCommand(item); - if (!s) { - this.log("命令执行存在error"); - } - } catch (Exception e) { - this.log(item + " 执行异常", e); - return false; - } - } - return true; - } - - /** - * 打包发布 - * - * @return false 执行需要结束 - */ - private boolean packageRelease() { - boolean status = packageFile(); - if (status && buildInfoModel.getReleaseMethod() != BuildReleaseMethod.No.getCode()) { - // 发布文件 - new ReleaseManage(buildExtraModule, this.userModel, this, buildInfoModel.getBuildId()).start(); - } else { - // - updateStatus(BuildStatus.Success); - } - return true; - } - - @Override - public void run() { - // 初始化构建流程 准备->拉取代码->执行构建命令->打包发布 - Map> suppliers = new LinkedHashMap<>(10); - suppliers.put("startReady", BuildInfoManage.this::startReady); - suppliers.put("pull", BuildInfoManage.this::pull); - suppliers.put("executeCommand", BuildInfoManage.this::executeCommand); - suppliers.put("release", BuildInfoManage.this::packageRelease); - // 依次执行流程,发生异常结束整个流程 - String processName = StrUtil.EMPTY; - BaseServerController.resetInfo(this.userModel); - try { - for (Map.Entry> stringSupplierEntry : suppliers.entrySet()) { - processName = stringSupplierEntry.getKey(); - Supplier value = stringSupplierEntry.getValue(); - // - this.asyncWebHooks(processName); - Boolean aBoolean = value.get(); - if (!aBoolean) { - // 有条件结束构建流程 - this.asyncWebHooks("stop", "process", processName); - break; - } - } - this.asyncWebHooks("success"); - } catch (RuntimeException runtimeException) { - Throwable cause = runtimeException.getCause(); - this.log("构建失败:" + processName, cause == null ? runtimeException : cause); - this.asyncWebHooks(processName, "error", runtimeException.getMessage()); - } catch (Exception e) { - this.log("构建失败:" + processName, e); - this.asyncWebHooks(processName, "error", e.getMessage()); - } finally { - BUILD_MANAGE_MAP.remove(buildInfoModel.getId()); - BaseServerController.remove(); - this.asyncWebHooks("done"); - } - } - - private void log(String title, Throwable throwable) { - log(title, throwable, BuildStatus.Error); - } - - /** - * 执行命令 - * - * @param command 命令 - * @return 是否存在错误 - * @throws IOException IO - */ - private boolean runCommand(String command) throws IOException, InterruptedException { - this.log(command); - // - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.directory(this.gitFile); - List commands = CommandUtil.getCommand(); - commands.add(command); - processBuilder.command(commands); - final boolean[] status = new boolean[1]; - processBuilder.redirectErrorStream(true); - process = processBuilder.start(); - // - InputStream inputStream = process.getInputStream(); - IoUtil.readLines(inputStream, JpomApplication.getCharset(), (LineHandler) line -> { - log(line); - status[0] = true; - }); - int waitFor = process.waitFor(); - log("process result " + waitFor); - return status[0]; - } - - /** - * 执行 webhooks 通知 - * - * @param type 类型 - * @param other 其他参数 - */ - private void asyncWebHooks(String type, Object... other) { - String webhook = this.buildInfoModel.getWebhook(); - if (StrUtil.isEmpty(webhook)) { - return; - } - long triggerTime = SystemClock.now(); - ThreadUtil.execute(() -> { - try { - HttpRequest httpRequest = HttpUtil.createGet(webhook); - httpRequest.form("buildId", this.buildInfoModel.getId()); - httpRequest.form("buildName", this.buildInfoModel.getName()); - httpRequest.form("type", type, other); - httpRequest.form("triggerTime", triggerTime); - String body = httpRequest.execute().body(); - DefaultSystemLog.getLog().info(this.buildInfoModel.getName() + ":" + body); - } catch (Exception e) { - DefaultSystemLog.getLog().error("WebHooks 调用错误", e); - } - }); - - } -} diff --git a/modules/server/src/main/java/io/jpom/build/BuildUtil.java b/modules/server/src/main/java/io/jpom/build/BuildUtil.java deleted file mode 100644 index f9ce076f39..0000000000 --- a/modules/server/src/main/java/io/jpom/build/BuildUtil.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.build; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.core.util.ZipUtil; -import cn.hutool.crypto.SecureUtil; -import io.jpom.common.Const; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.RepositoryModel; -import io.jpom.system.ConfigBean; -import org.springframework.util.Assert; - -import java.io.File; - -/** - * 构建工具类 - * - * @author bwcx_jzy - * @date 2019/7/19 - */ -public class BuildUtil { - - public static Long buildCacheSize = 0L; - public static long tempFileCacheSize = 0L; - - /** - * 刷新存储文件大小 - */ - public static void reloadCacheSize() { - File buildDataDir = BuildUtil.getBuildDataDir(); - BuildUtil.buildCacheSize = FileUtil.size(buildDataDir); - // - File file = ConfigBean.getInstance().getTempPath(); - tempFileCacheSize = FileUtil.size(file); - } - - public static File getBuildDataFile(String id) { - return FileUtil.file(getBuildDataDir(), id); - } - -// /** -// * 获取代码路径 -// * -// * @param buildModel 实体 -// * @return file -// * @see BuildUtil#getSourceById -// */ -// @Deprecated -// public static File getSource(BuildModel buildModel) { -// return FileUtil.file(BuildUtil.getBuildDataFile(buildModel.getId()), "source"); -// } - - /** - * @param id 构建ID - * @return file - * @author Hotstrip - * 新版本获取代码路径 - * @since 2021-08-22 - */ - public static File getSourceById(String id) { - return FileUtil.file(BuildUtil.getBuildDataFile(id), "source"); - } - - public static File getBuildDataDir() { - return FileUtil.file(ConfigBean.getInstance().getDataPath(), "build"); - } - - /** - * 获取构建产物存放路径 - * - * @param buildModelId 构建实体 - * @param buildId id - * @param resultFile 结果目录 - * @return file - */ - public static File getHistoryPackageFile(String buildModelId, int buildId, String resultFile) { - if (StrUtil.isEmpty(buildModelId) || StrUtil.isEmpty(resultFile)) { - return null; - } - return FileUtil.file(getBuildDataFile(buildModelId), - "history", - BuildInfoModel.getBuildIdStr(buildId), - "result", resultFile); - } - - /** - * 如果为文件夹自动打包为zip ,反之返回null - * - * @param file file - * @return 压缩包文件 - */ - public static File isDirPackage(File file) { - if (file.isFile()) { - return null; - } - String name = FileUtil.getName(file); - if (StrUtil.isEmpty(name)) { - name = "result"; - } - File zipFile = FileUtil.file(file.getParentFile().getParentFile(), name + ".zip"); - if (!zipFile.exists()) { - // 不存在则打包 - ZipUtil.zip(file.getAbsolutePath(), zipFile.getAbsolutePath()); - } - return zipFile; - } - - /** - * 获取日志记录文件 - * - * @param buildModelId buildModelId - * @param buildId 构建编号 - * @return file - */ - public static File getLogFile(String buildModelId, int buildId) { - if (StrUtil.isEmpty(buildModelId)) { - return null; - } - return FileUtil.file(getBuildDataFile(buildModelId), - "history", - BuildInfoModel.getBuildIdStr(buildId), - "info.log"); - } - - /** - * get rsa file - * - * @param path 文件名 - * @return file - */ - public static File getRepositoryRsaFile(String path) { - File sshDir = FileUtil.file(ConfigBean.getInstance().getDataPath(), Const.SSH_KEY); - return FileUtil.file(sshDir, path); - } - - /** - * get rsa file - * - * @param repositoryModel 仓库 - * @return 文件 - */ - public static File getRepositoryRsaFile(RepositoryModel repositoryModel) { - if (StrUtil.isEmpty(repositoryModel.getRsaPrv())) { - return null; - } - // ssh - File rsaFile; - if (StrUtil.startWith(repositoryModel.getRsaPrv(), URLUtil.FILE_URL_PREFIX)) { - String rsaPath = StrUtil.removePrefix(repositoryModel.getRsaPrv(), URLUtil.FILE_URL_PREFIX); - rsaFile = FileUtil.file(rsaPath); - } else { - if (StrUtil.isEmpty(repositoryModel.getId())) { - rsaFile = FileUtil.file(ConfigBean.getInstance().getTempPath(), Const.SSH_KEY, SecureUtil.sha1(repositoryModel.getGitUrl()) + Const.ID_RSA); - } else { - rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModel.getId() + Const.ID_RSA); - } - // 写入 - FileUtil.writeUtf8String(repositoryModel.getRsaPrv(), rsaFile); - } - Assert.state(FileUtil.isFile(rsaFile), "仓库密钥文件不存在或者异常,请检查后操作"); - return rsaFile; - } -} diff --git a/modules/server/src/main/java/io/jpom/build/ReleaseManage.java b/modules/server/src/main/java/io/jpom/build/ReleaseManage.java deleted file mode 100644 index 7f29dbafb2..0000000000 --- a/modules/server/src/main/java/io/jpom/build/ReleaseManage.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.build; - -import cn.hutool.core.date.BetweenFormatter; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.ssh.JschUtil; -import cn.hutool.extra.ssh.Sftp; -import cn.hutool.http.HttpStatus; -import cn.hutool.system.SystemUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.jcraft.jsch.Session; -import io.jpom.model.AfterOpt; -import io.jpom.model.BaseEnum; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.SshModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceEnvVarModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.model.enums.BuildStatus; -import io.jpom.model.log.BuildHistoryLog; -import io.jpom.outgiving.OutGivingRun; -import io.jpom.service.node.NodeService; -import io.jpom.service.node.ssh.SshService; -import io.jpom.service.system.WorkspaceEnvVarService; -import io.jpom.system.ConfigBean; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.CommandUtil; - -import java.io.File; -import java.io.InputStream; -import java.util.List; -import java.util.Objects; - -/** - * 发布管理 - * - * @author bwcx_jzy - * @date 2019/7/19 - */ -public class ReleaseManage extends BaseBuild { - - private final UserModel userModel; - private final int buildId; - private final BuildExtraModule buildExtraModule; - private final File resultFile; - private BaseBuild baseBuild; - - /** - * new ReleaseManage constructor - * - * @param buildModel 构建信息 - * @param userModel 用户信息 - * @param baseBuild 基础构建 - * @param buildId 构建序号ID - */ - ReleaseManage(BuildExtraModule buildModel, UserModel userModel, BaseBuild baseBuild, int buildId) { - super(BuildUtil.getLogFile(buildModel.getId(), buildId), - buildModel.getId()); - this.buildExtraModule = buildModel; - this.buildId = buildId; - this.userModel = userModel; - this.baseBuild = baseBuild; - this.resultFile = BuildUtil.getHistoryPackageFile(this.buildModelId, this.buildId, buildModel.getResultDirFile()); - } - - /** - * 重新发布 - * - * @param buildHistoryLog 构建历史 - * @param userModel 用户 - */ - public ReleaseManage(BuildHistoryLog buildHistoryLog, UserModel userModel) { - super(BuildUtil.getLogFile(buildHistoryLog.getBuildDataId(), buildHistoryLog.getBuildNumberId()), - buildHistoryLog.getBuildDataId()); - this.buildExtraModule = new BuildExtraModule(); - this.buildExtraModule.updateValue(buildHistoryLog); - - this.buildId = buildHistoryLog.getBuildNumberId(); - this.userModel = userModel; - this.resultFile = BuildUtil.getHistoryPackageFile(this.buildModelId, this.buildId, buildHistoryLog.getResultDirFile()); - } - - - @Override - public boolean updateStatus(BuildStatus status) { - if (baseBuild == null) { - return super.updateStatus(status); - } else { - return baseBuild.updateStatus(status); - } - } - - /** - * 不修改为发布中状态 - */ - public void start2() { - this.log("start release:" + FileUtil.readableFileSize(FileUtil.size(this.resultFile))); - if (!this.resultFile.exists()) { - this.log("不存在构建产物"); - updateStatus(BuildStatus.PubError); - return; - } - long time = SystemClock.now(); - int releaseMethod = this.buildExtraModule.getReleaseMethod(); - this.log("release method:" + BaseEnum.getDescByCode(BuildReleaseMethod.class, releaseMethod)); - try { - if (releaseMethod == BuildReleaseMethod.Outgiving.getCode()) { - // - this.doOutGiving(); - } else if (releaseMethod == BuildReleaseMethod.Project.getCode()) { - AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, this.buildExtraModule.getAfterOpt()); - if (afterOpt == null) { - afterOpt = AfterOpt.No; - } - this.doProject(afterOpt, this.buildExtraModule.isClearOld()); - } else if (releaseMethod == BuildReleaseMethod.Ssh.getCode()) { - this.doSsh(); - } else if (releaseMethod == BuildReleaseMethod.LocalCommand.getCode()) { - this.localCommand(); - } else { - this.log(" 没有实现的发布分发:" + releaseMethod); - } - } catch (Exception e) { - this.pubLog("发布异常", e); - return; - } - this.log("release complete : " + DateUtil.formatBetween(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND)); - updateStatus(BuildStatus.PubSuccess); - } - - /** - * 修改为发布中状态 - */ - public void start() { - updateStatus(BuildStatus.PubIng); - this.start2(); - } - - /** - * 格式化命令模版 - * - * @param commands 命令 - */ - private void formatCommand(String[] commands) { - // - WorkspaceEnvVarService workspaceEnvVarService = SpringUtil.getBean(WorkspaceEnvVarService.class); - WorkspaceEnvVarModel workspaceEnvVarModel = new WorkspaceEnvVarModel(); - workspaceEnvVarModel.setWorkspaceId(this.buildExtraModule.getWorkspaceId()); - List list = workspaceEnvVarService.listByBean(workspaceEnvVarModel); - for (int i = 0; i < commands.length; i++) { - commands[i] = this.formatCommandItem(commands[i], list); - } - } - - /** - * 格式化命令模版 - * - * @param command 命令 - * @param list 工作空间变量列表 - * @return 格式化后 - */ - private String formatCommandItem(String command, List list) { - String replace = StrUtil.replace(command, "#{BUILD_ID}", this.buildModelId); - replace = StrUtil.replace(replace, "#{BUILD_NAME}", this.buildExtraModule.getName()); - replace = StrUtil.replace(replace, "#{BUILD_RESULT_FILE}", FileUtil.getAbsolutePath(this.resultFile)); - replace = StrUtil.replace(replace, "#{BUILD_NUMBER_ID}", this.buildId + StrUtil.EMPTY); - - if (list != null) { - for (WorkspaceEnvVarModel envVarModel : list) { - replace = StrUtil.replace(replace, StrUtil.format("#{{}}", envVarModel.getName()), envVarModel.getValue()); - } - } - return replace; - } - - /** - * 本地命令执行 - */ - private void localCommand() { - // 执行命令 - String[] commands = StrUtil.splitToArray(this.buildExtraModule.getReleaseCommand(), StrUtil.LF); - if (ArrayUtil.isEmpty(commands)) { - this.log("没有需要执行的ssh命令"); - return; - } - String command = StrUtil.EMPTY; - this.log(DateUtil.now() + " start exec"); - InputStream templateInputStream = null; - try { - templateInputStream = ResourceUtil.getStream("classpath:/bin/execTemplate." + CommandUtil.SUFFIX); - if (templateInputStream == null) { - this.log("系统中没有命令模版"); - return; - } - String sshExecTemplate = IoUtil.readUtf8(templateInputStream); - StringBuilder stringBuilder = new StringBuilder(sshExecTemplate); - // 替换变量 - this.formatCommand(commands); - // - stringBuilder.append(ArrayUtil.join(commands, StrUtil.LF)); - File tempPath = ConfigBean.getInstance().getTempPath(); - File commandFile = FileUtil.file(tempPath, "build", this.buildModelId + StrUtil.DOT + CommandUtil.SUFFIX); - FileUtil.writeUtf8String(stringBuilder.toString(), commandFile); - // - command = SystemUtil.getOsInfo().isWindows() ? StrUtil.EMPTY : CommandUtil.SUFFIX; - command += " " + FileUtil.getAbsolutePath(commandFile); - String result = CommandUtil.execSystemCommand(command); - this.log(result); - } catch (Exception e) { - this.pubLog("执行本地命令异常:" + command, e); - } finally { - IoUtil.close(templateInputStream); - } - } - - /** - * ssh 发布 - */ - private void doSsh() { - String releaseMethodDataId = this.buildExtraModule.getReleaseMethodDataId(); - SshService sshService = SpringUtil.getBean(SshService.class); - SshModel item = sshService.getByKey(releaseMethodDataId, false); - if (item == null) { - this.log("没有找到对应的ssh项:" + releaseMethodDataId); - return; - } - Session session = SshService.getSessionByModel(item); - try { - try (Sftp sftp = new Sftp(session, item.getCharsetT())) { - if (this.buildExtraModule.isClearOld() && StrUtil.isNotEmpty(this.buildExtraModule.getReleasePath())) { - try { - sftp.delDir(this.buildExtraModule.getReleasePath()); - } catch (Exception e) { - this.pubLog("清除构建产物失败", e); - } - } - String prefix = ""; - if (!StrUtil.startWith(this.buildExtraModule.getReleasePath(), StrUtil.SLASH)) { - prefix = sftp.pwd(); - } - String normalizePath = FileUtil.normalize(prefix + StrUtil.SLASH + this.buildExtraModule.getReleasePath()); - sftp.syncUpload(this.resultFile, normalizePath); - } catch (Exception e) { - this.pubLog("执行ssh发布异常", e); - } - } finally { - JschUtil.close(session); - } - this.log(""); - // 执行命令 - String[] commands = StrUtil.splitToArray(this.buildExtraModule.getReleaseCommand(), StrUtil.LF); - if (commands == null || commands.length <= 0) { - this.log("没有需要执行的ssh命令"); - return; - } - // 替换变量 - this.formatCommand(commands); - // - this.log(DateUtil.now() + " start exec"); - try { - String s = sshService.exec(item, commands); - this.log(s); - } catch (Exception e) { - this.pubLog("执行异常", e); - } - } - - /** - * 发布项目 - * - * @param afterOpt 后续操作 - */ - private void doProject(AfterOpt afterOpt, boolean clearOld) { - String releaseMethodDataId = this.buildExtraModule.getReleaseMethodDataId(); - String[] strings = StrUtil.splitToArray(releaseMethodDataId, ":"); - if (strings == null || strings.length != 2) { - throw new JpomRuntimeException(releaseMethodDataId + " error"); - } - NodeService nodeService = SpringUtil.getBean(NodeService.class); - NodeModel nodeModel = nodeService.getByKey(strings[0]); - Objects.requireNonNull(nodeModel, "节点不存在"); - - File zipFile = BuildUtil.isDirPackage(this.resultFile); - boolean unZip = true; - if (zipFile == null) { - zipFile = this.resultFile; - unZip = false; - } - JsonMessage jsonMessage = OutGivingRun.fileUpload(zipFile, - strings[1], - unZip, - afterOpt, - nodeModel, this.userModel, clearOld); - if (jsonMessage.getCode() == HttpStatus.HTTP_OK) { - this.log("发布项目包成功:" + jsonMessage); - } else { - throw new JpomRuntimeException("发布项目包失败:" + jsonMessage); - } - } - - /** - * 分发包 - */ - private void doOutGiving() { - String releaseMethodDataId = this.buildExtraModule.getReleaseMethodDataId(); - File zipFile = BuildUtil.isDirPackage(this.resultFile); - boolean unZip = true; - if (zipFile == null) { - zipFile = this.resultFile; - unZip = false; - } - OutGivingRun.startRun(releaseMethodDataId, zipFile, userModel, unZip); - this.log("开始执行分发包啦,请到分发中查看当前状态"); - } - - - /** - * 发布异常日志 - * - * @param title 描述 - * @param throwable 异常 - */ - private void pubLog(String title, Throwable throwable) { - log(title, throwable, BuildStatus.PubError); - } -} diff --git a/modules/server/src/main/java/io/jpom/common/BaseServerController.java b/modules/server/src/main/java/io/jpom/common/BaseServerController.java deleted file mode 100644 index 91150abcd9..0000000000 --- a/modules/server/src/main/java/io/jpom/common/BaseServerController.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.util.StrUtil; -import io.jpom.common.interceptor.LoginInterceptor; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.node.NodeService; -import io.jpom.system.ServerConfigBean; -import org.springframework.util.Assert; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.annotation.Resource; - -/** - * Jpom server 端 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public abstract class BaseServerController extends BaseJpomController { - private static final ThreadLocal USER_MODEL_THREAD_LOCAL = new ThreadLocal<>(); - - public static final String NODE_ID = "nodeId"; - - @Resource - protected NodeService nodeService; - - protected NodeModel getNode() { - NodeModel nodeModel = tryGetNode(); - Assert.notNull(nodeModel, "节点信息不正确,对应对节点不存在"); - return nodeModel; - } - - protected NodeModel tryGetNode() { - String nodeId = getParameter(NODE_ID); - if (StrUtil.isEmpty(nodeId)) { - return null; - } - return nodeService.getByKey(nodeId); - } - - @Override - public void resetInfo() { - USER_MODEL_THREAD_LOCAL.set(getUserModel()); - } - - public static void resetInfo(UserModel userModel) { - USER_MODEL_THREAD_LOCAL.set(userModel); - } - - protected UserModel getUser() { - return getUserByThreadLocal(); - } - - /** - * 从线程 缓存中获取 用户信息 - * - * @return 用户 - */ - public static UserModel getUserByThreadLocal() { - UserModel userModel = USER_MODEL_THREAD_LOCAL.get(); - Assert.notNull(userModel, ServerConfigBean.AUTHORIZE_TIME_OUT_CODE + StrUtil.EMPTY); - return userModel; - } - - public static void remove() { - USER_MODEL_THREAD_LOCAL.remove(); - } - - public static UserModel getUserModel() { - ServletRequestAttributes servletRequestAttributes = tryGetRequestAttributes(); - if (servletRequestAttributes == null) { - return null; - } - return (UserModel) servletRequestAttributes.getAttribute(LoginInterceptor.SESSION_NAME, RequestAttributes.SCOPE_SESSION); - } -} diff --git a/modules/server/src/main/java/io/jpom/common/GlobalDefaultExceptionHandler.java b/modules/server/src/main/java/io/jpom/common/GlobalDefaultExceptionHandler.java deleted file mode 100644 index d948d57428..0000000000 --- a/modules/server/src/main/java/io/jpom/common/GlobalDefaultExceptionHandler.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.exceptions.ValidateException; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.system.AgentException; -import io.jpom.system.AuthorizeException; -import io.jpom.system.JpomRuntimeException; -import io.jpom.system.ServerConfigBean; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConversionException; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.HttpMediaTypeNotSupportedException; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.NoHandlerFoundException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.nio.file.AccessDeniedException; - -/** - * 全局异常处理 - * - * @author jiangzeyin - * @date 2019/04/17 - */ -@ControllerAdvice -public class GlobalDefaultExceptionHandler { - - /** - * 声明要捕获的异常 - * - * @param request 请求 - * @param response 响应 - * @param e 异常 - */ - @ExceptionHandler({AuthorizeException.class, RuntimeException.class, Exception.class}) - public void delExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { - if (e instanceof AuthorizeException) { - AuthorizeException authorizeException = (AuthorizeException) e; - ServletUtil.write(response, authorizeException.getJsonMessage().toString(), MediaType.APPLICATION_JSON_VALUE); - } else if (e instanceof JpomRuntimeException) { - DefaultSystemLog.getLog().error("global handle exception: {}", request.getRequestURI(), e.getCause()); - ServletUtil.write(response, JsonMessage.getString(500, e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } else { - DefaultSystemLog.getLog().error("global handle exception: {}", request.getRequestURI(), e); - boolean causedBy = ExceptionUtil.isCausedBy(e, AccessDeniedException.class); - if (causedBy) { - ServletUtil.write(response, JsonMessage.getString(500, "操作文件权限异常,请手动处理:" + e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - return; - } - ServletUtil.write(response, JsonMessage.getString(500, "服务异常:" + e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } - } - - /** - * 插件端异常 - *

- * 避免重复记录堆栈 - * - * @param request 请求 - * @param response 响应 - * @param e 异常 - * @author jzy - * @since 2021-08-01 - */ - @ExceptionHandler({AgentException.class}) - public void agentExceptionHandler(HttpServletRequest request, HttpServletResponse response, AgentException e) { - Throwable cause = e.getCause(); - if (cause != null) { - DefaultSystemLog.getLog().error("controller " + request.getRequestURI(), cause); - } - ServletUtil.write(response, JsonMessage.getString(405, e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } - - /** - * git 仓库操作相关异常 - * - * @param request 请求 - * @param response 响应 - * @param e 异常 - */ - @ExceptionHandler({GitAPIException.class}) - public void gitExceptionHandler(HttpServletRequest request, HttpServletResponse response, GitAPIException e) { - DefaultSystemLog.getLog().warn("GitAPIException: " + request.getRequestURI(), e); - ServletUtil.write(response, JsonMessage.getString(405, "git 仓库操作异常:" + e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } - - /** - * 声明要捕获的异常 (参数,状态,验证异常) - * - * @param request 请求 - * @param response 响应 - * @param e 异常 - */ - @ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class, ValidateException.class}) - public void paramExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { - DefaultSystemLog.getLog().error("controller " + request.getRequestURI(), e); - String message = e.getMessage(); - if (ObjectUtil.equals(message, ServerConfigBean.AUTHORIZE_TIME_OUT_CODE)) { - ServletUtil.write(response, JsonMessage.getString(ServerConfigBean.AUTHORIZE_TIME_OUT_CODE, "登录信息已失效,重新登录"), MediaType.APPLICATION_JSON_VALUE); - } else { - ServletUtil.write(response, JsonMessage.getString(405, message), MediaType.APPLICATION_JSON_VALUE); - } - } - - @ExceptionHandler({HttpMessageNotReadableException.class, HttpMessageConversionException.class}) - @ResponseBody - public JsonMessage handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - DefaultSystemLog.getLog().warn("参数解析异常:{}", e.getMessage()); - return new JsonMessage<>(HttpStatus.EXPECTATION_FAILED.value(), "传入的参数格式不正确"); - } - - @ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class}) - @ResponseBody - public JsonMessage handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { - return new JsonMessage<>(HttpStatus.METHOD_NOT_ALLOWED.value(), "不被支持的请求方式", e.getMessage()); - } - - @ExceptionHandler({NoHandlerFoundException.class}) - public void handleNoHandlerFoundException(HttpServletResponse response, NoHandlerFoundException e) { - ServletUtil.write(response, JsonMessage.getString(HttpStatus.NOT_FOUND.value(), "没有找到对应的资源", e.getMessage()), MediaType.APPLICATION_JSON_VALUE); - } -} diff --git a/modules/server/src/main/java/io/jpom/common/UrlRedirectUtil.java b/modules/server/src/main/java/io/jpom/common/UrlRedirectUtil.java deleted file mode 100644 index 8231138401..0000000000 --- a/modules/server/src/main/java/io/jpom/common/UrlRedirectUtil.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.extra.servlet.ServletUtil; -import org.springframework.http.HttpHeaders; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.function.Function; - -/** - * url 重定向 - * 配置nginx 代理实现 - * - * @author bwcx_jzy - * @date 2019/11/14 - */ -public class UrlRedirectUtil { - -// /** -// * 获取 protocol 协议完全跳转 -// * -// * @param request 请求 -// * @param url 跳转url -// * @see javax.servlet.http.HttpUtils#getRequestURL -// */ -// public static String getRedirect(HttpServletRequest request, String url) { -// int port = getPort(request); -// return getRedirect(request, url, port); -// } - -// /** -// * 获取 protocol 协议完全跳转 -// * -// * @param request 请求 -// * @param url 跳转url -// * @see javax.servlet.http.HttpUtils#getRequestURL -// */ -// public static String getRedirect(HttpServletRequest request, String url, int port) { -// String proto = ServletUtil.getHeaderIgnoreCase(request, "X-Forwarded-Proto"); -// if (proto == null) { -// return url; -// } else { -// String host = request.getHeader(HttpHeaders.HOST); -// if (StrUtil.isEmpty(host)) { -// throw new RuntimeException("请配置host header"); -// } -// if ("http".equals(proto) && port == 0) { -// port = 80; -// } else if ("https".equals(proto) && port == 0) { -// port = 443; -// } -// String format = StrUtil.format("{}://{}:{}{}", proto, host, port, url); -// return URLUtil.normalize(format); -// } -// } - -// /** -// * 获取 protocol 协议完全跳转 -// * -// * @param request 请求 -// * @param response 响应 -// * @param url 跳转url -// * @throws IOException io -// * @see javax.servlet.http.HttpUtils#getRequestURL -// */ -// public static void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url, int port) throws IOException { -// String toUrl = getRedirect(request, url, port); -// response.sendRedirect(toUrl); -// } - -// -// /** -// * 获取 protocol 协议完全跳转 -// * -// * @param request 请求 -// * @param response 响应 -// * @param url 跳转url -// * @throws IOException io -// * @see javax.servlet.http.HttpUtils#getRequestURL -// */ -// public static void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException { -// int port = getPort(request); -// sendRedirect(request, response, url, port); -// } - - private static int getPort(HttpServletRequest request) { - String proxyPort = ServletUtil.getHeaderIgnoreCase(request, "X-Forwarded-Port"); - int port = 0; - if (StrUtil.isNotEmpty(proxyPort)) { - port = Integer.parseInt(proxyPort); - } - return port; - } - - /** - * 二级代理路径 - * - * @param request req - * @return context-path+nginx配置 - */ - public static String getHeaderProxyPath(HttpServletRequest request, String headName) { - return getHeaderProxyPath(request, headName, null); - } - - /** - * 二级代理路径 - * - * @param request req - * @return context-path+nginx配置 - */ - public static String getHeaderProxyPath(HttpServletRequest request, String headName, Function function) { - String proxyPath = ServletUtil.getHeaderIgnoreCase(request, headName); - // - if (StrUtil.isEmpty(proxyPath)) { - return request.getContextPath(); - } - // 回调处理 - if (function != null) { - proxyPath = function.apply(proxyPath); - } - // - proxyPath = FileUtil.normalize(request.getContextPath() + StrUtil.SLASH + proxyPath); - if (proxyPath.endsWith(StrUtil.SLASH)) { - proxyPath = proxyPath.substring(0, proxyPath.length() - 1); - } - return proxyPath; - } -} diff --git a/modules/server/src/main/java/io/jpom/common/forward/NodeForward.java b/modules/server/src/main/java/io/jpom/common/forward/NodeForward.java deleted file mode 100644 index 01a1ae6e55..0000000000 --- a/modules/server/src/main/java/io/jpom/common/forward/NodeForward.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.forward; - -import cn.hutool.core.net.URLEncoder; -import cn.hutool.core.net.url.UrlQuery; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; -import cn.hutool.http.HttpStatus; -import cn.hutool.http.HttpUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.TypeReference; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.node.NodeService; -import io.jpom.system.*; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Map; - -/** - * 节点请求转发 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public class NodeForward { - - /** - * 普通消息转发 - * - * @param nodeModel 节点 - * @param request 请求 - * @param nodeUrl 节点的url - * @param 泛型 - * @return JSON - */ - public static JsonMessage request(NodeModel nodeModel, HttpServletRequest request, NodeUrl nodeUrl) { - return request(nodeModel, request, nodeUrl, true, null, null, null, null); - } - - /** - * 普通消息转发 - * - * @param nodeModel 节点 - * @param nodeUrl 节点的url - * @param jsonObject 数据 - * @param userModel user - * @return JSON - */ - public static JsonMessage request(NodeModel nodeModel, NodeUrl nodeUrl, UserModel userModel, JSONObject jsonObject) { - return request(nodeModel, null, nodeUrl, true, userModel, jsonObject, null, null); - } - - /** - * 普通消息转发 - * - * @param nodeModel 节点 - * @param nodeUrl 节点的url - * @param pName 主参数名 - * @param pVal 主参数值 - * @param val 其他参数 - * @return JSON - */ - public static JsonMessage requestBySys(NodeModel nodeModel, NodeUrl nodeUrl, String pName, Object pVal, Object... val) { - return request(nodeModel, null, nodeUrl, false, null, null, pName, pVal, val); - } - - /** - * 普通消息转发 - * - * @param nodeModel 节点 - * @param request 请求 - * @param nodeUrl 节点的url - * @param pVal 主参数值 - * @param pName 主参数名 - * @param userModel 用户 - * @param jsonData 数据 - * @param mustUser 是否必须需要user - * @param val 其他参数 - * @param 泛型 - * @return JSON - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private static JsonMessage request(NodeModel nodeModel, - HttpServletRequest request, - NodeUrl nodeUrl, - boolean mustUser, - UserModel userModel, - JSONObject jsonData, - String pName, - Object pVal, - Object... val) { - String url = nodeModel.getRealUrl(nodeUrl); - HttpRequest httpRequest = HttpUtil.createPost(url); - // - if (mustUser) { - if (userModel == null) { - userModel = BaseServerController.getUserModel(); - } - } - // - addUser(httpRequest, nodeModel, nodeUrl, userModel); - Map params = null; - if (request != null) { - params = request.getParameterMap(); - for (Map.Entry entry : (Iterable>) params.entrySet()) { - String[] values = entry.getValue(); - if (values != null) { - for (int i = 0, len = values.length; i < len; i++) { - // 参数 URL 编码,避免 特殊符号 不生效 - values[i] = URLUtil.encodeAll(values[i]); - } - entry.setValue(values); - } - } - } - httpRequest.form(pName, pVal, val); - // - if (jsonData != null) { - httpRequest.form(jsonData); - } - HttpResponse response; - try { - response = httpRequest - .form(params) - .execute(); - } catch (Exception e) { - /** - * @author Hotstrip - * revert version and add log print - * @author jzy 2021-08-01 add exception - */ - DefaultSystemLog.getLog().error("node [{}] connect failed...message: [{}]", nodeModel.getName(), e.getMessage()); - throw new AgentException(nodeModel.getName() + "节点异常:" + e.getMessage()); - } - // - return parseBody(response); - } - - /** - * 普通消息转发,并解析数据 - * - * @param nodeModel 节点 - * @param request 请求 - * @param nodeUrl 节点的url - * @param tClass 要解析的类 - * @param 泛型 - * @return T - */ - public static T requestData(NodeModel nodeModel, NodeUrl nodeUrl, HttpServletRequest request, Class tClass) { - JsonMessage jsonMessage = request(nodeModel, request, nodeUrl); - return jsonMessage.getData(tClass); - } - - /** - * 普通消息转发,并解析数据 - * - * @param nodeModel 节点 - * @param nodeUrl 节点的url - * @param tClass 要解析的类 - * @param 泛型 - * @param name 参数名 - * @param parameters 其他参数 - * @param value 值 - * @return T - */ - public static T requestData(NodeModel nodeModel, NodeUrl nodeUrl, Class tClass, String name, Object value, Object... parameters) { - String url = nodeModel.getRealUrl(nodeUrl); - // - HttpRequest httpRequest = HttpUtil.createPost(url); - if (name != null && value != null) { - httpRequest.form(name, value, parameters); - } - // - addUser(httpRequest, nodeModel, nodeUrl); - HttpResponse response; - try { - // - response = httpRequest - .execute(); - } catch (Exception e) { - /** - * @author Hotstrip - * revert version and add log print - * @author jzy 2021-08-01 add exception - */ - DefaultSystemLog.getLog().error("node [{}] connect failed", nodeModel.getName(), e); - throw new AgentException(nodeModel.getName() + "节点异常:" + e.getMessage()); - } - // - JsonMessage jsonMessage = parseBody(response); - return jsonMessage.getData(tClass); - } - - - /** - * 上传文件消息转发 - * - * @param nodeModel 节点 - * @param request 请求 - * @param nodeUrl 节点的url - * @return json - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public static JsonMessage requestMultipart(NodeModel nodeModel, MultipartHttpServletRequest request, NodeUrl nodeUrl) { - String url = nodeModel.getRealUrl(nodeUrl); - HttpRequest httpRequest = HttpUtil.createPost(url); - addUser(httpRequest, nodeModel, nodeUrl); - // - Map params = ServletUtil.getParams(request); - httpRequest.form(params); - // - Map fileMap = request.getFileMap(); - fileMap.forEach((s, multipartFile) -> { - try { - httpRequest.form(s, multipartFile.getBytes(), multipartFile.getOriginalFilename()); - } catch (IOException e) { - DefaultSystemLog.getLog().error("转发文件异常", e); - } - }); - HttpResponse response; - try { - // @author jzy add timeout - httpRequest.timeout(ServerExtConfigBean.getInstance().getUploadFileTimeOut()); - response = httpRequest.execute(); - } catch (Exception e) { - throw new AgentException(nodeModel.getName() + "节点异常:" + e.getMessage(), e); - } - return parseBody(response); - } - - /** - * 下载文件消息转发 - * - * @param nodeModel 节点 - * @param request 请求 - * @param response 响应 - * @param nodeUrl 节点的url - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public static void requestDownload(NodeModel nodeModel, HttpServletRequest request, HttpServletResponse response, NodeUrl nodeUrl) { - String url = nodeModel.getRealUrl(nodeUrl); - HttpRequest httpRequest = HttpUtil.createGet(url); - addUser(httpRequest, nodeModel, nodeUrl); - // - Map params = ServletUtil.getParams(request); - httpRequest.form(params); - // - HttpResponse response1; - try { - // @author jzy add timeout - httpRequest.timeout(ServerExtConfigBean.getInstance().getUploadFileTimeOut()); - response1 = httpRequest.execute(); - } catch (Exception e) { - throw new AgentException(nodeModel.getName() + "节点异常:" + e.getMessage(), e); - } - String contentDisposition = response1.header("Content-Disposition"); - response.setHeader("Content-Disposition", contentDisposition); - String contentType = response1.header("Content-Type"); - response.setContentType(contentType); - ServletUtil.write(response, response1.bodyStream()); - } - - private static void addUser(HttpRequest httpRequest, NodeModel nodeModel, NodeUrl nodeUrl) { - UserModel userModel = BaseServerController.getUserModel(); - addUser(httpRequest, nodeModel, nodeUrl, userModel); - } - - /** - * 添加agent 授权信息header - * - * @param httpRequest request - * @param nodeModel 节点 - * @param userModel 用户 - */ - private static void addUser(HttpRequest httpRequest, NodeModel nodeModel, NodeUrl nodeUrl, UserModel userModel) { - // 判断开启状态 - if (!nodeModel.isOpenStatus()) { - throw new JpomRuntimeException(nodeModel.getName() + "节点未启用"); - } - if (userModel != null) { - httpRequest.header(ConfigBean.JPOM_SERVER_USER_NAME, URLEncoder.DEFAULT.encode(UserModel.getOptUserName(userModel), CharsetUtil.CHARSET_UTF_8)); -// httpRequest.header(ConfigBean.JPOM_SERVER_SYSTEM_USER_ROLE, userModel.getUserRole(nodeModel).name()); - } - if (StrUtil.isEmpty(nodeModel.getLoginPwd())) { - NodeService nodeService = SpringUtil.getBean(NodeService.class); - NodeModel model = nodeService.getByKey(nodeModel.getId(), false); - nodeModel.setLoginPwd(model.getLoginPwd()); - nodeModel.setLoginName(model.getLoginName()); - } - httpRequest.header(ConfigBean.JPOM_AGENT_AUTHORIZE, nodeModel.toAuthorize()); - httpRequest.header(Const.WORKSPACEID_REQ_HEADER, nodeModel.getWorkspaceId()); - // - int timeOut = nodeModel.getTimeOut(); - if (nodeUrl.getTimeOut() != -1 && timeOut > 0) { - // - timeOut = Math.max(timeOut, 2); - httpRequest.timeout(timeOut * 1000); - } - } - - /** - * 获取节点socket 信息 - * - * @param nodeModel 节点信息 - * @param nodeUrl url - * @return url - */ - public static String getSocketUrl(NodeModel nodeModel, NodeUrl nodeUrl, UserModel userInfo, Object... parameters) { - String ws; - if ("https".equalsIgnoreCase(nodeModel.getProtocol())) { - ws = "wss"; - } else { - ws = "ws"; - } - if (StrUtil.isEmpty(nodeModel.getLoginPwd())) { - NodeService nodeService = SpringUtil.getBean(NodeService.class); - NodeModel model = nodeService.getByKey(nodeModel.getId(), false); - nodeModel.setLoginPwd(model.getLoginPwd()); - nodeModel.setLoginName(model.getLoginName()); - } - UrlQuery urlQuery = new UrlQuery(); - urlQuery.add(ConfigBean.JPOM_AGENT_AUTHORIZE, nodeModel.toAuthorize()); - // 兼容旧版本-节点升级 @author jzy - urlQuery.add("name", nodeModel.getLoginName()); - urlQuery.add("password", nodeModel.getLoginPwd()); - // - String optUser = UserModel.getOptUserName(userInfo); - optUser = URLUtil.encode(optUser); - urlQuery.add("optUser", optUser); - if (ArrayUtil.isNotEmpty(parameters)) { - for (int i = 0; i < parameters.length; i += 2) { - urlQuery.add(parameters[i].toString(), parameters[i + 1]); - } - } - return StrUtil.format("{}://{}{}?{}", ws, nodeModel.getUrl(), nodeUrl.getUrl(), urlQuery.toString()); - } - - /** - * 解析结果 - * - * @param response 响应 - * @return json - */ - private static JsonMessage parseBody(HttpResponse response) { - int status = response.getStatus(); - if (status != HttpStatus.HTTP_OK) { - throw new AgentException("agent 端响应异常:" + status); - } - String body = response.body(); - return toJsonMessage(body); - } - - private static JsonMessage toJsonMessage(String body) { - if (StrUtil.isEmpty(body)) { - throw new AgentException("agent 端响应内容为空"); - } - JsonMessage jsonMessage = JSON.parseObject(body, new TypeReference>() { - }); - if (jsonMessage.getCode() == ConfigBean.AUTHORIZE_ERROR) { - throw new AuthorizeException(jsonMessage, jsonMessage.getMsg()); - } - return jsonMessage; - } -} diff --git a/modules/server/src/main/java/io/jpom/common/forward/NodeUrl.java b/modules/server/src/main/java/io/jpom/common/forward/NodeUrl.java deleted file mode 100644 index c6805ceb0e..0000000000 --- a/modules/server/src/main/java/io/jpom/common/forward/NodeUrl.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.forward; - -import io.jpom.system.ServerExtConfigBean; - -/** - * agent 端的请求地址枚举 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public enum NodeUrl { - /** - * Jpom agent 信息 - */ - Info("/info"), - /** - * - */ - GetTop("/getTop"), - GetDirectTop("/getDirectTop"), - Status("/status"), - exportTop("/exportTop"), - Kill("/kill.json"), - - ProcessList("/processList", -1), - /** - * socket 连接 ,第一节项目id 第二节用户信息 - */ - TopSocket("/console"), - /** - * 脚本模板 模板id - */ - Script_Run("/script_run"), - /** - * Tomcat - */ - Tomcat_Socket("/tomcat_log"), - /** - * 节点升级 - */ - NodeUpdate("/node_update"), - - WhitelistDirectory_Submit("/system/whitelistDirectory_submit"), - - WhitelistDirectory_data("/system/whitelistDirectory_data"), - - Manage_SaveProject("/manage/saveProject"), - - Manage_DeleteProject("/manage/deleteProject"), - - Manage_ReleaseOutGiving("/manage/releaseOutGiving"), - - Manage_GetProjectInfo("/manage/getProjectInfo"), - - Manage_Jude_Lib("/manage/judge_lib.json"), - -// Manage_GetProjectGroup("/manage/getProjectGroup"), - - Manage_GetProjectItem("/manage/getProjectItem"), - - Manage_GetProjectStatus("/manage/getProjectStatus"), - - Manage_Restart("/manage/restart"), - - Manage_Start("/manage/start"), - - Manage_Stop("/manage/stop"), - - Manage_GetProjectPort("/manage/getProjectPort"), - - Manage_GetProjectCopyPort("/manage/getProjectCopyPort"), - - Manage_ProjectCopyList("/manage/project_copy_list"), - - Manage_Recover_List_Data("/manage/recover/list_data"), - - Manage_Recover_Item_Data("/manage/recover/item_data"), - - - Manage_File_GetFileList("/manage/file/getFileList"), - /** - * jzy add timeout - */ - Manage_File_Upload("/manage/file/upload", ServerExtConfigBean.getInstance().getUploadFileTimeOut()), - - Manage_File_DeleteFile("/manage/file/deleteFile"), - - Manage_File_UpdateConfigFile("/manage/file/update_config_file"), - - Manage_File_ReadFile("/manage/file/read_file"), - - Manage_File_Remote_Download("/manage/file/remote_download"), - - Manage_File_Download("/manage/file/download"), - - - Manage_Log_LogSize("/manage/log/logSize"), - - Manage_Log_ResetLog("/manage/log/resetLog"), - - Manage_Log_logBack_delete("/manage/log/logBack_delete"), - - Manage_Log_logBack_download("/manage/log/logBack_download"), - - Manage_Log_logBack("/manage/log/logBack"), - - Manage_Log_export("/manage/log/export.html"), - - Manage_internal_data("/manage/internal_data"), - Manage_internal_stack("/manage/internal_stack"), - Manage_internal_ram("/manage/internal_ram"), - Manage_internal_threadInfos("/manage/threadInfos"), - - /** - * jdk - */ - Manage_jdk_list("/manage/jdk/list"), - Manage_jdk_update("/manage/jdk/update"), - Manage_jdk_delete("/manage/jdk/delete"), - - System_Nginx_list_data("/system/nginx/list_data.json"), - System_Nginx_Tree("/system/nginx/tree.json"), - - System_Nginx_item_data("/system/nginx/item_data"), - - System_Nginx_updateNgx("/system/nginx/updateNgx"), - - System_Nginx_delete("/system/nginx/delete"), - - System_Nginx_status("/system/nginx/status"), - System_Nginx_config("/system/nginx/config"), - System_Nginx_open("/system/nginx/open"), - System_Nginx_close("/system/nginx/close"), - System_Nginx_updateConf("/system/nginx/updateConf"), - System_Nginx_reload("/system/nginx/reload"), - - System_Certificate_saveCertificate("/system/certificate/saveCertificate"), - System_Certificate_getCertList("/system/certificate/getCertList"), - System_Certificate_delete("/system/certificate/delete"), - System_Certificate_export("/system/certificate/export"), - - Script_List("/script/list.json"), - Script_Item("/script/item.json"), - Script_Save("/script/save.json"), - Script_Upload("/script/upload.json"), - Script_Del("/script/del.json"), - - Tomcat_List("/tomcat/list"), - Tomcat_Add("/tomcat/add"), - Tomcat_Update("/tomcat/update"), - Tomcat_Delete("/tomcat/delete"), - Tomcat_Start("/tomcat/start"), - Tomcat_Stop("/tomcat/stop"), - Tomcat_Restart("/tomcat/restart"), - Tomcat_GetItem("/tomcat/getItem"), - Tomcat_LOG_List("/tomcat/logList"), - Tomcat_GetTomcatProjectList("/tomcat/getTomcatProjectList"), - Tomcat_GetTomcatStatus("/tomcat/getTomcatStatus"), - Tomcat_TomcatProjectManage("/tomcat/tomcatProjectManage"), - Tomcat_File_GetFileList("/tomcat/getFileList"), - Tomcat_File_DeleteFile("/tomcat/deleteFile"), - Tomcat_File_Download("/tomcat/download"), - Tomcat_File_Upload("/tomcat/upload"), - Tomcat_File_UploadWar("/tomcat/uploadWar"), - /** - * 缓存 - */ - Cache("/system/cache"), - /** - * 缓存 - */ - ClearCache("/system/clearCache"), - /** - * 系统日志 - */ - SystemLog("/system/log_data.json"), - - DelSystemLog("/system/log_del.json"), - - DownloadSystemLog("/system/log_download"), - /** - * 更新系统jar包 - */ - SystemUploadJar("/system/uploadJar.json"), - /** - * 更新系统jar包 - */ - CHECK_VERSION("/system/check_version.json"), - /** - * 远程升级 - */ - REMOTE_UPGRADE("/system/remote_upgrade.json"), - CHANGE_LOG("/system/change_log"), - - /** - * - */ - SystemGetConfig("/system/getConfig.json"), - SystemSaveConfig("/system/save_config.json"), - ; - /** - * 相对请求地址 - */ - private final String url; - private int timeOut; - - public String getUrl() { - return url; - } - - public int getTimeOut() { - return timeOut; - } - - NodeUrl(String url, int timeOut) { - this.url = url; - this.timeOut = timeOut; - } - - NodeUrl(String url) { - this.url = url; - } -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/BaseJpomInterceptor.java b/modules/server/src/main/java/io/jpom/common/interceptor/BaseJpomInterceptor.java deleted file mode 100644 index edc5046248..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/BaseJpomInterceptor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.interceptor.BaseInterceptor; -import io.jpom.common.UrlRedirectUtil; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * 拦截器 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public abstract class BaseJpomInterceptor extends BaseInterceptor { - - public static final String PROXY_PATH = "Jpom-ProxyPath"; - -// static boolean isPage(HandlerMethod handlerMethod) { -// ResponseBody responseBody = handlerMethod.getMethodAnnotation(ResponseBody.class); -// if (responseBody == null) { -// RestController restController = handlerMethod.getBeanType().getAnnotation(RestController.class); -// return restController == null; -// } -// return false; -// } - -// public static void sendRedirects(HttpServletRequest request, HttpServletResponse response, String url) throws IOException { -// String newUrl = UrlRedirectUtil.getHeaderProxyPath(request, PROXY_PATH) + url; -// UrlRedirectUtil.sendRedirect(request, response, newUrl); -// } -// -// public static String getRedirect(HttpServletRequest request, String url) { -// String newUrl = UrlRedirectUtil.getHeaderProxyPath(request, PROXY_PATH) + url; -// String redirect = UrlRedirectUtil.getRedirect(request, newUrl); -// return String.format("redirect:%s", redirect); -// } - -// public static String getHeaderProxyPath(HttpServletRequest request) { -// String proxyPath = ServletUtil.getHeaderIgnoreCase(request, PROXY_PATH); -// if (StrUtil.isEmpty(proxyPath)) { -// return StrUtil.EMPTY; -// } -// if (proxyPath.endsWith(StrUtil.SLASH)) { -// proxyPath = proxyPath.substring(0, proxyPath.length() - 1); -// } -// return proxyPath; -// } -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/IpInterceptor.java b/modules/server/src/main/java/io/jpom/common/interceptor/IpInterceptor.java deleted file mode 100644 index bc36d12bca..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/IpInterceptor.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.hutool.core.net.NetUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.interceptor.InterceptorPattens; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.ServerOpenApi; -import io.jpom.model.data.SystemIpConfigModel; -import io.jpom.service.system.SystemParametersServer; -import org.springframework.http.MediaType; -import org.springframework.web.method.HandlerMethod; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * ip 访问限制拦截器 - * - * @author bwcx_jzy - * @date 2021/4/18 - */ -@InterceptorPattens(sort = -2, exclude = ServerOpenApi.API + "**") -public class IpInterceptor extends BaseJpomInterceptor { - - private static final int IP_ACCESS_CODE = 999; - - @Override - protected boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { - String clientIp = ServletUtil.getClientIP(request); - if (StrUtil.equals(NetUtil.LOCAL_IP, clientIp)) { - return true; - } - SystemParametersServer bean = SpringUtil.getBean(SystemParametersServer.class); - SystemIpConfigModel config = bean.getConfig(SystemIpConfigModel.ID, SystemIpConfigModel.class); - if (config == null) { - return true; - } - // 判断不允许访问 - String prohibited = config.getProhibited(); - if (StrUtil.isNotEmpty(prohibited) && this.checkIp(prohibited, clientIp, false)) { - ServletUtil.write(response, JsonMessage.getString(IP_ACCESS_CODE, "Prohibition of access"), MediaType.APPLICATION_JSON_VALUE); - return false; - } - String allowed = config.getAllowed(); - if (StrUtil.isEmpty(allowed) || this.checkIp(allowed, clientIp, true)) { - return true; - } - ServletUtil.write(response, JsonMessage.getString(IP_ACCESS_CODE, "Prohibition of access"), MediaType.APPLICATION_JSON_VALUE); - return false; - } - - - /** - * 检查ip 地址是否可以访问 - * - * @param value 配置的值 - * @param ip 被检查的 ip 地址 - * @param checkAll 是否检查开放所有、避免禁止所有 ip 访问 - * @return true 命中检查项 - */ - private boolean checkIp(String value, String ip, boolean checkAll) { - long ipNum = NetUtil.ipv4ToLong(ip); - String[] split = StrUtil.splitToArray(value, StrUtil.LF); - boolean check; - for (String itemIp : split) { - itemIp = itemIp.trim(); - if (itemIp.startsWith("#")) { - continue; - } - if (checkAll && StrUtil.equals(itemIp, "0.0.0.0")) { - // 开放所有 - return true; - } - if (StrUtil.contains(itemIp, CharUtil.SLASH)) { - // ip段 - String[] itemIps = StrUtil.splitToArray(itemIp, StrUtil.SLASH); - long aBegin = NetUtil.ipv4ToLong(itemIps[0]); - long aEnd = NetUtil.ipv4ToLong(itemIps[1]); - check = (ipNum >= aBegin) && (ipNum <= aEnd); - } else { - check = StrUtil.equals(itemIp, ip); - } - if (check) { - return true; - } - } - return false; - } -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/LoginInterceptor.java b/modules/server/src/main/java/io/jpom/common/interceptor/LoginInterceptor.java deleted file mode 100644 index 4f728ead2f..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/LoginInterceptor.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.jwt.JWT; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.interceptor.InterceptorPattens; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.BaseServerController; -import io.jpom.common.ServerOpenApi; -import io.jpom.model.data.UserModel; -import io.jpom.service.user.UserService; -import io.jpom.system.ServerConfigBean; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.system.db.DbConfig; -import io.jpom.util.JwtUtil; -import org.springframework.http.MediaType; -import org.springframework.web.method.HandlerMethod; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * 登录拦截器 - * - * @author jiangzeyin - * @date 2017/2/4. - */ -@InterceptorPattens(sort = -1, exclude = ServerOpenApi.API + "**") -public class LoginInterceptor extends BaseJpomInterceptor { - /** - * session - */ - public static final String SESSION_NAME = "user"; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { - HttpSession session = getSession(); - boolean init = DbConfig.getInstance().isInit(); - if (!init) { - ServletUtil.write(response, JsonMessage.getString(100, "数据库还没有初始化成功,请耐心等待"), MediaType.APPLICATION_JSON_VALUE); - return false; - } - // - NotLogin notLogin = handlerMethod.getMethodAnnotation(NotLogin.class); - if (notLogin == null) { - notLogin = handlerMethod.getBeanType().getAnnotation(NotLogin.class); - } - if (notLogin == null) { - // 这里需要判断请求头里是否有 Authorization 属性 - String authorization = request.getHeader(ServerOpenApi.HTTP_HEAD_AUTHORIZATION); - if (StrUtil.isNotEmpty(authorization)) { - // jwt token 检测机制 - int code = this.checkHeaderUser(request, session); - if (code > 0) { - this.responseLogin(request, response, handlerMethod, code); - return false; - } - } - // 老版本登录拦截 - if (!this.tryGetHeaderUser(request, session)) { - this.responseLogin(request, response, handlerMethod, ServerConfigBean.AUTHORIZE_TIME_OUT_CODE); - return false; - } - } - reload(); - // - return true; - } - - /** - * 尝试获取 header 中的信息 - * - * @param session ses - * @param request req - * @return true 获取成功 - */ - private int checkHeaderUser(HttpServletRequest request, HttpSession session) { - String token = request.getHeader(ServerOpenApi.HTTP_HEAD_AUTHORIZATION); - if (StrUtil.isEmpty(token)) { - return ServerConfigBean.AUTHORIZE_TIME_OUT_CODE; - } - JWT jwt = JwtUtil.readBody(token); - if (JwtUtil.expired(jwt, 0)) { - int renewal = ServerExtConfigBean.getInstance().getAuthorizeRenewal(); - if (jwt == null || renewal <= 0 || JwtUtil.expired(jwt, TimeUnit.MINUTES.toSeconds(renewal))) { - return ServerConfigBean.AUTHORIZE_TIME_OUT_CODE; - } - return ServerConfigBean.RENEWAL_AUTHORIZE_CODE; - } - UserModel user = (UserModel) session.getAttribute(SESSION_NAME); - UserService userService = SpringUtil.getBean(UserService.class); - String id = JwtUtil.getId(jwt); - UserModel newUser = userService.checkUser(id); - if (newUser == null) { - return ServerConfigBean.AUTHORIZE_TIME_OUT_CODE; - } - if (null != user) { - String tokenUserId = JwtUtil.readUserId(jwt); - boolean b = user.getId().equals(tokenUserId); - if (!b) { - return ServerConfigBean.AUTHORIZE_TIME_OUT_CODE; - } - } - session.setAttribute(LoginInterceptor.SESSION_NAME, newUser); - return 0; - } - - - /** - * 尝试获取 header 中的信息 - * - * @param session ses - * @param request req - * @return true 获取成功 - */ - private boolean tryGetHeaderUser(HttpServletRequest request, HttpSession session) { - String header = request.getHeader(ServerOpenApi.USER_TOKEN_HEAD); - if (StrUtil.isEmpty(header)) { - // 兼容就版本 登录状态 - UserModel user = (UserModel) session.getAttribute(SESSION_NAME); - return user != null; - } - UserService userService = SpringUtil.getBean(UserService.class); - UserModel userModel = userService.checkUser(header); - if (userModel == null) { - return false; - } - session.setAttribute(LoginInterceptor.SESSION_NAME, userModel); - return true; - } - - /** - * 提示登录 - * - * @param request req - * @param response res - * @param handlerMethod 方法 - * @throws IOException 异常 - */ - private void responseLogin(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, int code) throws IOException { -// if (isPage(handlerMethod)) { -// String url = "/login.html?"; -// String uri = request.getRequestURI(); -// if (StrUtil.isNotEmpty(uri) && !StrUtil.SLASH.equals(uri)) { -// String queryString = request.getQueryString(); -// if (queryString != null) { -// uri += "?" + queryString; -// } -// // 补全 -// String newUri = BaseJpomInterceptor.getHeaderProxyPath(request) + uri; -// newUri = UrlRedirectUtil.getRedirect(request, newUri); -// url += "&url=" + URLUtil.encodeAll(newUri); -// } -// String header = request.getHeader(HttpHeaders.REFERER); -// if (header != null) { -// url += "&r=" + header; -// } -// sendRedirects(request, response, url); -// return; -// } - ServletUtil.write(response, JsonMessage.getString(code, "登录信息已失效,重新登录"), MediaType.APPLICATION_JSON_VALUE); - } - - -// @Override -// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { -// super.postHandle(request, response, handler, modelAndView); -// HttpSession session; -// try { -// session = getSession(); -// } catch (Exception ignored) { -// return; -// } -// try { -// // 静态资源地址参数 -// session.setAttribute("staticCacheTime", DateUtil.currentSeconds()); -// // 代理二级路径 -// Object jpomProxyPath = session.getAttribute("jpomProxyPath"); -// if (jpomProxyPath == null) { -// String path = getHeaderProxyPath(request); -// session.setAttribute("jpomProxyPath", path); -// } -// } catch (Exception ignored) { -// } -// try { -// // 统一的js 注入 -// String jsCommonContext = (String) session.getAttribute("jsCommonContext"); -// if (jsCommonContext == null) { -// String path = ExtConfigBean.getInstance().getPath(); -// File file = FileUtil.file(String.format("%s/script/common.js", path)); -// if (file.exists()) { -// jsCommonContext = FileUtil.readString(file, CharsetUtil.CHARSET_UTF_8); -// jsCommonContext = URLEncoder.DEFAULT.encode(jsCommonContext, CharsetUtil.CHARSET_UTF_8); -// } -// session.setAttribute("jsCommonContext", jsCommonContext); -// } -// } catch (IllegalStateException ignored) { -// } -// } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { - super.afterCompletion(request, response, handler, ex); - BaseServerController.remove(); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/NotLogin.java b/modules/server/src/main/java/io/jpom/common/interceptor/NotLogin.java deleted file mode 100644 index 21889b07e8..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/NotLogin.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import java.lang.annotation.*; - -/** - * 游客可以访问的Controller 标记 - * - * @author jiangzeyin - * @date 2017/5/9. - */ -@Documented -@Target({ElementType.METHOD, ElementType.TYPE}) -@Inherited -@Retention(RetentionPolicy.RUNTIME) -public @interface NotLogin { - -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/OpenApiInterceptor.java b/modules/server/src/main/java/io/jpom/common/interceptor/OpenApiInterceptor.java deleted file mode 100644 index af4806a7be..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/OpenApiInterceptor.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.interceptor.BaseInterceptor; -import cn.jiangzeyin.common.interceptor.InterceptorPattens; -import io.jpom.common.ServerOpenApi; -import io.jpom.system.ServerExtConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.method.HandlerMethod; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author bwcx_jzy - * @date 2019/9/4 - */ -@InterceptorPattens(value = "/api/**") -public class OpenApiInterceptor extends BaseInterceptor { - - @Override - protected boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { - - NotLogin methodAnnotation = handlerMethod.getMethodAnnotation(NotLogin.class); - if (methodAnnotation == null) { - if (handlerMethod.getBeanType().isAnnotationPresent(NotLogin.class)) { - return true; - } - } else { - return true; - } - return checkOpenApi(request, response); - } - - private boolean checkOpenApi(HttpServletRequest request, HttpServletResponse response) { - String header = request.getHeader(ServerOpenApi.HEAD); - if (StrUtil.isEmpty(header)) { - ServletUtil.write(response, JsonMessage.getString(300, "token empty"), MediaType.APPLICATION_JSON_VALUE); - return false; - } - String authorizeToken = ServerExtConfigBean.getInstance().getAuthorizeToken(); - if (StrUtil.isEmpty(authorizeToken)) { - ServletUtil.write(response, JsonMessage.getString(300, "not config token"), MediaType.APPLICATION_JSON_VALUE); - return false; - } - String md5 = SecureUtil.md5(authorizeToken); - md5 = SecureUtil.sha1(md5 + ServerOpenApi.HEAD); - if (!StrUtil.equals(header, md5)) { - ServletUtil.write(response, JsonMessage.getString(300, "not config token"), MediaType.APPLICATION_JSON_VALUE); - return false; - } - return true; - } -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java b/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java deleted file mode 100644 index 007cc28e60..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; - -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.interceptor.InterceptorPattens; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.NodeService; -import io.jpom.system.AgentException; -import org.springframework.http.MediaType; -import org.springframework.web.method.HandlerMethod; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 权限拦截器 - * - * @author jiangzeyin - * @date 2019/03/16. - */ -@InterceptorPattens(sort = 1) -public class PermissionInterceptor extends BaseJpomInterceptor { - - private NodeService nodeService; - private static final MethodFeature[] DEMO = new MethodFeature[]{ - MethodFeature.DEL, - MethodFeature.UPLOAD, - MethodFeature.REMOTE_DOWNLOAD, - MethodFeature.EXECUTE}; - - - private void init() { - if (nodeService == null) { - nodeService = SpringUtil.getBean(NodeService.class); - } - } - - private SystemPermission getSystemPermission(HandlerMethod handlerMethod) { - SystemPermission systemPermission = handlerMethod.getMethodAnnotation(SystemPermission.class); - if (systemPermission == null) { - systemPermission = handlerMethod.getBeanType().getAnnotation(SystemPermission.class); - } - return systemPermission; - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { - this.init(); - this.addNode(request); - UserModel userModel = BaseServerController.getUserModel(); - if (userModel == null || userModel.isSuperSystemUser()) { - // 没有登录、或者超级管理自己放过 - return true; - } - boolean systemPermission = this.checkSystemPermission(userModel, response, handlerMethod); - if (!systemPermission) { - return false; - } - Feature feature = handlerMethod.getMethodAnnotation(Feature.class); - if (feature == null) { - return true; - } - MethodFeature method = feature.method(); - if (ArrayUtil.contains(DEMO, method) && userModel.isDemoUser()) { - this.errorMsg(response, "演示系统不能使用该功能,如果完整体验请部署后使用"); - return false; - } - return true; - } - - private boolean checkSystemPermission(UserModel userModel, HttpServletResponse response, HandlerMethod handlerMethod) { - - SystemPermission systemPermission = this.getSystemPermission(handlerMethod); - if (systemPermission == null) { - return true; - } - if (!userModel.isSystemUser()) { - this.errorMsg(response, "你没有权限:-2"); - return false; - } - if (systemPermission.superUser() && !userModel.isSuperSystemUser()) { - this.errorMsg(response, "你没有权限:-2"); - return false; - } - return true; - } - - private void addNode(HttpServletRequest request) { - String nodeId = request.getParameter("nodeId"); - if (!StrUtil.isBlankOrUndefined(nodeId)) { - // 节点信息 - NodeModel nodeModel = nodeService.getByKey(nodeId); - if (nodeModel != null && !nodeModel.isOpenStatus()) { - throw new AgentException(nodeModel.getName() + "节点未启用"); - } - request.setAttribute("node", nodeModel); - } - } - - private void errorMsg(HttpServletResponse response, String msg) { - JsonMessage jsonMessage = new JsonMessage<>(302, msg); - ServletUtil.write(response, jsonMessage.toString(), MediaType.APPLICATION_JSON_VALUE); - } -} diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/package-info.java b/modules/server/src/main/java/io/jpom/common/interceptor/package-info.java deleted file mode 100644 index 6696514c9a..0000000000 --- a/modules/server/src/main/java/io/jpom/common/interceptor/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common.interceptor; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/common/package-info.java b/modules/server/src/main/java/io/jpom/common/package-info.java deleted file mode 100644 index 3e1eb2b191..0000000000 --- a/modules/server/src/main/java/io/jpom/common/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.common; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/controller/IndexControl.java b/modules/server/src/main/java/io/jpom/controller/IndexControl.java deleted file mode 100644 index b093651421..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/IndexControl.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; - -import cn.hutool.core.io.FileTypeUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ReUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.http.ContentType; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.UrlRedirectUtil; -import io.jpom.common.interceptor.BaseJpomInterceptor; -import io.jpom.common.interceptor.NotLogin; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.user.UserService; -import io.jpom.system.ExtConfigBean; -import io.jpom.system.ServerExtConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.InputStream; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * 首页 - * - * @author Administrator - */ -@RestController -@RequestMapping(value = "/") -public class IndexControl extends BaseServerController { - - private final UserService userService; - - public IndexControl(UserService userService) { - this.userService = userService; - } - - /** - * 加载首页 - */ - @GetMapping(value = {"index", "", "/"}, produces = MediaType.TEXT_HTML_VALUE) - @NotLogin - public void index(HttpServletResponse response) { - InputStream inputStream = ResourceUtil.getStream("classpath:/dist/index.html"); - String html = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); - //

- String path = ExtConfigBean.getInstance().getPath(); - File file = FileUtil.file(String.format("%s/script/common.js", path)); - String jsCommonContext = StrUtil.EMPTY; - if (file.exists()) { - jsCommonContext = FileUtil.readString(file, CharsetUtil.CHARSET_UTF_8); - } - html = StrUtil.replace(html, "
", jsCommonContext); - // - String proxyPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), BaseJpomInterceptor.PROXY_PATH); - html = StrUtil.replace(html, "", proxyPath); - // - int webApiTimeout = ServerExtConfigBean.getInstance().getWebApiTimeout(); - html = StrUtil.replace(html, "", TimeUnit.SECONDS.toMillis(webApiTimeout) + ""); - // 修改网页标题 - String title = ReUtil.get(".*?", html, 0); - if (StrUtil.isNotEmpty(title)) { - html = StrUtil.replace(html, title, "" + ServerExtConfigBean.getInstance().getName() + ""); - } - ServletUtil.write(response, html, ContentType.TEXT_HTML.getValue()); - } - - /** - * logo 图片 - */ - @RequestMapping(value = "logo_image", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE) - @NotLogin - public void logoImage(HttpServletResponse response) { - ServerExtConfigBean instance = ServerExtConfigBean.getInstance(); - String logoFile = instance.getLogoFile(); - if (StrUtil.isNotEmpty(logoFile)) { - File file = FileUtil.file(logoFile); - if (FileUtil.isFile(file)) { - String type = FileTypeUtil.getType(file); - if (StrUtil.equalsAnyIgnoreCase(type, "jpg", "png", "gif")) { - ServletUtil.write(response, file); - return; - } - } - } - // 默认logo - InputStream inputStream = ResourceUtil.getStream("classpath:/logo/jpom.png"); - ServletUtil.write(response, inputStream, MediaType.IMAGE_PNG_VALUE); - } - - - /** - * @return json - * @author Hotstrip - * 检查是否需要初始化系统 - * check if need to init system - */ - @NotLogin - @RequestMapping(value = "check-system", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String checkSystem() { - JSONObject data = new JSONObject(); - data.put("routerBase", UrlRedirectUtil.getHeaderProxyPath(getRequest(), BaseJpomInterceptor.PROXY_PATH)); - // - ServerExtConfigBean instance = ServerExtConfigBean.getInstance(); - data.put("name", instance.getName()); - data.put("subTitle", instance.getSubTitle()); - data.put("loginTitle", instance.getLoginTitle()); - if (userService.canUse()) { - return JsonMessage.getString(200, "success", data); - } - return JsonMessage.getString(500, "需要初始化系统", data); - } - - @RequestMapping(value = "menus_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String menusData() { - NodeModel nodeModel = tryGetNode(); - UserModel userModel = getUserModel(); - // 菜单 - InputStream inputStream; - if (nodeModel == null) { - inputStream = ResourceUtil.getStream("classpath:/menus/index.json"); - } else { - inputStream = ResourceUtil.getStream("classpath:/menus/node-index.json"); - } - - 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)) { - return false; - } - JSONArray childs = jsonObject.getJSONArray("childs"); - if (childs != null) { - List collect = childs.stream().filter(o1 -> { - JSONObject jsonObject1 = (JSONObject) o1; - return testMenus(jsonObject1, userModel); - }).collect(Collectors.toList()); - if (collect.isEmpty()) { - return false; - } - jsonObject.put("childs", collect); - } - return true; - }).collect(Collectors.toList()); - return JsonMessage.getString(200, "", collect1); - } - - private boolean testMenus(JSONObject jsonObject, UserModel userModel) { - String role = jsonObject.getString("role"); - if (StrUtil.equals(role, UserModel.SYSTEM_ADMIN) && !userModel.isSuperSystemUser()) { - // 超级理员权限 - return false; - } - // 系统管理员权限 - return !StrUtil.equals(role, "system") || userModel.isSystemUser(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/InstallController.java b/modules/server/src/main/java/io/jpom/controller/InstallController.java deleted file mode 100644 index c5cfa0c18a..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/InstallController.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; - -import cn.hutool.crypto.SecureUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.JpomApplication; -import io.jpom.common.BaseServerController; -import io.jpom.common.interceptor.LoginInterceptor; -import io.jpom.common.interceptor.NotLogin; -import io.jpom.model.data.UserModel; -import io.jpom.model.dto.UserLoginDto; -import io.jpom.service.user.UserService; -import io.jpom.util.JwtUtil; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * 初始化程序 - * - * @author jiangzeyin - * @date 2019/2/22 - */ -@Controller -public class InstallController extends BaseServerController { - - - private final UserService userService; - - public InstallController(UserService userService) { - this.userService = userService; - } - - /** - * 初始化提交 - * - * @param userName 系统管理员登录名 - * @param userPwd 系统管理员的登录密码 - * @return json - */ - @RequestMapping(value = "install_submit.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @NotLogin - @ResponseBody - public String installSubmit( - @ValidatorConfig(value = { - @ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "登录名不能为空"), - @ValidatorItem(value = ValidatorRule.NOT_BLANK, range = "3:20", msg = "登录名长度范围3-20"), - @ValidatorItem(value = ValidatorRule.WORD, msg = "登录名不能包含汉字并且不能包含特殊字符") - }) String userName, - @ValidatorConfig(value = { - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "密码不能为空") - }) String userPwd) { - // - Assert.state(!userService.canUse(), "系统已经初始化过啦,请勿重复初始化"); - - if (JpomApplication.SYSTEM_ID.equalsIgnoreCase(userName) || UserModel.SYSTEM_ADMIN.equals(userName)) { - return JsonMessage.getString(400, "当前登录名已经被系统占用啦"); - } - // 创建用户 - UserModel userModel = new UserModel(); - userModel.setName(UserModel.SYSTEM_OCCUPY_NAME); - userModel.setId(userName); - userModel.setSalt(userService.generateSalt()); - userModel.setPassword(SecureUtil.sha1(userPwd + userModel.getSalt())); - userModel.setSystemUser(1); - userModel.setParent(UserModel.SYSTEM_ADMIN); - try { - userService.insert(userModel); - } catch (Exception e) { - DefaultSystemLog.getLog().error("初始化用户失败", e); - return JsonMessage.getString(400, "初始化失败"); - } - // 自动登录 - setSessionAttribute(LoginInterceptor.SESSION_NAME, userModel); - String jwtId = userService.getUserJwtId(userName); - UserLoginDto userLoginDto = new UserLoginDto(JwtUtil.builder(userModel, jwtId), jwtId); - return JsonMessage.getString(200, "初始化成功", userLoginDto); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/LoginControl.java b/modules/server/src/main/java/io/jpom/controller/LoginControl.java deleted file mode 100644 index 624c0eb079..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/LoginControl.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; - -import cn.hutool.cache.impl.LFUCache; -import cn.hutool.captcha.CircleCaptcha; -import cn.hutool.core.date.BetweenFormatter; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.jwt.JWT; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.common.BaseServerController; -import io.jpom.common.ServerOpenApi; -import io.jpom.common.interceptor.LoginInterceptor; -import io.jpom.common.interceptor.NotLogin; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.model.dto.UserLoginDto; -import io.jpom.service.user.UserBindWorkspaceService; -import io.jpom.service.user.UserService; -import io.jpom.system.ServerConfigBean; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.util.JwtUtil; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.servlet.http.HttpServletResponse; -import java.awt.*; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * 登录控制 - * - * @author Administrator - */ -@Controller -public class LoginControl extends BaseServerController { - /** - * ip 黑名单 - */ - public static final LFUCache LFU_CACHE = new LFUCache<>(1000); - - private static final String LOGIN_CODE = "login_code"; - - private static final String SHOW_CODE = "show_code"; - -// public static final int INPUT_CODE = 600; -// private static final int INPUT_CODE_ERROR_COUNT = 3; - - private final UserService userService; - private final UserBindWorkspaceService userBindWorkspaceService; - - public LoginControl(UserService userService, - UserBindWorkspaceService userBindWorkspaceService) { - this.userService = userService; - this.userBindWorkspaceService = userBindWorkspaceService; - } - - /** - * 验证码 - * - * @throws IOException IO - */ - @RequestMapping(value = "randCode.png", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE) - @ResponseBody - @NotLogin - public void randCode() throws IOException { - int height = 50; - CircleCaptcha circleCaptcha = new CircleCaptcha(100, height, 4, 8); - // 设置为默认字体 - circleCaptcha.setFont(new Font(null, Font.PLAIN, (int) (height * 0.75))); - circleCaptcha.createCode(); - HttpServletResponse response = getResponse(); - circleCaptcha.write(response.getOutputStream()); - String code = circleCaptcha.getCode(); - setSessionAttribute(LOGIN_CODE, code); - // 会话显示验证码 - setSessionAttribute(SHOW_CODE, true); - } - - private Integer ipError() { - if (ServerExtConfigBean.getInstance().getIpErrorLockTime() <= 0) { - return 0; - } - String ip = getIp(); - int count = ObjectUtil.defaultIfNull(LFU_CACHE.get(ip), 0) + 1; - LFU_CACHE.put(ip, count, ServerExtConfigBean.getInstance().getIpErrorLockTime()); - return count; - } - - private void ipSuccess() { - String ip = getIp(); - LFU_CACHE.remove(ip); - } - - /** - * 当登录的ip 错误次数达到配置的10倍以上锁定当前ip - * - * @return true - */ - private boolean ipLock() { - if (ServerExtConfigBean.getInstance().userAlwaysLoginError <= 0) { - return false; - } - String ip = getIp(); - Integer count = LFU_CACHE.get(ip); - if (count == null) { - count = 0; - } - // 大于10倍时 封ip - return count > ServerExtConfigBean.getInstance().userAlwaysLoginError * 10; - } - - /** - * 登录接口 - * - * @param userName 登录名 - * @param userPwd 登录密码 - * @param code 验证码 - * @return json - */ - @RequestMapping(value = "userLogin", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @NotLogin - public String userLogin( - @ValidatorConfig(value = { - @ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "请输入登录信息") - }) String userName, - @ValidatorConfig(value = { - @ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "请输入登录信息") - }) String userPwd, - String code) { - if (this.ipLock()) { - return JsonMessage.getString(400, "尝试次数太多,请稍后再来"); - } - synchronized (UserModel.class) { - UserModel userModel = userService.getByKey(userName); - if (userModel == null) { - this.ipError(); - return JsonMessage.getString(400, "登录失败,请输入正确的密码和账号,多次失败将锁定账号"); - } - // 获取验证码 - String sCode = getSessionAttribute(LOGIN_CODE); - if (StrUtil.isEmpty(code) || !sCode.equalsIgnoreCase(code)) { - return JsonMessage.getString(400, "请输入正确的验证码"); - } - removeSessionAttribute(LOGIN_CODE); - - try { - long lockTime = userModel.overLockTime(); - if (lockTime > 0) { - String msg = DateUtil.formatBetween(lockTime * 1000, BetweenFormatter.Level.MINUTE); - userModel.errorLock(); - this.ipError(); - return JsonMessage.getString(400, "该账户登录失败次数过多,已被锁定" + msg + ",请不要再次尝试"); - } - // 验证 - if (userService.simpleLogin(userName, userPwd) != null) { - userModel.unLock(); - // 判断工作空间 - List bindWorkspaceModels = userBindWorkspaceService.listUserWorkspaceInfo(userModel); - Assert.notEmpty(bindWorkspaceModels, "当前账号没有绑定任何工作空间,请联系管理员处理"); - setSessionAttribute(LoginInterceptor.SESSION_NAME, userModel); - removeSessionAttribute(SHOW_CODE); - this.ipSuccess(); - String jwtId = userService.getUserJwtId(userName); - UserLoginDto userLoginDto = new UserLoginDto(JwtUtil.builder(userModel, jwtId), jwtId); - return JsonMessage.getString(200, "登录成功", userLoginDto); - } else { - userModel.errorLock(); - this.ipError(); - return JsonMessage.getString(501, "登录失败,请输入正确的密码和账号,多次失败将锁定账号"); - } - } finally { - userService.update(userModel); - } - } - } - - /** - * 退出登录 - * - * @return json - */ - @RequestMapping(value = "logout2", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @NotLogin - public String logout() { - getSession().invalidate(); - return JsonMessage.getString(200, "退出成功"); - } - - /** - * 刷新token - */ - @RequestMapping(value = "renewal", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @NotLogin - public String renewalToken() { - String token = getRequest().getHeader(ServerOpenApi.HTTP_HEAD_AUTHORIZATION); - if (StrUtil.isEmpty(token)) { - return JsonMessage.getString(ServerConfigBean.AUTHORIZE_TIME_OUT_CODE, "刷新token失败"); - } - JWT jwt = JwtUtil.readBody(token); - if (JwtUtil.expired(jwt, 0)) { - int renewal = ServerExtConfigBean.getInstance().getAuthorizeRenewal(); - if (jwt == null || renewal <= 0 || JwtUtil.expired(jwt, TimeUnit.MINUTES.toSeconds(renewal))) { - return JsonMessage.getString(ServerConfigBean.AUTHORIZE_TIME_OUT_CODE, "刷新token超时"); - } - } - UserModel userModel = userService.checkUser(JwtUtil.getId(jwt)); - if (userModel == null) { - return JsonMessage.getString(ServerConfigBean.AUTHORIZE_TIME_OUT_CODE, "没有对应的用户"); - } - String jwtId = userService.getUserJwtId(userModel.getId()); - UserLoginDto userLoginDto = new UserLoginDto(JwtUtil.builder(userModel, jwtId), jwtId); - return JsonMessage.getString(200, "", userLoginDto); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/MyErrorController.java b/modules/server/src/main/java/io/jpom/controller/MyErrorController.java deleted file mode 100644 index 9477c8a3c2..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/MyErrorController.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; - -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController; -import org.springframework.boot.web.servlet.error.ErrorAttributes; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.multipart.MaxUploadSizeExceededException; - -import javax.servlet.RequestDispatcher; -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; - -/** - * @author bwcx_jzy - * @date 2021/3/17 - * @see org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController - */ -@Controller -@RequestMapping("${server.error.path:${error.path:/error}}") -public class MyErrorController extends AbstractErrorController { - - public MyErrorController(ErrorAttributes errorAttributes) { - super(errorAttributes); - } - - @RequestMapping - public ResponseEntity> error(HttpServletRequest request) { - HttpStatus status = getStatus(request); - if (status == HttpStatus.NO_CONTENT) { - return new ResponseEntity<>(status); - } - Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); - String requestUri = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); - DefaultSystemLog.getLog().error("发生异常:" + statusCode + " " + requestUri); - // 判断异常信息 - Object attribute = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); - Map body = new HashMap<>(5); - body.put(JsonMessage.CODE, HttpStatus.INTERNAL_SERVER_ERROR.value()); - String msg = "啊哦,好像哪里出错了,请稍候再试试吧~"; - if (attribute instanceof MaxUploadSizeExceededException) { - // 上传文件大小异常 - msg = "上传文件太大了,请重新选择一个较小的文件上传吧"; - } else if (status == HttpStatus.NOT_FOUND) { - msg = "没有找到对应的资源"; - body.put(JsonMessage.DATA, requestUri); - } - body.put(JsonMessage.MSG, msg); - - return new ResponseEntity<>(body, HttpStatus.OK); - } - - @Override - public String getErrorPath() { - return null; - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoController.java b/modules/server/src/main/java/io/jpom/controller/build/BuildInfoController.java deleted file mode 100644 index 5547a2b7e9..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoController.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.build; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.RegexPool; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.jpom.build.BuildUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.AfterOpt; -import io.jpom.model.BaseEnum; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.data.SshModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.DbBuildHistoryLogService; -import io.jpom.service.dblog.RepositoryService; -import io.jpom.service.node.ssh.SshService; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.util.CommandUtil; -import io.jpom.util.GitUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.*; - -import java.io.File; -import java.util.List; -import java.util.Objects; - -/** - * 构建列表,新版本,数据存放到数据库,不再是文件了 - * 以前的数据会在程序启动时插入到数据库中 - * - * @author Hotstrip - * @date 2021-08-09 - */ -@RestController -@Feature(cls = ClassFeature.BUILD) -public class BuildInfoController extends BaseServerController { - - - private final DbBuildHistoryLogService dbBuildHistoryLogService; - private final SshService sshService; - private final BuildInfoService buildInfoService; - private final RepositoryService repositoryService; - - public BuildInfoController(DbBuildHistoryLogService dbBuildHistoryLogService, - SshService sshService, - BuildInfoService buildInfoService, - RepositoryService repositoryService) { - this.dbBuildHistoryLogService = dbBuildHistoryLogService; - this.sshService = sshService; - this.buildInfoService = buildInfoService; - this.repositoryService = repositoryService; - } - - /** - * load build list with params - * - * @return json - */ - @RequestMapping(value = "/build/list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getBuildList() { - // load list with page - PageResultDto list = buildInfoService.listPage(getRequest()); - return JsonMessage.getString(200, "", list); - } - - /** - * load build list with params - * - * @return json - */ - @GetMapping(value = "/build/list_all", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getBuildListAll() { - // load list with page - List modelList = buildInfoService.listByWorkspace(getRequest()); - return JsonMessage.getString(200, "", modelList); - } - - /** - * edit build info - * - * @param id - * @param name - * @param repositoryId - * @param resultDirFile - * @param script - * @param releaseMethod - * @param branchName - * @param webhook - * @param extraData - * @return json - */ - @RequestMapping(value = "/build/edit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String updateMonitor(String id, - @ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "构建名称不能为空")) String name, - @ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "仓库信息不能为空")) String repositoryId, - @ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "构建产物目录不能为空,长度1-200", range = "1:200")) String resultDirFile, - @ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "构建命令不能为空")) String script, - @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "发布方法不正确") int releaseMethod, - String branchName, String branchTagName, String webhook, - String extraData) { - // 根据 repositoryId 查询仓库信息 - RepositoryModel repositoryModel = repositoryService.getByKey(repositoryId, getRequest()); - Assert.notNull(repositoryModel, "无效的仓库信息"); - // 如果是 GIT 需要检测分支是否存在 - if (RepositoryModel.RepoType.Git.getCode() == repositoryModel.getRepoType()) { - Assert.hasText(branchName, "请选择分支"); - } else if (RepositoryModel.RepoType.Svn.getCode() == repositoryModel.getRepoType()) { - // 如果是 SVN - branchName = "trunk"; - } - if (ServerExtConfigBean.getInstance().getBuildCheckDeleteCommand()) { - // 判断删除命令 - Assert.state(!CommandUtil.checkContainsDel(script), "不能包含删除命令"); - } - // 查询构建信息 - BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, getRequest()); - if (null == buildInfoModel) { - buildInfoModel = new BuildInfoModel(); - } - // 设置参数 - if (StrUtil.isNotEmpty(webhook)) { - Validator.validateMatchRegex(RegexPool.URL_HTTP, webhook, "WebHooks 地址不合法"); - } - buildInfoModel.setWebhook(webhook); - buildInfoModel.setRepositoryId(repositoryId); - buildInfoModel.setName(name); - buildInfoModel.setBranchName(branchName); - buildInfoModel.setBranchTagName(branchTagName); - buildInfoModel.setResultDirFile(resultDirFile); - buildInfoModel.setScript(script); - // 设置修改人 - buildInfoModel.setModifyUser(UserModel.getOptUserName(getUser())); - // 发布方式 - BuildReleaseMethod releaseMethod1 = BaseEnum.getEnum(BuildReleaseMethod.class, releaseMethod); - Assert.notNull(releaseMethod1, "发布方法不正确"); - buildInfoModel.setReleaseMethod(releaseMethod1.getCode()); - // 把 extraData 信息转换成 JSON 字符串 - JSONObject jsonObject = JSON.parseObject(extraData); - - // 验证发布方式 和 extraData 信息 - if (releaseMethod1 == BuildReleaseMethod.Project) { - this.formatProject(jsonObject); - } else if (releaseMethod1 == BuildReleaseMethod.Ssh) { - this.formatSsh(jsonObject); - } else if (releaseMethod1 == BuildReleaseMethod.Outgiving) { - String releaseMethodDataId = jsonObject.getString("releaseMethodDataId_1"); - Assert.hasText(releaseMethodDataId, "请选择分发项目"); - jsonObject.put("releaseMethodDataId", releaseMethodDataId); - } else if (releaseMethod1 == BuildReleaseMethod.LocalCommand) { - this.formatLocalCommand(jsonObject); - jsonObject.put("releaseMethodDataId", "LocalCommand"); - } - // 检查关联数据ID - buildInfoModel.setReleaseMethodDataId(jsonObject.getString("releaseMethodDataId")); - if (buildInfoModel.getReleaseMethod() != BuildReleaseMethod.No.getCode()) { - Assert.hasText(buildInfoModel.getReleaseMethodDataId(), "没有发布分发对应关联数据ID"); - } - buildInfoModel.setExtraData(jsonObject.toJSONString()); - - // 新增构建信息 - if (StrUtil.isEmpty(id)) { - // set default buildId - buildInfoModel.setBuildId(0); - buildInfoService.insert(buildInfoModel); - return JsonMessage.getString(200, "添加成功"); - } - - buildInfoService.update(buildInfoModel); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 验证构建信息 - * 当发布方式为【SSH】的时候 - * - * @param jsonObject 配置信息 - */ - private void formatSsh(JSONObject jsonObject) { - // 发布方式 - String releaseMethodDataId = jsonObject.getString("releaseMethodDataId_3"); - Assert.hasText(releaseMethodDataId, "请选择分发SSH项"); - - String releasePath = jsonObject.getString("releasePath"); - Assert.hasText(releasePath, "请输入发布到ssh中的目录"); - - releasePath = FileUtil.normalize(releasePath); - SshModel sshServiceItem = sshService.getByKey(releaseMethodDataId, getRequest()); - Assert.notNull(sshServiceItem, "没有对应的ssh项"); - jsonObject.put("releaseMethodDataId", releaseMethodDataId); - // - if (releasePath.startsWith(StrUtil.SLASH)) { - // 以根路径开始 - List fileDirs = sshServiceItem.fileDirs(); - Assert.notEmpty(fileDirs, "此ssh未授权操作此目录"); - - boolean find = false; - for (String fileDir : fileDirs) { - if (FileUtil.isSub(new File(fileDir), new File(releasePath))) { - find = true; - } - } - Assert.state(find, "此ssh未授权操作此目录"); - } - // 发布命令 - String releaseCommand = jsonObject.getString("releaseCommand"); - if (StrUtil.isNotEmpty(releaseCommand)) { - int length = releaseCommand.length(); - Assert.state(length <= 4000, "发布命令长度限制在4000字符"); - //return JsonMessage.getString(405, "请输入发布命令"); - String[] commands = StrUtil.splitToArray(releaseCommand, StrUtil.LF); - - for (String commandItem : commands) { - boolean checkInputItem = SshModel.checkInputItem(sshServiceItem, commandItem); - Assert.state(checkInputItem, "发布命令中包含禁止执行的命令"); - } - } - } - - private void formatLocalCommand(JSONObject jsonObject) { - // 发布命令 - String releaseCommand = jsonObject.getString("releaseCommand"); - if (StrUtil.isNotEmpty(releaseCommand)) { - int length = releaseCommand.length(); - Assert.state(length <= 4000, "发布命令长度限制在4000字符"); - } - } - - /** - * 验证构建信息 - * 当发布方式为【项目】的时候 - * - * @param jsonObject 配置信息 - */ - private void formatProject(JSONObject jsonObject) { - String releaseMethodDataId2Node = jsonObject.getString("releaseMethodDataId_2_node"); - String releaseMethodDataId2Project = jsonObject.getString("releaseMethodDataId_2_project"); - - Assert.state(!StrUtil.hasEmpty(releaseMethodDataId2Node, releaseMethodDataId2Project), "请选择节点和项目"); - jsonObject.put("releaseMethodDataId", String.format("%s:%s", releaseMethodDataId2Node, releaseMethodDataId2Project)); - // - String afterOpt = jsonObject.getString("afterOpt"); - AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); - Assert.notNull(afterOpt1, "请选择打包后的操作"); - // - String clearOld = jsonObject.getString("clearOld"); - jsonObject.put("afterOpt", afterOpt1.getCode()); - jsonObject.put("clearOld", Convert.toBool(clearOld, false)); - } - - /** - * 获取分支信息 - * - * @param repositoryId 仓库id - * @return json - * @throws Exception 异常 - */ - @RequestMapping(value = "/build/branch-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String branchList( - @ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "仓库ID不能为空")) String repositoryId) throws Exception { - // 根据 repositoryId 查询仓库信息 - RepositoryModel repositoryModel = repositoryService.getByKey(repositoryId, false); - Assert.notNull(repositoryModel, "无效的仓库信息"); - // - Assert.state(repositoryModel.getRepoType() == 0, "只有 GIT 仓库才有分支信息"); - Tuple branchAndTagList = GitUtil.getBranchAndTagList(repositoryModel); - Assert.notNull(branchAndTagList, "没有任何分支"); - Object[] members = branchAndTagList.getMembers(); - return JsonMessage.getString(200, "ok", members); - } - - - /** - * 删除构建信息 - * - * @param id 构建ID - * @return json - */ - @PostMapping(value = "/build/delete", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据id") String id) { - // 查询构建信息 - BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, getRequest()); - Objects.requireNonNull(buildInfoModel, "没有对应数据"); - // - String e = buildInfoService.checkStatus(buildInfoModel.getStatus()); - if (e != null) { - return e; - } - dbBuildHistoryLogService.delByBuildId(buildInfoModel.getId()); - - // 删除构建信息文件 - File file = BuildUtil.getBuildDataFile(buildInfoModel.getId()); - if (!FileUtil.del(file)) { - FileUtil.del(file.toPath()); - return JsonMessage.getString(500, "清理历史构建产物失败,已经重新尝试"); - } - - // 删除构建信息数据 - buildInfoService.delByKey(buildInfoModel.getId()); - return JsonMessage.getString(200, "清理历史构建产物成功"); - } - - - /** - * 清除构建信息 - * - * @param id 构建ID - * @return json - */ - @PostMapping(value = "/build/clean-source", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String cleanSource(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据id") String id) { - // 查询构建信息 - BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, getRequest()); - Objects.requireNonNull(buildInfoModel, "没有对应数据"); - - File source = BuildUtil.getSourceById(buildInfoModel.getId()); - boolean del = FileUtil.del(source); - if (!del) { - del = FileUtil.del(source.toPath()); - } - return JsonMessage.getString(200, "清理成功", del); - } - -} diff --git a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoHistoryController.java b/modules/server/src/main/java/io/jpom/controller/build/BuildInfoHistoryController.java deleted file mode 100644 index 530f1bc61d..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoHistoryController.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.build; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.build.BuildUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.log.BuildHistoryLog; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.DbBuildHistoryLogService; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.io.IOException; -import java.util.Objects; - -/** - * new version for build info history controller - * - * @author Hotstrip - * @since 2021-08-26 - */ -@RestController -@Feature(cls = ClassFeature.BUILD_LOG) -public class BuildInfoHistoryController extends BaseServerController { - - private final BuildInfoService buildInfoService; - private final DbBuildHistoryLogService dbBuildHistoryLogService; - - public BuildInfoHistoryController(BuildInfoService buildInfoService, - DbBuildHistoryLogService dbBuildHistoryLogService) { - this.buildInfoService = buildInfoService; - this.dbBuildHistoryLogService = dbBuildHistoryLogService; - } - - /** - * 下载构建物 - * - * @param logId 日志id - */ - @RequestMapping(value = "/build/history/download_file.html", method = RequestMethod.GET) - @Feature(method = MethodFeature.DOWNLOAD) - public void downloadFile(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String logId) { - BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId); - if (buildHistoryLog == null) { - return; - } - BuildInfoModel item = buildInfoService.getByKey(buildHistoryLog.getBuildDataId()); - if (item == null) { - return; - } - File logFile = BuildUtil.getHistoryPackageFile(item.getId(), buildHistoryLog.getBuildNumberId(), buildHistoryLog.getResultDirFile()); - if (!FileUtil.exist(logFile)) { - return; - } - if (logFile.isFile()) { - ServletUtil.write(getResponse(), logFile); - } else { - File zipFile = BuildUtil.isDirPackage(logFile); - assert zipFile != null; - ServletUtil.write(getResponse(), zipFile); - } - } - - - @RequestMapping(value = "/build/history/download_log.html", method = RequestMethod.GET) - @ResponseBody - @Feature(method = MethodFeature.DOWNLOAD) - public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String logId) throws IOException { - BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId); - Objects.requireNonNull(buildHistoryLog); - BuildInfoModel item = buildInfoService.getByKey(buildHistoryLog.getBuildDataId()); - Objects.requireNonNull(item); - File logFile = BuildUtil.getLogFile(item.getId(), buildHistoryLog.getBuildNumberId()); - if (!FileUtil.exist(logFile)) { - return; - } - if (logFile.isFile()) { - ServletUtil.write(getResponse(), logFile); - } - } - - @RequestMapping(value = "/build/history/history_list.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String historyList() { - PageResultDto pageResultTemp = dbBuildHistoryLogService.listPage(getRequest()); - return JsonMessage.getString(200, "获取成功", pageResultTemp); - } - - /** - * 构建 - * - * @param logId id - * @return json - */ - @RequestMapping(value = "/build/history/delete_log.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String delete(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String logId) { - BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, getRequest()); - Objects.requireNonNull(buildHistoryLog); - JsonMessage jsonMessage = dbBuildHistoryLogService.deleteLogAndFile(buildHistoryLog); - return jsonMessage.toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoManageController.java b/modules/server/src/main/java/io/jpom/controller/build/BuildInfoManageController.java deleted file mode 100644 index 6e81ce76d4..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoManageController.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.build; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONObject; -import io.jpom.build.BuildInfoManage; -import io.jpom.build.BuildUtil; -import io.jpom.build.ReleaseManage; -import io.jpom.common.BaseServerController; -import io.jpom.model.BaseEnum; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.enums.BuildStatus; -import io.jpom.model.log.BuildHistoryLog; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.DbBuildHistoryLogService; -import io.jpom.util.LimitQueue; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.util.Objects; - -/** - * new build info manage controller - * ` * - * - * @author Hotstrip - * @since 2021-08-23 - */ -@RestController -@Feature(cls = ClassFeature.BUILD) -public class BuildInfoManageController extends BaseServerController { - - - private final BuildInfoService buildInfoService; - private final DbBuildHistoryLogService dbBuildHistoryLogService; - - public BuildInfoManageController(BuildInfoService buildInfoService, - DbBuildHistoryLogService dbBuildHistoryLogService) { - this.buildInfoService = buildInfoService; - this.dbBuildHistoryLogService = dbBuildHistoryLogService; - } - - /** - * 开始构建 - * - * @param id id - * @return json - */ - @RequestMapping(value = "/build/manage/start", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String start(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String id) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); - Assert.notNull(item, "没有对应数据"); - String e = buildInfoService.checkStatus(item.getStatus()); - Assert.isNull(e, () -> e); - // set buildId field - int buildId = ObjectUtil.defaultIfNull(item.getBuildId(), 0); - item.setBuildId(buildId + 1); - // userModel - UserModel userModel = getUser(); - String optUserName = userModel == null ? "openApi" : UserModel.getOptUserName(userModel); - item.setModifyUser(optUserName); - buildInfoService.update(item); - // 执行构建 - return buildInfoService.start(item, userModel, null); - } - - /** - * 取消构建 - * - * @param id id - * @return json - */ - @RequestMapping(value = "/build/manage/cancel", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String cancel(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String id) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); - Objects.requireNonNull(item, "没有对应数据"); - BuildStatus nowStatus = BaseEnum.getEnum(BuildStatus.class, item.getStatus()); - Objects.requireNonNull(nowStatus); - if (BuildStatus.Ing != nowStatus && BuildStatus.PubIng != nowStatus) { - return JsonMessage.getString(501, "当前状态不在进行中"); - } - boolean status = BuildInfoManage.cancel(item.getId()); - if (!status) { - item.setStatus(BuildStatus.Cancel.getCode()); - buildInfoService.update(item); - } - return JsonMessage.getString(200, "取消成功"); - } - - /** - * 重新发布 - * - * @param logId logId - * @return json - */ - @RequestMapping(value = "/build/manage/reRelease", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String reRelease(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String logId) { - BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, getRequest()); - Objects.requireNonNull(buildHistoryLog, "没有对应构建记录."); - BuildInfoModel item = buildInfoService.getByKey(buildHistoryLog.getBuildDataId()); - Objects.requireNonNull(item, "没有对应数据"); - String e = buildInfoService.checkStatus(item.getStatus()); - if (e != null) { - return e; - } - UserModel userModel = getUser(); - ReleaseManage releaseManage = new ReleaseManage(buildHistoryLog, userModel); - // 标记发布中 - releaseManage.updateStatus(BuildStatus.PubIng); - ThreadUtil.execute(releaseManage::start2); - return JsonMessage.getString(200, "重新发布中"); - } - - /** - * 获取构建的日志 - * - * @param id id - * @param buildId 构建编号 - * @param line 需要获取的行号 - * @return json - */ - @RequestMapping(value = "/build/manage/get-now-log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String 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()); - Assert.notNull(item, "没有对应数据"); - Assert.state(buildId <= item.getBuildId(), "还没有对应的构建记录"); - - BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); - buildHistoryLog.setBuildDataId(id); - buildHistoryLog.setBuildNumberId(buildId); - BuildHistoryLog queryByBean = dbBuildHistoryLogService.queryByBean(buildHistoryLog); - Assert.notNull(queryByBean, "没有对应的构建历史"); - - File file = BuildUtil.getLogFile(item.getId(), buildId); - Assert.state(FileUtil.isFile(file), "日志文件错误"); - - if (!file.exists()) { - if (buildId == item.getBuildId()) { - return JsonMessage.getString(201, "还没有日志文件"); - } - return JsonMessage.getString(300, "日志文件不存在"); - } - JSONObject data = new JSONObject(); - // 运行中 - Integer status = queryByBean.getStatus(); - data.put("run", status == BuildStatus.Ing.getCode() || status == BuildStatus.PubIng.getCode()); - // 构建中 - data.put("buildRun", status == BuildStatus.Ing.getCode()); - // 读取文件 - int linesInt = Convert.toInt(line, 1); - LimitQueue lines = new LimitQueue<>(500); - final int[] readCount = {0}; - FileUtil.readLines(file, CharsetUtil.CHARSET_UTF_8, (LineHandler) line1 -> { - readCount[0]++; - if (readCount[0] < linesInt) { - return; - } - lines.add(line1); - }); - // 下次应该获取的行数 - data.put("line", readCount[0] + 1); - data.put("getLine", linesInt); - data.put("dataLines", lines); - return JsonMessage.getString(200, "ok", data); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoTriggerController.java b/modules/server/src/main/java/io/jpom/controller/build/BuildInfoTriggerController.java deleted file mode 100644 index 528a1e4f93..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/build/BuildInfoTriggerController.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.build; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.DigestAlgorithm; -import cn.hutool.crypto.digest.Digester; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.common.ServerOpenApi; -import io.jpom.common.UrlRedirectUtil; -import io.jpom.common.interceptor.BaseJpomInterceptor; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.UserModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -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.HashMap; -import java.util.Map; - -/** - * new trigger controller for build - * - * @author Hotstrip - * @since 2021-08-23 - */ -@RestController -@Feature(cls = ClassFeature.BUILD) -public class BuildInfoTriggerController extends BaseServerController { - - private final BuildInfoService buildInfoService; - - /** - * 填充的长度 - */ - public static final int BUILD_INFO_TRIGGER_TOKEN_FILL_LEN = 3; - public static final int BUILD_INFO_TRIGGER_TOKEN_DIGEST_COUNT_MAX = 500; - - public BuildInfoTriggerController(BuildInfoService buildInfoService) { - this.buildInfoService = buildInfoService; - } - - /** - * get a trigger url - * - * @param id id - * @return json - */ - @RequestMapping(value = "/build/trigger/url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String getTriggerUrl(String id) { - BuildInfoModel item = buildInfoService.getByKey(id); - if (StrUtil.isEmpty(item.getTriggerToken())) { - item.setTriggerToken(this.createTriggerUrl()); - buildInfoService.update(item); - } - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), BaseJpomInterceptor.PROXY_PATH); - String url = ServerOpenApi.BUILD_TRIGGER_BUILD2. - replace("{id}", item.getId()). - replace("{token}", item.getTriggerToken()); - String triggerBuildUrl = String.format("/%s/%s", contextPath, url); - Map map = new HashMap<>(10); - map.put("triggerBuildUrl", FileUtil.normalize(triggerBuildUrl)); - return JsonMessage.getString(200, "ok", map); - } - - - /** - * reset new trigger url - * - * @param id id - * @return json - */ - @RequestMapping(value = "/build/trigger/rest", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String triggerRest(String id) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); - // new trigger url - item.setTriggerToken(this.createTriggerUrl()); - buildInfoService.update(item); - return JsonMessage.getString(200, "ok"); - } - - private String createTriggerUrl() { - UserModel user = getUser(); - int randomInt = RandomUtil.randomInt(1, BUILD_INFO_TRIGGER_TOKEN_DIGEST_COUNT_MAX); - String fill = StrUtil.fillBefore(randomInt + "", '0', BUILD_INFO_TRIGGER_TOKEN_FILL_LEN); - String nowStr = new Digester(DigestAlgorithm.SHA256).setDigestCount(randomInt).digestHex(user.getId()); - return StrUtil.format("{}{}", fill, nowStr); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/build/RepositoryController.java b/modules/server/src/main/java/io/jpom/controller/build/RepositoryController.java deleted file mode 100644 index f395b9a001..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/build/RepositoryController.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.build; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import io.jpom.build.BuildUtil; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.RepositoryService; -import io.jpom.system.JpomRuntimeException; -import io.jpom.util.GitUtil; -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.RestController; - -import java.io.File; -import java.util.List; - -/** - * @author Hotstrip - * Repository controller - */ -@RestController -@Feature(cls = ClassFeature.BUILD_REPOSITORY) -public class RepositoryController extends BaseServerController { - - private final RepositoryService repositoryService; - private final BuildInfoService buildInfoService; - - public RepositoryController(RepositoryService repositoryService, - BuildInfoService buildInfoService) { - this.repositoryService = repositoryService; - this.buildInfoService = buildInfoService; - } - - /** - * load repository list - * - * @return json - */ - @PostMapping(value = "/build/repository/list") - @Feature(method = MethodFeature.LIST) - public Object loadRepositoryList() { - PageResultDto pageResult = repositoryService.listPage(getRequest()); - return JsonMessage.getString(200, "获取成功", pageResult); - } - - /** - * load repository list - * - * @return json - */ - @GetMapping(value = "/build/repository/list_all") - @Feature(method = MethodFeature.LIST) - public Object loadRepositoryListAll() { - List repositoryModels = repositoryService.listByWorkspace(getRequest()); - return JsonMessage.getString(200, "", repositoryModels); - } - - /** - * edit - * - * @param repositoryModelReq 仓库实体 - * @return json - */ - @PostMapping(value = "/build/repository/edit") - @Feature(method = MethodFeature.EDIT) - public Object editRepository(RepositoryModel repositoryModelReq) { - this.checkInfo(repositoryModelReq); - // 检查 rsa 私钥 - boolean andUpdateSshKey = this.checkAndUpdateSshKey(repositoryModelReq); - Assert.state(andUpdateSshKey, "rsa 私钥文件不存在或者有误"); - - if (repositoryModelReq.getRepoType() == RepositoryModel.RepoType.Git.getCode()) { - RepositoryModel repositoryModel = repositoryService.getByKey(repositoryModelReq.getId(), false); - if (repositoryModel != null) { - repositoryModelReq.setRsaPrv(StrUtil.emptyToDefault(repositoryModelReq.getRsaPrv(), repositoryModel.getRsaPrv())); - repositoryModelReq.setPassword(StrUtil.emptyToDefault(repositoryModelReq.getPassword(), repositoryModel.getPassword())); - } - // 验证 git 仓库信息 - try { - Tuple tuple = GitUtil.getBranchAndTagList(repositoryModelReq); - } catch (JpomRuntimeException jpomRuntimeException) { - throw jpomRuntimeException; - } catch (Exception e) { - DefaultSystemLog.getLog().warn("获取仓库分支失败", e); - return JsonMessage.toJson(500, "无法连接此仓库," + e.getMessage()); - } - } - if (StrUtil.isEmpty(repositoryModelReq.getId())) { - // insert data - repositoryService.insert(repositoryModelReq); - } else { - // update data - repositoryModelReq.setWorkspaceId(repositoryService.getCheckUserWorkspace(getRequest())); - repositoryService.updateById(repositoryModelReq); - } - - return JsonMessage.toJson(200, "操作成功"); - } - - /** - * edit - * - * @param id 仓库信息 - * @return json - */ - @PostMapping(value = "/build/repository/rest_hide_field") - @Feature(method = MethodFeature.EDIT) - public Object restHideField(@ValidatorItem String id) { - RepositoryModel repositoryModel = new RepositoryModel(); - repositoryModel.setId(id); - repositoryModel.setPassword(StrUtil.EMPTY); - repositoryModel.setRsaPrv(StrUtil.EMPTY); - repositoryModel.setWorkspaceId(repositoryService.getCheckUserWorkspace(getRequest())); - repositoryService.updateById(repositoryModel); - return JsonMessage.toJson(200, "操作成功"); - } - - /** - * 检查信息 - * - * @param repositoryModelReq 仓库信息 - */ - private void checkInfo(RepositoryModel repositoryModelReq) { - Assert.notNull(repositoryModelReq, "请传人正确的信息"); - Assert.hasText(repositoryModelReq.getName(), "请填写仓库名称"); - Integer repoType = repositoryModelReq.getRepoType(); - Assert.state(repoType != null && (repoType == 1 || repoType == 0), "请选择仓库类型"); - Assert.hasText(repositoryModelReq.getGitUrl(), "请填写仓库地址"); - // - Integer protocol = repositoryModelReq.getProtocol(); - Assert.state(protocol != null && (protocol == GitProtocolEnum.HTTP.getCode() || protocol == GitProtocolEnum.SSH.getCode()), "请选择拉取代码的协议"); - // 修正字段 - if (protocol == GitProtocolEnum.HTTP.getCode()) { - // http - repositoryModelReq.setRsaPub(StrUtil.EMPTY); - } else if (protocol == GitProtocolEnum.SSH.getCode()) { - // ssh - repositoryModelReq.setPassword(StrUtil.emptyToDefault(repositoryModelReq.getPassword(), StrUtil.EMPTY)); - } - // 判断仓库是否重复 - Entity entity = Entity.create(); - if (repositoryModelReq.getId() != null) { - Validator.validateGeneral(repositoryModelReq.getId(), "错误的ID"); - entity.set("id", "<> " + repositoryModelReq.getId()); - } - String workspaceId = repositoryService.getCheckUserWorkspace(getRequest()); - entity.set("workspaceId", workspaceId); - entity.set("gitUrl", repositoryModelReq.getGitUrl()); - Assert.state(!repositoryService.exists(entity), "已经存在对应的仓库信息啦"); - } - - /** - * check and update ssh key - * - * @param repositoryModelReq 仓库 - */ - private boolean checkAndUpdateSshKey(RepositoryModel repositoryModelReq) { - if (repositoryModelReq.getProtocol() == GitProtocolEnum.SSH.getCode()) { - // if rsa key is not empty - if (StrUtil.isNotEmpty(repositoryModelReq.getRsaPrv())) { - /** - * if rsa key is start with "file:" - * copy this file - */ - if (StrUtil.startWith(repositoryModelReq.getRsaPrv(), URLUtil.FILE_URL_PREFIX)) { - String rsaPath = StrUtil.removePrefix(repositoryModelReq.getRsaPrv(), URLUtil.FILE_URL_PREFIX); - if (!FileUtil.exist(rsaPath)) { - DefaultSystemLog.getLog().warn("there is no rsa file... {}", rsaPath); - return false; - } - } else { - //File rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModelReq.getId() + Const.ID_RSA); - // or else put into file - //FileUtil.writeUtf8String(repositoryModelReq.getRsaPrv(), rsaFile); - } - } - } - return true; - } - - /** - * delete - * - * @param id 仓库ID - * @return json - */ - @PostMapping(value = "/build/repository/delete") - @Feature(method = MethodFeature.DEL) - public Object delRepository(String id) { - // 判断仓库是否被关联 - Entity entity = Entity.create(); - entity.set("repositoryId", id); - boolean exists = buildInfoService.exists(entity); - Assert.state(!exists, "当前仓库被构建关联,不能直接删除"); - - repositoryService.delByKey(id, getRequest()); - File rsaFile = BuildUtil.getRepositoryRsaFile(id + Const.ID_RSA); - FileUtil.del(rsaFile); - return JsonMessage.getString(200, "删除成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/monitor/MonitorListController.java b/modules/server/src/main/java/io/jpom/controller/monitor/MonitorListController.java deleted file mode 100644 index b993a4a52f..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/monitor/MonitorListController.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.monitor; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.BaseServerController; -import io.jpom.model.Cycle; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.MonitorModel; -import io.jpom.model.data.UserModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.DbMonitorNotifyLogService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.service.user.UserService; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.sql.SQLException; -import java.util.List; - -/** - * 监控列表 - * - * @author bwcx_jzy - * @date 2019/6/15 - */ -@RestController -@RequestMapping(value = "/monitor") -@Feature(cls = ClassFeature.MONITOR) -public class MonitorListController extends BaseServerController { - - private final MonitorService monitorService; - private final DbMonitorNotifyLogService dbMonitorNotifyLogService; - private final UserService userService; - private final ProjectInfoCacheService projectInfoCacheService; - - public MonitorListController(MonitorService monitorService, - DbMonitorNotifyLogService dbMonitorNotifyLogService, - UserService userService, - ProjectInfoCacheService projectInfoCacheService) { - this.monitorService = monitorService; - this.dbMonitorNotifyLogService = dbMonitorNotifyLogService; - this.userService = userService; - this.projectInfoCacheService = projectInfoCacheService; - } - - /** - * 展示监控列表 - * - * @return json - */ - @RequestMapping(value = "getMonitorList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String getMonitorList() { - PageResultDto pageResultDto = monitorService.listPage(getRequest()); - return JsonMessage.getString(200, "", pageResultDto); - } - - /** - * 删除列表 - * - * @param id id - * @return json - */ - @RequestMapping(value = "deleteMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DEL) - public String deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id) throws SQLException { - // - int delByKey = monitorService.delByKey(id, getRequest()); - if (delByKey > 0) { - // 删除日志 - Entity where = new Entity(); - where.set("monitorId", id); - dbMonitorNotifyLogService.del(where); - } - return JsonMessage.getString(200, "删除成功"); - } - - - /** - * 增加或修改监控 - * - * @param id id - * @param name name - * @param notifyUser user - * @return json - */ - @RequestMapping(value = "updateMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String updateMonitor(String id, - @ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空")) String name, - String notifyUser) { - int cycle = getParameterInt("cycle", Cycle.five.getCode()); - String status = getParameter("status"); - String autoRestart = getParameter("autoRestart"); - - JSONArray jsonArray = JSONArray.parseArray(notifyUser); -// List notifyUsers = jsonArray.toJavaList(String.class); - List notifyUserList = jsonArray.toJavaList(String.class); - Assert.notEmpty(jsonArray, "请选择报警联系人"); - for (Object o : jsonArray) { - String userId = (String) o; - Assert.state(userService.exists(new UserModel(userId)), "没有对应的用户:" + userId); - } - String projects = getParameter("projects"); - JSONArray projectsArray = JSONArray.parseArray(projects); - List nodeProjects = projectsArray.toJavaList(MonitorModel.NodeProject.class); - Assert.notEmpty(nodeProjects, "请至少选择一个节点"); - for (MonitorModel.NodeProject nodeProject : nodeProjects) { - Assert.notEmpty(nodeProject.getProjects(), "请至少选择一个项目"); - for (String project : nodeProject.getProjects()) { - boolean exists = projectInfoCacheService.exists(nodeProject.getNode(), project); - Assert.state(exists, "没有对应的项目:" + project); - } - } - boolean start = "on".equalsIgnoreCase(status); - MonitorModel monitorModel = monitorService.getByKey(id); - if (monitorModel == null) { - monitorModel = new MonitorModel(); - } - monitorModel.setAutoRestart("on".equalsIgnoreCase(autoRestart)); - monitorModel.setCycle(cycle); - monitorModel.projects(nodeProjects); - monitorModel.setStatus(start); - monitorModel.notifyUser(notifyUserList); - monitorModel.setName(name); - - if (StrUtil.isEmpty(id)) { - //添加监控 - monitorService.insert(monitorModel); - return JsonMessage.getString(200, "添加成功"); - } - monitorService.updateById(monitorModel); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 开启或关闭监控 - * - * @param id id - * @param status 状态 - * @param type 类型 - * @return json - */ - @RequestMapping(value = "changeStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String changeStatus(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "id不能为空")) String id, - String status, String type) { - MonitorModel monitorModel = monitorService.getByKey(id); - Assert.notNull(monitorModel, "不存在监控项啦"); - - boolean bStatus = Convert.toBool(status, false); - if ("status".equalsIgnoreCase(type)) { - monitorModel.setStatus(bStatus); - } else if ("restart".equalsIgnoreCase(type)) { - monitorModel.setAutoRestart(bStatus); - } else { - return JsonMessage.getString(405, "type不正确"); - } - monitorService.updateById(monitorModel); - return JsonMessage.getString(200, "修改成功"); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/controller/monitor/MonitorLogController.java b/modules/server/src/main/java/io/jpom/controller/monitor/MonitorLogController.java deleted file mode 100644 index 0c49a14129..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/monitor/MonitorLogController.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.monitor; - -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.log.MonitorNotifyLog; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.DbMonitorNotifyLogService; -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; - -/** - * 监控列表 - * - * @author bwcx_jzy - * @date 2019/7/16 - */ -@RestController -@RequestMapping(value = "/monitor") -@Feature(cls = ClassFeature.MONITOR_LOG) -public class MonitorLogController extends BaseServerController { - - private final DbMonitorNotifyLogService dbMonitorNotifyLogService; - - public MonitorLogController(DbMonitorNotifyLogService dbMonitorNotifyLogService) { - this.dbMonitorNotifyLogService = dbMonitorNotifyLogService; - } - - /** - * 展示用户列表 - * - * @return json - */ - @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String listData() { - PageResultDto pageResult = dbMonitorNotifyLogService.listPage(getRequest()); - return JsonMessage.getString(200, "获取成功", pageResult); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/monitor/MonitorUserOptListController.java b/modules/server/src/main/java/io/jpom/controller/monitor/MonitorUserOptListController.java deleted file mode 100644 index f7c03afcf8..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/monitor/MonitorUserOptListController.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.monitor; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.MonitorUserOptModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.monitor.MonitorUserOptService; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -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.Objects; -import java.util.stream.Collectors; - -/** - * 监控用户操作 - * - * @author bwcx_jzy - * @date 2020/08/06 - */ -@RestController -@RequestMapping(value = "/monitor_user_opt") -@Feature(cls = ClassFeature.OPT_MONITOR) -public class MonitorUserOptListController extends BaseServerController { - - private final MonitorUserOptService monitorUserOptService; - - public MonitorUserOptListController(MonitorUserOptService monitorUserOptService) { - this.monitorUserOptService = monitorUserOptService; - } - - - @RequestMapping(value = "list_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getMonitorList() { - PageResultDto pageResultDto = monitorUserOptService.listPage(getRequest()); - return JsonMessage.getString(200, "", pageResultDto); - } - - /** - * 操作监控类型列表 - * - * @return json - */ - @RequestMapping(value = "type_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getOperateTypeList() { - JSONObject jsonObject = new JSONObject(); - // - List classFeatureList = Arrays.stream(ClassFeature.values()) - .filter(classFeature -> classFeature != ClassFeature.NULL) - .map(classFeature -> { - JSONObject jsonObject1 = new JSONObject(); - jsonObject1.put("title", classFeature.getName()); - jsonObject1.put("value", classFeature.name()); - return jsonObject1; - }) - .collect(Collectors.toList()); - jsonObject.put("classFeature", classFeatureList); - // - List methodFeatureList = Arrays.stream(MethodFeature.values()) - .filter(methodFeature -> methodFeature != MethodFeature.NULL && methodFeature != MethodFeature.LIST) - .map(classFeature -> { - JSONObject jsonObject1 = new JSONObject(); - jsonObject1.put("title", classFeature.getName()); - jsonObject1.put("value", classFeature.name()); - return jsonObject1; - }) - .collect(Collectors.toList()); - jsonObject.put("methodFeature", methodFeatureList); - - return JsonMessage.getString(200, "success", jsonObject); - } - - /** - * 删除列表 - * - * @param id id - * @return json - */ - @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id) { - // - monitorUserOptService.delByKey(id, getRequest()); - return JsonMessage.getString(200, "删除成功"); - } - - - /** - * 增加或修改监控 - * - * @param id id - * @param name name - * @param notifyUser user - * @return json - */ - @RequestMapping(value = "update", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String updateMonitor(String id, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空") String name, - String notifyUser, - String monitorUser, - String monitorOpt, - String monitorFeature) { - - String status = getParameter("status"); - - JSONArray jsonArray = JSONArray.parseArray(notifyUser); - List notifyUsers = jsonArray.toJavaList(String.class) - .stream() - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - Assert.notEmpty(notifyUsers, "请选择报警联系人"); - - - JSONArray monitorUserArray = JSONArray.parseArray(monitorUser); - List monitorUserArrays = monitorUserArray.toJavaList(String.class) - .stream() - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - Assert.notEmpty(monitorUserArrays, "请选择监控人员"); - - - JSONArray monitorOptArray = JSONArray.parseArray(monitorOpt); - List monitorOptArrays = monitorOptArray - .stream() - .map(o -> EnumUtil.fromString(MethodFeature.class, StrUtil.toString(o), null)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - Assert.notEmpty(monitorOptArrays, "请选择监控的操作"); - - JSONArray monitorFeatureArray = JSONArray.parseArray(monitorFeature); - List monitorFeatureArrays = monitorFeatureArray - .stream() - .map(o -> EnumUtil.fromString(ClassFeature.class, StrUtil.toString(o), null)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - Assert.notEmpty(monitorFeatureArrays, "请选择监控的功能"); - - - boolean start = "on".equalsIgnoreCase(status); - MonitorUserOptModel monitorModel = monitorUserOptService.getByKey(id); - if (monitorModel == null) { - monitorModel = new MonitorUserOptModel(); - } - monitorModel.monitorUser(monitorUserArrays); - monitorModel.setStatus(start); - monitorModel.monitorOpt(monitorOptArrays); - monitorModel.monitorFeature(monitorFeatureArrays); - monitorModel.notifyUser(notifyUsers); - monitorModel.setName(name); - - if (StrUtil.isEmpty(id)) { - //添加监控 - monitorUserOptService.insert(monitorModel); - return JsonMessage.getString(200, "添加成功"); - } - monitorUserOptService.update(monitorModel); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 开启或关闭监控 - * - * @param id id - * @param status 状态 - * @return json - */ - @RequestMapping(value = "changeStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String changeStatus(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "id不能为空") String id, - String status) { - MonitorUserOptModel monitorModel = monitorUserOptService.getByKey(id); - Assert.notNull(monitorModel, "不存在监控项啦"); - - boolean bStatus = Convert.toBool(status, false); - monitorModel.setStatus(bStatus); - monitorUserOptService.update(monitorModel); - return JsonMessage.getString(200, "修改成功"); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/NodeEditController.java b/modules/server/src/main/java/io/jpom/controller/node/NodeEditController.java deleted file mode 100644 index 78905ed734..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/NodeEditController.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.jpom.controller.node; - -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.ProjectInfoModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.node.OutGivingServer; -import io.jpom.service.node.ProjectInfoCacheService; -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; - -/** - * 节点管理 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -@RequestMapping(value = "/node") -@Feature(cls = ClassFeature.NODE) -public class NodeEditController extends BaseServerController { - - private final OutGivingServer outGivingServer; - private final MonitorService monitorService; - private final BuildInfoService buildService; - private final ProjectInfoCacheService projectInfoCacheService; - - public NodeEditController(OutGivingServer outGivingServer, - MonitorService monitorService, - BuildInfoService buildService, - ProjectInfoCacheService projectInfoCacheService) { - this.outGivingServer = outGivingServer; - this.monitorService = monitorService; - this.buildService = buildService; - this.projectInfoCacheService = projectInfoCacheService; - } - - - @PostMapping(value = "list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String listJson() { - PageResultDto nodeModelPageResultDto = nodeService.listPage(getRequest()); - return JsonMessage.getString(200, "", nodeModelPageResultDto); - } - - @GetMapping(value = "list_data_all.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String listDataAll() { - List list = nodeService.listByWorkspace(getRequest()); - return JsonMessage.getString(200, "", list); - } - - @RequestMapping(value = "node_status", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String nodeStatus() { - long timeMillis = System.currentTimeMillis(); - NodeModel node = getNode(); - JSONObject jsonObject = NodeForward.requestData(node, NodeUrl.Status, getRequest(), JSONObject.class); - Assert.notNull(jsonObject, "获取信息失败"); - JSONArray jsonArray = new JSONArray(); - jsonObject.put("timeOut", System.currentTimeMillis() - timeMillis); - jsonObject.put("nodeId", node.getId()); - jsonArray.add(jsonObject); - return JsonMessage.getString(200, "", jsonArray); - } - - @PostMapping(value = "save.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String save() { - nodeService.update(getRequest()); - return JsonMessage.getString(200, "操作成功"); - } - - - /** - * 删除节点 - * - * @param id 节点id - * @return json - */ - @PostMapping(value = "del.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String del(String id) { - // 判断分发 - HttpServletRequest request = getRequest(); - boolean checkNode = outGivingServer.checkNode(id, request); - Assert.state(!checkNode, "该节点存在分发项目,不能删除"); - // 监控 - boolean checkNode1 = monitorService.checkNode(id); - Assert.state(!checkNode1, "该节点存在监控项,不能删除"); - boolean checkNode2 = buildService.checkNode(id); - Assert.state(!checkNode2, "该节点存在构建项,不能删除"); - // - ProjectInfoModel projectInfoModel = new ProjectInfoModel(); - projectInfoModel.setNodeId(id); - projectInfoModel.setWorkspaceId(projectInfoCacheService.getCheckUserWorkspace(request)); - boolean exists = projectInfoCacheService.exists(projectInfoModel); - Assert.state(!exists, "该节点下还存在项目,不能删除"); - nodeService.delByKey(id, request); - return JsonMessage.getString(200, "操作成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/NodeProjectInfoController.java b/modules/server/src/main/java/io/jpom/controller/node/NodeProjectInfoController.java deleted file mode 100644 index b2ab6e60fe..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/NodeProjectInfoController.java +++ /dev/null @@ -1,122 +0,0 @@ -package io.jpom.controller.node; - -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.ProjectInfoModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.ProjectInfoCacheService; -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 java.util.List; - -/** - * 节点管理 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@RestController -@RequestMapping(value = "/node") -@Feature(cls = ClassFeature.NODE) -public class NodeProjectInfoController extends BaseServerController { - - private final ProjectInfoCacheService projectInfoCacheService; - - public NodeProjectInfoController(ProjectInfoCacheService projectInfoCacheService) { - this.projectInfoCacheService = projectInfoCacheService; - } - - /** - * @return json - * @author Hotstrip - * load node project list - * 加载节点项目列表 - */ - @PostMapping(value = "node_project_list", produces = MediaType.APPLICATION_JSON_VALUE) - public String nodeProjectList() { - PageResultDto resultDto = projectInfoCacheService.listPageNode(getRequest()); - return JsonMessage.getString(200, "success", resultDto); - } - - - /** - * load node project list - * 加载节点项目列表 - * - * @return json - * @author Hotstrip - */ - @PostMapping(value = "project_list", produces = MediaType.APPLICATION_JSON_VALUE) - public String projectList() { - PageResultDto resultDto = projectInfoCacheService.listPage(getRequest()); - return JsonMessage.getString(200, "success", resultDto); - } - - /** - * load node project list - * 加载节点项目列表 - * - * @return json - * @author Hotstrip - */ - @GetMapping(value = "project_list_all", produces = MediaType.APPLICATION_JSON_VALUE) - public String projectListAll() { - List projectInfoModels = projectInfoCacheService.listByWorkspace(getRequest()); - return JsonMessage.getString(200, "", projectInfoModels); - } - - /** - * 同步节点项目 - * - * @return json - */ - @GetMapping(value = "sync_project", produces = MediaType.APPLICATION_JSON_VALUE) - public String syncProject(String nodeId) { - NodeModel nodeModel = nodeService.getByKey(nodeId); - Assert.notNull(nodeModel, "对应的节点不存在"); - String msg = projectInfoCacheService.syncExecuteNode(nodeModel); - return JsonMessage.getString(200, msg); - } - - /** - * 删除节点缓存的项目信息 - * - * @return json - */ - @GetMapping(value = "del_project_cache", produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission() - public String delProjectCache(String nodeId) { - NodeModel nodeModel = nodeService.getByKey(nodeId); - Assert.notNull(nodeModel, "对应的节点不存在"); - int count = projectInfoCacheService.delProjectCache(nodeId, getRequest()); - return JsonMessage.getString(200, "成功删除" + count + "条项目缓存"); - } - - /** - * 删除节点缓存的所有项目 - * - * @return json - */ - @GetMapping(value = "clear_all_project", produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission(superUser = true) - @Feature(method = MethodFeature.DEL) - public String clearAll() { - Entity where = Entity.create(); - where.set("id", " <> id"); - int del = projectInfoCacheService.del(where); - return JsonMessage.getString(200, "成功删除" + del + "条项目缓存"); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/NodeUpdateController.java b/modules/server/src/main/java/io/jpom/controller/node/NodeUpdateController.java deleted file mode 100644 index 4868e0f110..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/NodeUpdateController.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.jpom.controller.node; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.JpomManifest; -import io.jpom.common.RemoteVersion; -import io.jpom.common.Type; -import io.jpom.model.AgentFileModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -/** - * @author bwcx_jzy - * @since 2021/11/29 - */ -@RestController -@RequestMapping(value = "/node") -@SystemPermission(superUser = true) -@Feature(cls = ClassFeature.UPGRADE_NODE_LIST) -public class NodeUpdateController extends BaseServerController { - - private final SystemParametersServer systemParametersServer; - - public NodeUpdateController(SystemParametersServer systemParametersServer) { - this.systemParametersServer = systemParametersServer; - } - - /** - * 远程下载 - * - * @return json - * @see RemoteVersion - */ - @GetMapping(value = "download_remote.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.REMOTE_DOWNLOAD) - public String downloadRemote() throws IOException { - Tuple download = RemoteVersion.download(ConfigBean.getInstance().getTempPath().getAbsolutePath(), Type.Agent); - // 保存文件 - this.saveAgentFile(download); - return JsonMessage.getString(200, "下载成功"); - } - - /** - * 检查版本更新 - * - * @return json - * @see RemoteVersion - * @see AgentFileModel - */ - @GetMapping(value = "check_version.json", produces = MediaType.APPLICATION_JSON_VALUE) - public String checkVersion() { - RemoteVersion remoteVersion = RemoteVersion.cacheInfo(); - AgentFileModel agentFileModel = systemParametersServer.getConfig(AgentFileModel.ID, AgentFileModel.class); - JSONObject jsonObject = new JSONObject(); - if (remoteVersion == null) { - jsonObject.put("upgrade", false); - } else { - String tagName = StrUtil.removePrefixIgnoreCase(remoteVersion.getTagName(), "v"); - if (agentFileModel == null) { - jsonObject.put("upgrade", true); - } else { - String version = StrUtil.removePrefixIgnoreCase(agentFileModel.getVersion(), "v"); - jsonObject.put("upgrade", StrUtil.compareVersion(version, tagName) < 0); - } - } - return JsonMessage.getString(200, "", jsonObject); - } - - @RequestMapping(value = "upload_agent", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission - @Feature(method = MethodFeature.UPLOAD) - public String uploadAgent() throws IOException { - String saveDir = ServerConfigBean.getInstance().getAgentPath().getAbsolutePath(); - MultipartFileBuilder multipartFileBuilder = createMultipart(); - multipartFileBuilder - .setFileExt("jar", "zip") - .addFieldName("file") - .setUseOriginalFilename(true) - .setSavePath(saveDir); - String path = multipartFileBuilder.save(); - // 解析压缩包 - File file = JpomManifest.zipFileFind(path, Type.Agent, saveDir); - path = FileUtil.getAbsolutePath(file); - // 基础检查 - JsonMessage error = JpomManifest.checkJpomJar(path, Type.Agent, false); - if (error.getCode() != HttpStatus.HTTP_OK) { - FileUtil.del(path); - return error.toString(); - } - // 保存文件 - this.saveAgentFile(error.getData()); - return JsonMessage.getString(200, "上传成功"); - } - - private void saveAgentFile(Tuple data) { - File file = data.get(3); - AgentFileModel agentFileModel = new AgentFileModel(); - agentFileModel.setName(file.getName()); - agentFileModel.setSize(file.length()); - agentFileModel.setSavePath(FileUtil.getAbsolutePath(file)); - // - agentFileModel.setVersion(data.get(0)); - agentFileModel.setTimeStamp(data.get(1)); - systemParametersServer.upsert(AgentFileModel.ID, agentFileModel, AgentFileModel.ID); - // 删除历史包 @author jzy 2021-08-03 - String saveDir = ServerConfigBean.getInstance().getAgentPath().getAbsolutePath(); - List files = FileUtil.loopFiles(saveDir, pathname -> !FileUtil.equals(pathname, file)); - for (File file1 : files) { - FileUtil.del(file1); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/NodeWelcomeController.java b/modules/server/src/main/java/io/jpom/controller/node/NodeWelcomeController.java deleted file mode 100644 index 32b8d382c5..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/NodeWelcomeController.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.jpom.controller.node; - -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.db.Page; -import cn.hutool.db.sql.Direction; -import cn.hutool.db.sql.Order; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.BaseEnum; -import io.jpom.model.Cycle; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.NodeModel; -import io.jpom.model.log.SystemMonitorLog; -import io.jpom.permission.SystemPermission; -import io.jpom.service.dblog.DbSystemMonitorLogService; -import io.jpom.util.StringUtil; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletResponse; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * 欢迎页 - * - * @author Administrator - */ -@RestController -@RequestMapping(value = "/node") -public class NodeWelcomeController extends BaseServerController { - - private final DbSystemMonitorLogService dbSystemMonitorLogService; - - public NodeWelcomeController(DbSystemMonitorLogService dbSystemMonitorLogService) { - this.dbSystemMonitorLogService = dbSystemMonitorLogService; - } - - private Cycle getCycle() { - NodeModel node = getNode(); - return BaseEnum.getEnum(Cycle.class, node.getCycle()); - } - - private long getCycleMillis() { - Cycle cycle = getCycle(); - long millis = cycle == null ? TimeUnit.SECONDS.toMillis(30) : cycle.getMillis(); - if (millis <= 0) { - millis = TimeUnit.SECONDS.toMillis(30); - } - return millis; - } - - @RequestMapping(value = "nodeMonitor_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String nodeMonitorJson(String time) { - JSONObject object = getData(time); - return JsonMessage.getString(200, "ok", object); - } - - private PageResultDto getList(String time, long millis) { - long endTime = System.currentTimeMillis(); - long startTime = endTime - TimeUnit.MINUTES.toMillis(30); - if (StrUtil.isNotEmpty(time)) { - // 处理时间 - List list = StrSplitter.splitTrim(time, "~", true); - DateTime startDate = DateUtil.parseDateTime(list.get(0)); - startTime = startDate.getTime(); - DateTime endDate = DateUtil.parseDateTime(list.get(1)); - if (startDate.equals(endDate) || StrUtil.equalsAny("00:00:00", endDate.toString(DatePattern.NORM_TIME_FORMAT), startDate.toString(DatePattern.NORM_TIME_FORMAT))) { - endDate = DateUtil.endOfDay(endDate); - } - endTime = endDate.getTime(); - } - int count = (int) ((endTime - startTime) / millis); - NodeModel node = getNode(); - // 开启了节点信息采集 - Page pageObj = new Page(1, count); - pageObj.addOrder(new Order("monitorTime", Direction.DESC)); - Entity entity = Entity.create(); - entity.set("nodeId", node.getId()); - - entity.set(" MONITORTIME", ">= " + startTime); - entity.set("MONITORTIME", "<= " + endTime); - return dbSystemMonitorLogService.listPage(entity, pageObj); - } - - private JSONObject getData(String selTime) { - long millis = getCycleMillis(); - PageResultDto pageResult = getList(selTime, millis); - List series = new ArrayList<>(); - List scale = new ArrayList<>(); - for (int i = pageResult.getTotal() - 1; i >= 0; i--) { - SystemMonitorLog systemMonitorLog = pageResult.get(i); - if (StrUtil.isEmpty(selTime)) { - scale.add(DateUtil.formatTime(new Date(systemMonitorLog.getMonitorTime()))); - } else { - scale.add(new DateTime(systemMonitorLog.getMonitorTime()).toString(DatePattern.NORM_DATETIME_PATTERN)); - } - JSONObject jsonObject = new JSONObject(); - jsonObject.put("cpu", systemMonitorLog.getOccupyCpu()); - jsonObject.put("memory", systemMonitorLog.getOccupyMemory()); - jsonObject.put("memoryUsed", systemMonitorLog.getOccupyMemoryUsed()); - jsonObject.put("disk", systemMonitorLog.getOccupyDisk()); - series.add(jsonObject); - } - // - int minSize = 12; - while (scale.size() <= minSize) { - if (scale.size() == 0) { - scale.add(DateUtil.formatTime(DateUtil.date())); - } - String time = scale.get(scale.size() - 1); - String newTime = StringUtil.getNextScaleTime(time, millis); - scale.add(newTime); - } - - JSONObject object = new JSONObject(); - object.put("scales", scale); - object.put("series", series); - return object; - } - - @RequestMapping(value = "getTop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getTop() { - Cycle cycle = getCycle(); - NodeModel node = getNode(); - if (cycle == null || cycle == Cycle.none) { - // 未开启、直接查询 - return NodeForward.request(node, getRequest(), NodeUrl.GetTop).toString(); - } - JSONObject object = getData(null); - return JsonMessage.getString(200, "ok", object); - } - - @RequestMapping(value = "exportTop") - public void exportTop(String time) throws UnsupportedEncodingException { - PageResultDto monitorData = getList(time, getCycleMillis()); - if (monitorData.getTotal() <= 0) { - // NodeForward.requestDownload(node, getRequest(), getResponse(), NodeUrl.exportTop); - } else { - NodeModel node = getNode(); - StringBuilder buf = new StringBuilder(); - buf.append("监控时间").append(",占用cpu").append(",占用内存").append(",占用磁盘").append("\r\n"); - List result = monitorData.getResult(); - for (SystemMonitorLog log : result) { - long monitorTime = log.getMonitorTime(); - buf.append(DateUtil.date(monitorTime).toString()).append(",") - .append(log.getOccupyCpu()).append("%").append(",") - .append(log.getOccupyMemory()).append("%").append(",") - .append(log.getOccupyDisk()).append("%").append("\r\n"); - } - String fileName = URLEncoder.encode("Jpom系统监控-" + node.getId(), "UTF-8"); - HttpServletResponse response = getResponse(); - response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8), "GBK") + ".csv"); - response.setContentType("text/csv;charset=utf-8"); - ServletUtil.write(getResponse(), buf.toString(), CharsetUtil.UTF_8); - } - } - - @RequestMapping(value = "processList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProcessList() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.ProcessList).toString(); - } - - @RequestMapping(value = "kill.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission - public String kill() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Kill).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/manage/EditProjectController.java b/modules/server/src/main/java/io/jpom/controller/node/manage/EditProjectController.java deleted file mode 100644 index 1e397490ce..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/manage/EditProjectController.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.jpom.controller.node.manage; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.service.system.WhitelistDirectoryService; -import io.jpom.system.ConfigBean; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.annotation.Resource; -import java.util.List; - -/** - * 项目管理 - * - * @author jiangzeyin - * @date 2018/9/29 - */ -@Controller -@RequestMapping(value = "/node/manage/") -@Feature(cls = ClassFeature.PROJECT) -public class EditProjectController extends BaseServerController { - - private final ProjectInfoCacheService projectInfoCacheService; - @Resource - private WhitelistDirectoryService whitelistDirectoryService; - - public EditProjectController(ProjectInfoCacheService projectInfoCacheService) { - this.projectInfoCacheService = projectInfoCacheService; - } - - @RequestMapping(value = "getProjectData.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getProjectData(@ValidatorItem String id) { - JSONObject projectInfo = projectInfoCacheService.getItem(getNode(), id); - return JsonMessage.getString(200, "", projectInfo); - } - - /** - * @return - * @author Hotstrip - * get project access list - * 获取项目的白名单 - */ - @RequestMapping(value = "project-access-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String projectAccessList() { - List jsonArray = whitelistDirectoryService.getProjectDirectory(getNode()); - return JsonMessage.getString(200, "success", jsonArray); - } - - /** - * 保存项目 - * - * @param id id - * @return json - */ - @RequestMapping(value = "saveProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String saveProject(String id) { - // 防止和Jpom冲突 - if (StrUtil.isNotEmpty(ConfigBean.getInstance().applicationTag) && ConfigBean.getInstance().applicationTag.equalsIgnoreCase(id)) { - return JsonMessage.getString(401, "当前项目id已经被Jpom占用"); - } - NodeModel node = getNode(); - JsonMessage request = NodeForward.request(node, getRequest(), NodeUrl.Manage_SaveProject); - if (request.getCode() == HttpStatus.OK.value()) { - projectInfoCacheService.syncNode(node, id); - } - return request.toString(); - } - - - /** - * 验证lib 暂时用情况 - * - * @return json - */ - @RequestMapping(value = "judge_lib.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String saveProject() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_Jude_Lib).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/manage/JdkManageController.java b/modules/server/src/main/java/io/jpom/controller/node/manage/JdkManageController.java deleted file mode 100644 index 9555719ea1..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/manage/JdkManageController.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.jpom.controller.node.manage; - -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@RequestMapping(value = "/node/manage/") -@Feature(cls = ClassFeature.PROJECT) -public class JdkManageController extends BaseServerController { - - - @RequestMapping(value = "jdk/list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String list() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_jdk_list).toString(); - } - - @RequestMapping(value = "jdk/delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String delete() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_jdk_delete).toString(); - } - - @RequestMapping(value = "jdk/update", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String update() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_jdk_update).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/manage/ProjectManageControl.java b/modules/server/src/main/java/io/jpom/controller/node/manage/ProjectManageControl.java deleted file mode 100644 index 37511cc42a..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/manage/ProjectManageControl.java +++ /dev/null @@ -1,181 +0,0 @@ -package io.jpom.controller.node.manage; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.OutGivingModel; -import io.jpom.model.data.ProjectInfoModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.node.OutGivingServer; -import io.jpom.service.node.ProjectInfoCacheService; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import java.util.List; - -/** - * 项目管理 - * - * @author Administrator - */ -@RestController -@RequestMapping(value = "/node/manage/") -@Feature(cls = ClassFeature.PROJECT) -public class ProjectManageControl extends BaseServerController { - - @Resource - private OutGivingServer outGivingServer; - @Resource - private MonitorService monitorService; - @Resource - private BuildInfoService buildService; - - private final ProjectInfoCacheService projectInfoCacheService; - - public ProjectManageControl(ProjectInfoCacheService projectInfoCacheService) { - this.projectInfoCacheService = projectInfoCacheService; - } - - - /** - * 展示项目页面 - */ - @RequestMapping(value = "project_copy_list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - @ResponseBody - public String projectCopyList() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_ProjectCopyList).toString(); - } - - /** - * 获取正在运行的项目的端口和进程id - * - * @return json - */ - @RequestMapping(value = "getProjectPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getProjectPort() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_GetProjectPort).toString(); - } - - /** - * 获取正在运行的项目的端口和进程id - * - * @return json - */ - @RequestMapping(value = "getProjectCopyPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getProjectCopyPort() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_GetProjectCopyPort).toString(); - } - - - /** - * 查询所有项目 - * - * @return json - */ - @PostMapping(value = "get_project_info", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getProjectInfo() { - PageResultDto modelPageResultDto = projectInfoCacheService.listPage(getRequest()); -// JSONArray jsonArray = projectInfoService.listAll(nodeModel, getRequest()); - return JsonMessage.getString(200, "", modelPageResultDto); - } - - /** - * 删除项目 - * - * @param id id - * @return json - */ - @PostMapping(value = "deleteProject", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String deleteProject(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id, String copyId) { - NodeModel nodeModel = getNode(); - if (StrUtil.isEmpty(copyId)) { - // 检查节点分发 - List outGivingModels = outGivingServer.list(); - if (outGivingModels != null) { - for (OutGivingModel outGivingModel : outGivingModels) { - if (outGivingModel.checkContains(nodeModel.getId(), id)) { - return JsonMessage.getString(405, "当前项目存在节点分发,不能直接删除"); - } - } - } - // - if (monitorService.checkProject(nodeModel.getId(), id)) { - return JsonMessage.getString(405, "当前项目存在监控项,不能直接删除"); - } - boolean releaseMethod = buildService.checkReleaseMethod(nodeModel.getId() + StrUtil.COLON + id, BuildReleaseMethod.Project); - Assert.state(!releaseMethod, "当前项目存在构建项,不能直接删除"); - } - JsonMessage request = NodeForward.request(nodeModel, getRequest(), NodeUrl.Manage_DeleteProject); - if (request.getCode() == HttpStatus.OK.value()) { - // - projectInfoCacheService.syncNode(nodeModel); - } - return request.toString(); - } - - /** - * 重启项目 - *

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

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

- * nodeId,id,copyId - * - * @return json - */ - @RequestMapping(value = "stop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EXECUTE) - public String stop() { - NodeModel nodeModel = getNode(); - return NodeForward.request(nodeModel, getRequest(), NodeUrl.Manage_Stop).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/manage/file/ProjectFileControl.java b/modules/server/src/main/java/io/jpom/controller/node/manage/file/ProjectFileControl.java deleted file mode 100644 index 06435baac7..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/manage/file/ProjectFileControl.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.jpom.controller.node.manage.file; - -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.ProjectInfoCacheService; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -/** - * 文件管理 - * - * @author Administrator - */ -@RestController -@RequestMapping(value = "/node/manage/file/") -@Feature(cls = ClassFeature.PROJECT_FILE) -public class ProjectFileControl extends BaseServerController { - - private final ProjectInfoCacheService projectInfoService; - - public ProjectFileControl(ProjectInfoCacheService projectInfoService) { - this.projectInfoService = projectInfoService; - } - - /** - * 列出目录下的文件 - * - * @return json - */ - @RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - // @ProjectPermission() - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.LIST) - public String getFileList() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_File_GetFileList).toString(); - } - - - /** - * 上传文件 - * - * @return json - */ - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.UPLOAD) - public String upload() { - return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.Manage_File_Upload).toString(); - } - - /** - * 下载文件 - */ - @RequestMapping(value = "download", method = RequestMethod.GET) - @ResponseBody - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.DOWNLOAD) - public void download() { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.Manage_File_Download); - } - - /** - * 删除文件 - * - * @return json - */ - @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.DEL) - public String deleteFile() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_File_DeleteFile).toString(); - } - - - /** - * 更新配置文件 - * - * @return json - */ - @PostMapping(value = "update_config_file", produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) - public String updateConfigFile() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_File_UpdateConfigFile).toString(); - } - - /** - * 删除文件 - * - * @return json - */ - @GetMapping(value = "read_file", produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.LIST) - public String readFile() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_File_ReadFile).toString(); - } - - /** - * 下载远程文件 - * - * @return json - */ - @GetMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.REMOTE_DOWNLOAD) - public String remoteDownload() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_File_Remote_Download).toString(); - - } - - -// /** -// * 获取可编辑文件格式 -// * -// * @return json -// */ -// @RequestMapping(value = "geFileFormat", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.GET_FILE_FOMAT) -// public String geFileFormat() { -// String[] file = fileFormat.split("\\|"); -// JSONObject jsonObject = new JSONObject(); -// jsonObject.put("fileFormat", file); -// return JsonMessage.getString(200, "获取成功", jsonObject); -// } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/manage/log/LogBackController.java b/modules/server/src/main/java/io/jpom/controller/node/manage/log/LogBackController.java deleted file mode 100644 index f78183c744..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/manage/log/LogBackController.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.jpom.controller.node.manage.log; - -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.ProjectInfoCacheService; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * 控制台日志备份管理 - * - * @author jiangzeyin - * @date 2019/3/7 - */ -@Controller -@RequestMapping(value = "node/manage/log") -@Feature(cls = ClassFeature.PROJECT_LOG) -public class LogBackController extends BaseServerController { - - private final ProjectInfoCacheService projectInfoCacheService; - - public LogBackController(ProjectInfoCacheService projectInfoCacheService) { - this.projectInfoCacheService = projectInfoCacheService; - } - - @RequestMapping(value = "export.html", method = RequestMethod.GET) - @ResponseBody - @Feature(method = MethodFeature.DOWNLOAD) - public void export() { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.Manage_Log_export); - } - - /** - * get log back list - * 日志备份列表接口 - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "log-back-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String logBackList() { - JSONObject jsonObject = NodeForward.requestData(getNode(), NodeUrl.Manage_Log_logBack, getRequest(), JSONObject.class); - return JsonMessage.getString(200, "success", jsonObject); - } - - @RequestMapping(value = "logBack_download", method = RequestMethod.GET) - @ResponseBody - @Feature(method = MethodFeature.DOWNLOAD) - public void download() { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.Manage_Log_logBack_download); - } - - @RequestMapping(value = "logBack_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DEL) - public String clear() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_Log_logBack_delete).toString(); - } - - @RequestMapping(value = "logSize", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String logSize(String id, String copyId) { - JSONObject info = projectInfoCacheService.getLogSize(getNode(), id, copyId); - return JsonMessage.getString(200, "", info); - } - - /** - * 重置日志 - * - * @return json - */ - @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(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/manage/recover/ProjectRecoverControl.java b/modules/server/src/main/java/io/jpom/controller/node/manage/recover/ProjectRecoverControl.java deleted file mode 100644 index ac47f7814d..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/manage/recover/ProjectRecoverControl.java +++ /dev/null @@ -1,64 +0,0 @@ -//package io.jpom.controller.node.manage.recover; -// -//import cn.jiangzeyin.common.JsonMessage; -//import io.jpom.common.BaseServerController; -//import io.jpom.common.forward.NodeForward; -//import io.jpom.common.forward.NodeUrl; -//import io.jpom.plugin.ClassFeature; -//import io.jpom.plugin.Feature; -//import io.jpom.plugin.MethodFeature; -//import org.springframework.http.MediaType; -//import org.springframework.stereotype.Controller; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestMethod; -//import org.springframework.web.bind.annotation.ResponseBody; -// -//import java.io.IOException; -//import java.util.List; -// -///** -// * 项目管理 -// * -// * @author Administrator -// */ -//@Controller -//@RequestMapping(value = "/node/manage/recover") -//@Feature(cls = ClassFeature.PROJECT_RECOVER) -//public class ProjectRecoverControl extends BaseServerController { -// -//// /** -//// * 展示项目页面 -//// * -//// * @return page -//// */ -//// @RequestMapping(value = "list.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE) -//// @Feature(method = MethodFeature.LIST) -//// public String projectInfo() { -//// List list = NodeForward.requestData(getNode(), NodeUrl.Manage_Recover_List_Data, getRequest(), List.class); -//// setAttribute("array", list); -//// return "node/manage/project_recover"; -//// } -// -// /** -// * @author Hotstrip -// * get recover list -// * 项目回收列表 -// * @return -// * @throws IOException -// */ -// @RequestMapping(value = "recover-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.LIST) -// public String recoverList() throws IOException { -// List list = NodeForward.requestData(getNode(), NodeUrl.Manage_Recover_List_Data, getRequest(), List.class); -// return JsonMessage.getString(200, "success", list); -// } -// -// @RequestMapping(value = "data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.LIST) -// public String project() throws IOException { -// return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_Recover_Item_Data).toString(); -// } -// -//} diff --git a/modules/server/src/main/java/io/jpom/controller/node/monitor/InternalController.java b/modules/server/src/main/java/io/jpom/controller/node/monitor/InternalController.java deleted file mode 100644 index 758365a1c6..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/monitor/InternalController.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.jpom.controller.node.monitor; - -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * 内存查看 - * - * @author Administrator - */ -@Controller -@RequestMapping(value = "/node/manage/") -public class InternalController extends BaseServerController { - - - /** - * @return - * @author Hotstrip - * get InternalData - * 获取内存信息接口 - */ - @RequestMapping(value = "getInternalData", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getInternalData() { - JSONObject data = NodeForward.requestData(getNode(), NodeUrl.Manage_internal_data, getRequest(), JSONObject.class); - DefaultSystemLog.getLog().info("data: {}", data == null ? "" : data.toString()); - return JsonMessage.getString(200, "success", data); - } - - /** - * 查询监控线程列表 - * - * @return json - */ - @RequestMapping(value = "threadInfos", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String threadInfos() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_internal_threadInfos).toString(); - } - - /** - * 导出堆栈信息 - */ - @RequestMapping(value = "stack", method = RequestMethod.GET) - @ResponseBody - public void stack() { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.Manage_internal_stack); - } - - /** - * 导出内存信息 - */ - @RequestMapping(value = "ram", method = RequestMethod.GET) - @ResponseBody - public void ram() { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.Manage_internal_ram); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/script/ScriptController.java b/modules/server/src/main/java/io/jpom/controller/node/script/ScriptController.java deleted file mode 100644 index 8a481e3db6..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/script/ScriptController.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.jpom.controller.node.script; - -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.script.ScriptServer; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * 脚本管理 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -@Controller -@RequestMapping(value = "/node/script") -@Feature(cls = ClassFeature.SCRIPT) -@SystemPermission -public class ScriptController extends BaseServerController { - - private final ScriptServer scriptServer; - - public ScriptController(ScriptServer scriptServer) { - this.scriptServer = scriptServer; - } - - /** - * @return - * @Hotstrip get script list - */ - @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String scriptList() { - JSONArray jsonArray = scriptServer.listToArray(getNode()); - return JsonMessage.getString(200, "success", jsonArray); - } - - /** - * 保存脚本 - * - * @return json - */ - @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String save() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Script_Save).toString(); - } - - @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DEL) - public String del() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Script_Del).toString(); - } - - /** - * 导入脚本 - * - * @return json - */ - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.UPLOAD) - public String upload() { - return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.Script_Upload).toString(); - } -// -// @RequestMapping(value = "console.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE) -// @Feature(method = MethodFeature.EXECUTE) -// public String console(String id) { -// return "node/script/console"; -// } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshController.java b/modules/server/src/main/java/io/jpom/controller/node/ssh/SshController.java deleted file mode 100644 index 53c69e130f..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshController.java +++ /dev/null @@ -1,237 +0,0 @@ -package io.jpom.controller.node.ssh; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.StrSplitter; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.extra.ssh.JschUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONObject; -import com.jcraft.jsch.Session; -import io.jpom.common.BaseServerController; -import io.jpom.common.Type; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.SshModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.model.log.SshTerminalExecuteLog; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.SshTerminalExecuteLogService; -import io.jpom.service.node.ssh.SshService; -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.nio.charset.Charset; -import java.util.List; - -/** - * @author bwcx_jzy - * @date 2019/8/9 - */ -@RestController -@RequestMapping(value = "node/ssh") -@Feature(cls = ClassFeature.SSH) -public class SshController extends BaseServerController { - - private final SshService sshService; - private final SshTerminalExecuteLogService sshTerminalExecuteLogService; - private final BuildInfoService buildInfoService; - - public SshController(SshService sshService, - SshTerminalExecuteLogService sshTerminalExecuteLogService, - BuildInfoService buildInfoService) { - this.sshService = sshService; - this.sshTerminalExecuteLogService = sshTerminalExecuteLogService; - this.buildInfoService = buildInfoService; - } - - - @PostMapping(value = "list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public JsonMessage> listData() { - PageResultDto pageResultDto = sshService.listPage(getRequest()); - return new JsonMessage<>(200, "", pageResultDto); - } - - @GetMapping(value = "list_data_all.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public JsonMessage> listDataAll() { - List list = sshService.listByWorkspace(getRequest()); - return new JsonMessage<>(200, "", list); - } - - /** - * 编辑 - * - * @param name 名称 - * @param host 端口 - * @param user 用户名 - * @param password 密码 - * @param connectType 连接方式 - * @param privateKey 私钥 - * @param port 端口 - * @param charset 编码格式 - * @param fileDirs 文件夹 - * @param id ID - * @param notAllowedCommand 禁止输入的命令 - * @return json - */ - @PostMapping(value = "save.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String save(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "ssh名称不能为空") String name, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "host不能为空") String host, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "user不能为空") String user, - String password, - SshModel.ConnectType connectType, - String privateKey, - @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "port错误") int port, - String charset, String fileDirs, - String id, String notAllowedCommand) { - SshModel sshModel; - boolean add = StrUtil.isEmpty(getParameter("id")); - if (add) { - // 优先判断参数 如果是 password 在修改时可以不填写 - if (connectType == SshModel.ConnectType.PASS) { - Assert.hasText(password, "请填写登录密码"); - } else if (connectType == SshModel.ConnectType.PUBKEY) { - Assert.hasText(privateKey, "请填写证书内容"); - } - sshModel = new SshModel(); - } else { - sshModel = sshService.getByKey(id); - Assert.notNull(sshModel, "不存在对应ssh"); - } - // 目录 - if (StrUtil.isEmpty(fileDirs)) { - sshModel.fileDirs(null); - } else { - List list = StrSplitter.splitTrim(fileDirs, StrUtil.LF, true); - sshModel.fileDirs(list); - } - sshModel.setHost(host); - // 如果密码传递不为空就设置值 因为上面已经判断了只有修改的情况下 password 才可能为空 - if (StrUtil.isNotEmpty(password)) { - sshModel.setPassword(password); - } - if (StrUtil.isNotEmpty(privateKey)) { - sshModel.setPrivateKey(privateKey); - } - - sshModel.setPort(port); - sshModel.setUser(user); - sshModel.setName(name); - sshModel.setNotAllowedCommand(notAllowedCommand); - sshModel.setConnectType(connectType.name()); - // 获取允许编辑的后缀 - String allowEditSuffix = getParameter("allowEditSuffix"); - List allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "允许编辑的文件后缀不能为空"); - sshModel.allowEditSuffix(allowEditSuffixList); - try { - Charset.forName(charset); - sshModel.setCharset(charset); - } catch (Exception e) { - return JsonMessage.getString(405, "请填写正确的编码格式"); - } - // 判断重复 - HttpServletRequest request = getRequest(); - String workspaceId = sshService.getCheckUserWorkspace(request); - Entity entity = Entity.create(); - entity.set("host", sshModel.getHost()); - entity.set("port", sshModel.getPort()); - entity.set("workspaceId", workspaceId); - if (StrUtil.isNotEmpty(id)) { - entity.set("id", StrUtil.format(" <> {}", id)); - } - boolean exists = sshService.exists(entity); - Assert.state(!exists, "对应的SSH已经存在啦"); - try { - SshModel model = sshService.getByKey(id, false); - if (model != null) { - sshModel.setPassword(StrUtil.emptyToDefault(sshModel.getPassword(), model.getPassword())); - sshModel.setPrivateKey(StrUtil.emptyToDefault(sshModel.getPrivateKey(), model.getPrivateKey())); - } - Session session = SshService.getSessionByModel(sshModel); - JschUtil.close(session); - } catch (Exception e) { - return JsonMessage.getString(505, "ssh连接失败:" + e.getMessage()); - } - if (add) { - sshService.insert(sshModel); - } else { - sshService.update(sshModel); - } - return JsonMessage.getString(200, "操作成功"); - } - - - /** - * 检查 ssh 是否安装插件端 - * - * @param ids ids - * @return json - */ - @GetMapping(value = "check_agent.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String checkAgent(String ids) { - List sshModels = sshService.listById(StrUtil.split(ids, StrUtil.COMMA), getRequest()); - Assert.notEmpty(sshModels, "没有任何节点信息"); - - JSONObject result = new JSONObject(); - for (SshModel sshModel : sshModels) { - List nodeBySshId = nodeService.getNodeBySshId(sshModel.getId()); - JSONObject data = new JSONObject(); - NodeModel nodeModel = CollUtil.getFirst(nodeBySshId); - if (nodeModel == null) { - try { - SshModel model = sshService.getByKey(sshModel.getId(), false); - Integer pid = sshService.checkSshRunPid(model, Type.Agent.getTag()); - data.put("pid", ObjectUtil.defaultIfNull(pid, 0)); - data.put("ok", true); - } catch (Exception e) { - DefaultSystemLog.getLog().error("检查运行状态异常:{}", e.getMessage()); - data.put("error", e.getMessage()); - } - } else { - data.put("nodeId", nodeModel.getId()); - data.put("nodeName", nodeModel.getName()); - } - result.put(sshModel.getId(), data); - } - return JsonMessage.getString(200, "", result); - } - - @PostMapping(value = "del.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String del(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id) { - boolean checkSsh = buildInfoService.checkReleaseMethod(id, BuildReleaseMethod.Ssh); - Assert.state(!checkSsh, "当前ssh存在构建项,不能删除"); - sshService.delByKey(id, getRequest()); - return JsonMessage.getString(200, "操作成功"); - } - - /** - * 执行记录 - * - * @return json - */ - @PostMapping(value = "log_list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(cls = ClassFeature.SSH_TERMINAL_LOG, method = MethodFeature.LIST) - public String logListData() { - PageResultDto pageResult = sshTerminalExecuteLogService.listPage(getRequest()); - return JsonMessage.getString(200, "获取成功", pageResult); - } - -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshFileController.java b/modules/server/src/main/java/io/jpom/controller/node/ssh/SshFileController.java deleted file mode 100644 index 8ca262f76a..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshFileController.java +++ /dev/null @@ -1,444 +0,0 @@ -package io.jpom.controller.node.ssh; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.*; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.extra.ssh.ChannelType; -import cn.hutool.extra.ssh.JschUtil; -import cn.hutool.extra.ssh.Sftp; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.jcraft.jsch.ChannelSftp; -import com.jcraft.jsch.Session; -import com.jcraft.jsch.SftpException; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.model.data.SshModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.ssh.SshService; -import io.jpom.system.ServerConfigBean; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Vector; - -/** - * ssh 文件管理 - * - * @author bwcx_jzy - * @date 2019/8/10 - */ -@RestController -@RequestMapping("node/ssh") -@Feature(cls = ClassFeature.SSH_FILE) -public class SshFileController extends BaseServerController { - - private final SshService sshService; - - public SshFileController(SshService sshService) { - this.sshService = sshService; - } - - @RequestMapping(value = "download.html", method = RequestMethod.GET) - @ResponseBody - @Feature(method = MethodFeature.DOWNLOAD) - public void download(String id, String path, String name) throws IOException { - HttpServletResponse response = getResponse(); - SshModel sshModel = sshService.getByKey(id, false); - if (sshModel == null) { - ServletUtil.write(response, "ssh error", MediaType.TEXT_HTML_VALUE); - return; - } - List fileDirs = sshModel.fileDirs(); - // - if (StrUtil.isEmpty(path) || !fileDirs.contains(path)) { - ServletUtil.write(response, "没有配置此文件夹", MediaType.TEXT_HTML_VALUE); - return; - } - if (StrUtil.isEmpty(name)) { - ServletUtil.write(response, "name error", MediaType.TEXT_HTML_VALUE); - return; - } - try { - this.downloadFile(sshModel, path, name, response); - } catch (SftpException e) { - DefaultSystemLog.getLog().error("下载失败", e); - ServletUtil.write(response, "download error", MediaType.TEXT_HTML_VALUE); - } - } - - /** - * 根据 id 获取 fileDirs 目录集合 - * - * @param id ssh id - * @return json - * @author Hotstrip - * @since for dev 3.x - */ - @RequestMapping(value = "root_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String rootFileList(String id) { - SshModel sshModel = sshService.getByKey(id, false); - Assert.notNull(sshModel, "不存在对应ssh"); - List fileDirs = sshModel.fileDirs(); - Assert.notEmpty(fileDirs, "未设置授权目录"); - JSONArray jsonArray = this.listDir(sshModel, fileDirs); - return JsonMessage.getString(200, "ok", jsonArray); - } - - private SshModel check(String id, String path, String children) { - SshModel sshModel = sshService.getByKey(id, false); - Assert.notNull(sshModel, "不存在对应ssh"); - Assert.hasText(path, "请选择文件夹"); - List fileDirs = sshModel.fileDirs(); - Assert.state(CollUtil.contains(fileDirs, path), "没有配置此文件夹"); - // - if (StrUtil.isNotEmpty(children)) { - // 判断是否合法 - children = FileUtil.normalize(children); - FileUtil.file(path, children); - } - return sshModel; - } - - @RequestMapping(value = "list_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String listData(String id, String path, String children) throws SftpException { - SshModel sshModel = this.check(id, path, children); - // - JSONArray jsonArray = listDir(sshModel, path, children); - return JsonMessage.getString(200, "ok", jsonArray); - } - - @RequestMapping(value = "read_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String readFileData(String id, String path, String children) { - SshModel sshModel = this.check(id, path, children); - // - List allowEditSuffix = sshModel.allowEditSuffix(); - Charset charset = AgentWhitelist.checkFileSuffix(allowEditSuffix, children); - // - String content = this.readFile(sshModel, path, children, charset); - return JsonMessage.getString(200, "ok", content); - } - - @RequestMapping(value = "update_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String updateFileData(String id, String path, String children, String content) { - SshModel sshModel = this.check(id, path, children); - // - List allowEditSuffix = sshModel.allowEditSuffix(); - Charset charset = AgentWhitelist.checkFileSuffix(allowEditSuffix, children); - // 缓存到本地 - File file = FileUtil.file(ServerConfigBean.getInstance().getUserTempPath(), sshModel.getId(), children); - FileUtil.writeString(content, file, charset); - // 上传 - this.syncFile(sshModel, path, children, file); - // - FileUtil.del(file); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 读取文件 - * - * @param sshModel ssh - * @param path 路径 - * @param name 文件 - * @param charset 编码格式 - */ - private String readFile(SshModel sshModel, String path, String name, Charset charset) { - Sftp sftp = null; - try { - Session session = SshService.getSessionByModel(sshModel); - sftp = new Sftp(session, sshModel.getCharsetT()); - String normalize = FileUtil.normalize(path + StrUtil.SLASH + name); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - sftp.download(normalize, byteArrayOutputStream); - byte[] bytes = byteArrayOutputStream.toByteArray(); - return new String(bytes, charset); - } finally { - IoUtil.close(sftp); - } - } - - /** - * 上传文件 - * - * @param sshModel ssh - * @param path 路径 - * @param name 文件 - * @param file 同步上传文件 - */ - private void syncFile(SshModel sshModel, String path, String name, File file) { - Sftp sftp = null; - try { - Session session = SshService.getSessionByModel(sshModel); - sftp = new Sftp(session, sshModel.getCharsetT()); - String normalize = FileUtil.normalize(path + StrUtil.SLASH + name); - sftp.upload(normalize, file); - } finally { - IoUtil.close(sftp); - } - } - - /** - * 下载文件 - * - * @param sshModel ssh - * @param path 路径 - * @param name 文件 - * @param response 响应 - * @throws IOException io - * @throws SftpException sftp - */ - private void downloadFile(SshModel sshModel, String path, String name, HttpServletResponse response) throws IOException, SftpException { - final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8); - String fileName = FileUtil.getName(name); - response.setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, Charset.forName(charset)))); - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - Session session = null; - ChannelSftp channel = null; - try { - session = SshService.getSessionByModel(sshModel); - channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); - String normalize = FileUtil.normalize(path + StrUtil.SLASH + name); - channel.get(normalize, response.getOutputStream()); - } finally { - JschUtil.close(channel); - JschUtil.close(session); - } - } - - /** - * 查询文件夹下所有文件 - * - * @param sshModel ssh - * @param path 路径 - * @param children 文件夹 - * @return array - * @throws SftpException sftp - */ - @SuppressWarnings("unchecked") - private JSONArray listDir(SshModel sshModel, String path, String children) throws SftpException { - Session session = null; - ChannelSftp channel = null; - List allowEditSuffix = sshModel.allowEditSuffix(); - try { - session = SshService.getSessionByModel(sshModel); - channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); - Vector vector; - if (StrUtil.isNotEmpty(children)) { - String allPath = StrUtil.format("{}/{}", path, children); - allPath = FileUtil.normalize(allPath); - vector = channel.ls(allPath); - } else { - vector = channel.ls(path); - } - JSONArray jsonArray = new JSONArray(); - for (ChannelSftp.LsEntry lsEntry : vector) { - String filename = lsEntry.getFilename(); - if (StrUtil.DOT.equals(filename) || StrUtil.DOUBLE_DOT.equals(filename)) { - continue; - } - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", filename); - jsonObject.put("id", IdUtil.fastSimpleUUID()); - int mTime = lsEntry.getAttrs().getMTime(); - String format = DateUtil.format(DateUtil.date(mTime * 1000L), DatePattern.NORM_DATETIME_MINUTE_PATTERN); - jsonObject.put("modifyTime", format); - if (lsEntry.getAttrs().isDir()) { - jsonObject.put("dir", true); - jsonObject.put("title", filename); - } else { - jsonObject.put("title", filename); - long fileSize = lsEntry.getAttrs().getSize(); - jsonObject.put("size", FileUtil.readableFileSize(fileSize)); - // 允许编辑 - jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(allowEditSuffix, filename)); - } - // - if (StrUtil.isEmpty(children)) { - jsonObject.put("parentDir", filename); - } else { - jsonObject.put("parentDir", FileUtil.normalize(StrUtil.format("{}/{}", children, filename))); - } - jsonArray.add(jsonObject); - } - return jsonArray; - } finally { - JschUtil.close(channel); - JschUtil.close(session); - } - } - - /** - * 列出目前,判断是否存在 - * - * @param sshModel 数据信息 - * @param list 目录 - * @return Array - */ - private JSONArray listDir(SshModel sshModel, List list) { - Session session = null; - ChannelSftp channel = null; - try { - session = SshService.getSessionByModel(sshModel); - channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); - JSONArray jsonArray = new JSONArray(); - for (String item : list) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("path", item); - try { - channel.ls(item); - } catch (SftpException e) { - // 标记文件夹不存在 - jsonObject.put("error", true); - } - jsonArray.add(jsonObject); - } - return jsonArray; - } finally { - JschUtil.close(channel); - JschUtil.close(session); - } - } - - - @RequestMapping(value = "delete.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String delete(String id, String path, String name) { - Assert.hasText(name, "name error"); - SshModel sshModel = this.check(id, path, name); - name = FileUtil.normalize(name); - Assert.state(!StrUtil.equals(name, StrUtil.SLASH), "不能删除根目录"); - Session session = null; - Sftp sftp = null; - try { - // 验证合法性,防止越权 - FileUtil.file(path, name); - // - String normalize = FileUtil.normalize(path + StrUtil.SLASH + name); - session = SshService.getSessionByModel(sshModel); - sftp = new Sftp(session, sshModel.getCharsetT()); - // 尝试删除 - boolean dirOrFile = this.tryDelDirOrFile(sftp, normalize); - if (dirOrFile) { - String parent = FileUtil.getParent(name, 1); - return JsonMessage.getString(200, "删除成功", parent); - } - return JsonMessage.getString(200, "删除成功"); - } catch (Exception e) { - DefaultSystemLog.getLog().error("ssh删除文件异常", e); - return JsonMessage.getString(400, "删除失败:" + e.getMessage()); - } finally { - IoUtil.close(sftp); - JschUtil.close(session); - } - } - - /** - * 删除文件 或者 文件夹 - * - * @param sftp ftp - * @param path 路径 - * @return true 删除的是 文件夹 - */ - private boolean tryDelDirOrFile(Sftp sftp, String path) { - try { - // 先尝试删除文件夹 - sftp.delDir(path); - return true; - } catch (Exception e) { - // 删除文件 - sftp.delFile(path); - } - return false; - } - -// /** -// * 删除文件或者文件夹 -// * -// * @param channel channel -// * @param path 文件路径 -// * @throws SftpException SftpException -// */ -// private void deleteFile(ChannelSftp channel, String path) throws SftpException { -// Vector vector = channel.ls(path); -// if (null == vector) { -// return; -// } -// int size = vector.size(); -// if (size == 1) { -// // 文件,直接删除 -// channel.rm(path); -// } else if (size == 2) { -// // 空文件夹,直接删除 -// channel.rmdir(path); -// } else { -// // 删除文件夹下所有文件 -// String fileName; -// for (ChannelSftp.LsEntry en : vector) { -// fileName = en.getFilename(); -// if (!".".equals(fileName) && !"..".equals(fileName)) { -// deleteFile(channel, path + "/" + fileName); -// } -// } -// channel.rmdir(path); -// } -// } - - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.UPLOAD) - public String upload(String id, String path, String name) { - SshModel sshModel = sshService.getByKey(id, false); - Assert.notNull(sshModel, "ssh error"); - List fileDirs = sshModel.fileDirs(); - Assert.state(CollUtil.contains(fileDirs, path), "没有配置此文件夹"); - Session session = null; - ChannelSftp channel = null; - String localPath = null; - try { - session = SshService.getSessionByModel(sshModel); - channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); - MultipartFileBuilder multipartFileBuilder = createMultipart().addFieldName("file").setUseOriginalFilename(true); - localPath = multipartFileBuilder.save(); - File file = FileUtil.file(localPath); - String normalize = FileUtil.normalize(path + StrUtil.SLASH + name); - channel.cd(normalize); - channel.put(IoUtil.toStream(file), file.getName()); - } catch (Exception e) { - DefaultSystemLog.getLog().error("ssh上传文件异常", e); - return JsonMessage.getString(400, "上传失败"); - } finally { - JschUtil.close(channel); - JschUtil.close(session); - FileUtil.del(localPath); - } - return JsonMessage.getString(200, "上传成功"); - } - -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java b/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java deleted file mode 100644 index 603a1e14e6..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java +++ /dev/null @@ -1,197 +0,0 @@ -package io.jpom.controller.node.ssh; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.ZipUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.Type; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.SshModel; -import io.jpom.model.system.AgentAutoUser; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.ssh.SshService; -import io.jpom.system.ConfigBean; -import io.jpom.system.ExtConfigBean; -import io.jpom.system.ServerConfigBean; -import org.springframework.boot.env.YamlPropertySourceLoader; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.FileUrlResource; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.io.File; -import java.util.List; -import java.util.Objects; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * ssh 安装插件端 - * - * @author bwcx_jzy - * @date 2019/8/17 - */ -@Controller -@RequestMapping(value = "node/ssh") -@Feature(cls = ClassFeature.SSH) -public class SshInstallAgentController extends BaseServerController { - - private final SshService sshService; - - public SshInstallAgentController(SshService sshService) { - this.sshService = sshService; - } - - @RequestMapping(value = "installAgentSubmit.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EXECUTE) - public String installAgentSubmit(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "节点数据") String nodeData, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "安装路径") String path) throws Exception { - // 判断输入的节点信息 - Object object = getNodeModel(nodeData); - if (object instanceof JsonMessage) { - return object.toString(); - } - NodeModel nodeModel = (NodeModel) object; - // - SshModel sshModel = sshService.getByKey(id, false); - Objects.requireNonNull(sshModel, "没有找到对应ssh"); - // - String tempFilePath = ServerConfigBean.getInstance().getUserTempPath().getAbsolutePath(); - MultipartFileBuilder cert = createMultipart().addFieldName("file").setSavePath(tempFilePath); - String filePath = cert.save(); - // - File outFle = FileUtil.file(tempFilePath, Type.Agent.name() + "_" + IdUtil.fastSimpleUUID()); - try { - try (ZipFile zipFile = new ZipFile(filePath)) { - // 判断文件是否正确 - ZipEntry sh = zipFile.getEntry(Type.Agent.name() + ".sh"); - ZipEntry lib = zipFile.getEntry("lib" + StrUtil.SLASH); - if (sh == null || null == lib || !lib.isDirectory()) { - return JsonMessage.getString(405, "不是 Jpom 插件包"); - } - ZipUtil.unzip(zipFile, outFle); - } - // 获取上传的tag - File shFile = FileUtil.file(outFle, Type.Agent.name() + ".sh"); - List lines = FileUtil.readLines(shFile, CharsetUtil.CHARSET_UTF_8); - String tag = null; - for (String line : lines) { - line = line.trim(); - if (StrUtil.startWith(line, "Tag=\"") && StrUtil.endWith(line, "\"")) { - tag = line.substring(5, line.length() - 1); - break; - } - } - Assert.hasText(tag, "管理命令中不存在tag"); - // 读取授权信息 - File configFile = FileUtil.file(outFle, ExtConfigBean.FILE_NAME); - if (configFile.exists()) { - YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); - List> extConfig = yamlPropertySourceLoader.load(ExtConfigBean.FILE_NAME, new FileUrlResource(configFile.getAbsolutePath())); - Assert.notEmpty(extConfig, "没有加载到配置信息,或者配置信息为空"); - PropertySource propertySource = extConfig.get(0); - Object user = propertySource.getProperty(ConfigBean.AUTHORIZE_USER_KEY); - nodeModel.setLoginName(Convert.toStr(user, "")); - // - Object pwd = propertySource.getProperty(ConfigBean.AUTHORIZE_AUTHORIZE_KEY); - nodeModel.setLoginPwd(Convert.toStr(pwd, "")); - } - // 查询远程是否运行 - - Assert.state(!sshService.checkSshRun(sshModel, tag), "对应服务器中已经存在 Jpom 插件端,不需要再次安装啦"); - - // 上传文件到服务器 - sshService.uploadDir(sshModel, path, outFle); - // - String shPtah = FileUtil.normalize(path + StrUtil.SLASH + Type.Agent.name() + ".sh"); - String chmod = getParameter("chmod"); - if (StrUtil.isEmpty(chmod)) { - chmod = StrUtil.EMPTY; - } else { - chmod = StrUtil.format("{} {} && ", chmod, shPtah); - } - String command = StrUtil.format("{}sh {} start upgrade", chmod, shPtah); - String result = sshService.exec(sshModel, command); - // 休眠 5 秒, 尝试 5 次 - int waitCount = getParameterInt("waitCount", 5); - waitCount = Math.max(waitCount, 5); - //int time = 3; - while (--waitCount >= 0) { - //DefaultSystemLog.getLog().debug("there is left {} / 3 times try to get authorize info", waitCount); - Thread.sleep(5 * 1000); - if (StrUtil.isEmpty(nodeModel.getLoginName()) || StrUtil.isEmpty(nodeModel.getLoginPwd())) { - String error = this.getAuthorize(sshModel, nodeModel, path); - // 获取授权成功就不需要继续循环了 - if (error == null) { - waitCount = -1; - } - // 获取授权失败且尝试次数用完 - if (error != null && waitCount == 0) { - return error; - } - } - } - nodeModel.setOpenStatus(1); - // 绑定关系 - //nodeModel.setSshId(sshModel.getId()); - nodeService.insert(nodeModel); - // - return JsonMessage.getString(200, "操作成功:" + result); - } finally { - // 清理资源 - FileUtil.del(filePath); - FileUtil.del(outFle); - } - } - - private String getAuthorize(SshModel sshModel, NodeModel nodeModel, String path) { - File saveFile = null; - try { - String tempFilePath = ServerConfigBean.getInstance().getUserTempPath().getAbsolutePath(); - // 获取远程的授权信息 - String normalize = FileUtil.normalize(StrUtil.format("{}/{}/{}", path, ConfigBean.DATA, ConfigBean.AUTHORIZE)); - saveFile = FileUtil.file(tempFilePath, IdUtil.fastSimpleUUID() + ConfigBean.AUTHORIZE); - sshService.download(sshModel, normalize, saveFile); - // - String json = FileUtil.readString(saveFile, CharsetUtil.CHARSET_UTF_8); - AgentAutoUser autoUser = JSONObject.parseObject(json, AgentAutoUser.class); - nodeModel.setLoginPwd(autoUser.getAgentPwd()); - nodeModel.setLoginName(autoUser.getAgentName()); - } catch (Exception e) { - DefaultSystemLog.getLog().error("拉取授权信息失败:{}", e.getMessage()); - return JsonMessage.getString(500, "获取授权信息失败,请检查对应的插件端运行状态", e.getMessage()); - } finally { - FileUtil.del(saveFile); - } - return null; - } - - private Object getNodeModel(String data) { - NodeModel nodeModel = JSONObject.toJavaObject(JSONObject.parseObject(data), NodeModel.class); - Assert.hasText(nodeModel.getId(), "节点id错误"); - - Assert.hasText(nodeModel.getName(), "输入节点名称"); - - Assert.hasText(nodeModel.getUrl(), "请输入节点地址"); - - return nodeModel; - } - -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/system/WhitelistDirectoryController.java b/modules/server/src/main/java/io/jpom/controller/node/system/WhitelistDirectoryController.java deleted file mode 100644 index 721b9b3678..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/system/WhitelistDirectoryController.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.jpom.controller.node.system; - -import cn.hutool.core.util.ReflectUtil; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.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 java.lang.reflect.Field; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -/** - * 白名单目录 - * - * @author jiangzeyin - * @date 2019/2/28 - */ -@RestController -@RequestMapping(value = "/node/system") -@Feature(cls = ClassFeature.NODE_CONFIG_WHITELIST) -@SystemPermission -public class WhitelistDirectoryController extends BaseServerController { - - private final WhitelistDirectoryService whitelistDirectoryService; - - public WhitelistDirectoryController(WhitelistDirectoryService whitelistDirectoryService) { - this.whitelistDirectoryService = whitelistDirectoryService; - } - - - /** - * get whiteList data - * 白名单数据接口 - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission - public String whiteList() { - AgentWhitelist agentWhitelist = whitelistDirectoryService.getData(getNode()); - Map map = new HashMap<>(8); - if (agentWhitelist != null) { - /** - * put key and value into map - * 赋值给 map 对象返回 - */ - Field[] fields = ReflectUtil.getFields(AgentWhitelist.class); - for (Field field : fields) { - Collection fieldValue = (Collection) ReflectUtil.getFieldValue(agentWhitelist, field); - map.put(field.getName(), AgentWhitelist.convertToLine(fieldValue)); - } - } - return JsonMessage.getString(200, "ok", map); - } - - - /** - * 保存接口 - * - * @return json - */ - @RequestMapping(value = "whitelistDirectory_submit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission - public String whitelistDirectorySubmit() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.WhitelistDirectory_Submit).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/system/nginx/NginxController.java b/modules/server/src/main/java/io/jpom/controller/node/system/nginx/NginxController.java deleted file mode 100644 index 734325887c..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/system/nginx/NginxController.java +++ /dev/null @@ -1,171 +0,0 @@ -package io.jpom.controller.node.system.nginx; - -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.system.WhitelistDirectoryService; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.List; - -/** - * nginx 管理 - * - * @author Arno - */ -@Controller -@RequestMapping("/node/system/nginx") -@Feature(cls = ClassFeature.NGINX) -@SystemPermission -public class NginxController extends BaseServerController { - - private final WhitelistDirectoryService whitelistDirectoryService; - - public NginxController(WhitelistDirectoryService whitelistDirectoryService) { - this.whitelistDirectoryService = whitelistDirectoryService; - } - - - /** - * 配置列表 - * - * @return json - */ - @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String list() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_list_data).toString(); - } - - /** - * 配置列表 - * - * @return json - */ - @RequestMapping(value = "tree.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String tree() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_Tree).toString(); - } - - - /** - * @return - * @author Hotstrip - * load Nginx white list data - */ - @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String loadWhiteList() { - List list = whitelistDirectoryService.getNgxDirectory(getNode()); - return JsonMessage.getString(200, "success", list); - } - - /** - * @return - * @author Hotstrip - * load Nginx config data - */ - @RequestMapping(value = "load-config", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String loadConfig() { - JSONObject data = NodeForward.requestData(getNode(), NodeUrl.System_Nginx_item_data, getRequest(), JSONObject.class); - return JsonMessage.getString(200, "success", data); - } - - @RequestMapping(value = "updateNgx", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String updateNgx() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_updateNgx).toString(); - } - - - @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DEL) - public String delete() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_delete).toString(); - } - - /** - * 获取nginx状态 - * - * @return json - */ - @RequestMapping(value = "status", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String status() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_status).toString(); - } - - /** - * 获取nginx配置状态 - * - * @return json - */ - @RequestMapping(value = "config", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String config() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_config).toString(); - } - - /** - * 启动nginx - * - * @return json - */ - @RequestMapping(value = "open", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EXECUTE) - public String open() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_open).toString(); - } - - /** - * 关闭nginx - * - * @return json - */ - @RequestMapping(value = "close", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EXECUTE) - public String close() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_close).toString(); - } - - - /** - * 修改nginx - * - * @return json - */ - @RequestMapping(value = "updateConf", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String updateConf() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_updateConf).toString(); - } - - @RequestMapping(value = "reload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EXECUTE) - public String reload() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Nginx_reload).toString(); - } - -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/system/ssl/CertificateController.java b/modules/server/src/main/java/io/jpom/controller/node/system/ssl/CertificateController.java deleted file mode 100644 index 0cd27cfff0..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/system/ssl/CertificateController.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.jpom.controller.node.system.ssl; - -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.system.WhitelistDirectoryService; -import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.List; - -/** - * 证书管理 - * - * @author Arno - */ -@Controller -@RequestMapping(value = "/node/system/certificate") -@Feature(cls = ClassFeature.SSL) -@SystemPermission -public class CertificateController extends BaseServerController { - - private final WhitelistDirectoryService whitelistDirectoryService; - - public CertificateController(WhitelistDirectoryService whitelistDirectoryService) { - this.whitelistDirectoryService = whitelistDirectoryService; - } - - /** - * @return - * @author Hotstrip - * load Cert white list data - */ - @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String loadWhiteList() { - List list = whitelistDirectoryService.getCertificateDirectory(getNode()); - return JsonMessage.getString(200, "success", list); - } - - /** - * 保存证书 - * - * @return json - */ - @RequestMapping(value = "/saveCertificate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.EDIT) - public String saveCertificate() { - if (ServletFileUpload.isMultipartContent(getRequest())) { - return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.System_Certificate_saveCertificate).toString(); - } - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Certificate_saveCertificate).toString(); - } - - - /** - * 证书列表 - * - * @return json - */ - @RequestMapping(value = "/getCertList", produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String getCertList() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Certificate_getCertList).toString(); - } - - /** - * 删除证书 - * - * @return json - */ - @RequestMapping(value = "/delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DEL) - public String delete() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.System_Certificate_delete).toString(); - } - - - /** - * 导出证书 - */ - @RequestMapping(value = "/export", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DOWNLOAD) - public void export() { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.System_Certificate_export); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/tomcat/TomcatLogController.java b/modules/server/src/main/java/io/jpom/controller/node/tomcat/TomcatLogController.java deleted file mode 100644 index e4d31016cf..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/tomcat/TomcatLogController.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.jpom.controller.node.tomcat; - -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author bwcx_jzy - * @date 2019/8/16 - */ -@RestController -@RequestMapping(value = TomcatManageController.TOMCAT_URL) -@Feature(cls = ClassFeature.TOMCAT_LOG) -@SystemPermission -public class TomcatLogController extends BaseServerController { - - - @RequestMapping(value = "getLogList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String getLogList() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Tomcat_LOG_List).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/node/tomcat/TomcatManageController.java b/modules/server/src/main/java/io/jpom/controller/node/tomcat/TomcatManageController.java deleted file mode 100644 index 01af1bc72f..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/node/tomcat/TomcatManageController.java +++ /dev/null @@ -1,207 +0,0 @@ -package io.jpom.controller.node.tomcat; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.tomcat.TomcatService; -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.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; - -/** - * tomcat 管理 - * - * @author lf - */ -@RestController -@RequestMapping(value = TomcatManageController.TOMCAT_URL) -@Feature(cls = ClassFeature.TOMCAT) -@SystemPermission -public class TomcatManageController extends BaseServerController { - - public static final String TOMCAT_URL = "/node/tomcat/"; - - - @Resource - private TomcatService tomcatService; - - /** - * @return - * @Hotstrip get tomcat list - */ - @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.LIST) - public String tomcatList() { - JSONArray jsonArray = tomcatService.getTomcatList(getNode()); - return JsonMessage.getString(200, "success", jsonArray); - } - - /** - * 查询tomcat的项目 - * - * @param id id - * @return tomcat的项目信息 - */ - @RequestMapping(value = "getTomcatProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getTomcatProject(String id) { - // 查询tomcat管理的项目的列表 - JSONArray tomcatProjects = tomcatService.getTomcatProjectList(getNode(), id); - return JsonMessage.getString(200, "查询成功", tomcatProjects); - } - - /** - * 查询tomcat状态 - * - * @return tomcat运行状态 - */ - @RequestMapping(value = "getTomcatStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getStatus() { - return tomcatService.getTomcatStatus(getNode(), getRequest()); - } - - - /** - * 保存Tomcat信息 - * - * @param id tomcat的id,如果id非空则更新,如果id是空则保存 - * @return 操作结果 - */ - @RequestMapping(value = "save", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(method = MethodFeature.EDIT) - public String save(String id) { - NodeModel nodeModel = getNode(); - if (StrUtil.isEmpty(id)) { - return tomcatService.addTomcat(nodeModel, getRequest()); - } else { - // 修改Tomcat信息 - return tomcatService.updateTomcat(nodeModel, getRequest()); - } - } - - /** - * 删除tomcat - * - * @return 操作结果 - */ - @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(method = MethodFeature.DEL) - public String delete() { - return tomcatService.delete(getNode(), getRequest()); - } - - - /** - * tomcat项目管理 - * - * @return 操作结果 - */ - @RequestMapping(value = "tomcatProjectManage", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String tomcatProjectManage() { - return tomcatService.tomcatProjectManage(getNode(), getRequest()); - } - - /** - * 启动tomcat - * - * @return 操作结果 - */ - @RequestMapping(value = "start", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String start() { - return tomcatService.start(getNode(), getRequest()); - } - - /** - * 重启tomcat - * - * @return 操作结果 - */ - @RequestMapping(value = "restart", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String restart() { - return tomcatService.restart(getNode(), getRequest()); - } - - /** - * 停止tomcat - * - * @return 操作结果 - */ - @RequestMapping(value = "stop", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String stop() { - return tomcatService.stop(getNode(), getRequest()); - } - - - /** - * 查询文件列表 - * - * @return 文件列表 - */ - @RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.TEXT_HTML_VALUE) - @Feature(cls = ClassFeature.TOMCAT_FILE, method = MethodFeature.LIST) - public String getFileList() { - return tomcatService.getFileList(getNode(), getRequest()); - } - - - /** - * 上传文件 - * - * @return 操作结果 - */ - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(cls = ClassFeature.TOMCAT_FILE, method = MethodFeature.UPLOAD) - public String upload() { - return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.Tomcat_File_Upload).toString(); - } - - /** - * 上传War包 - * - * @return 操作结果 - */ - @RequestMapping(value = "uploadWar", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(cls = ClassFeature.TOMCAT_FILE, method = MethodFeature.UPLOAD) - public String uploadWar() { - return tomcatService.uploadWar(getNode(), getMultiRequest()); - } - - /** - * 下载文件 - */ - @RequestMapping(value = "download", method = RequestMethod.GET) - @Feature(cls = ClassFeature.TOMCAT_FILE, method = MethodFeature.DOWNLOAD) - public void download() { - tomcatService.download(getNode(), getRequest(), getResponse()); - } - - /** - * 删除文件 - * - * @return 操作结果 - */ - @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(cls = ClassFeature.TOMCAT_FILE, method = MethodFeature.DEL) - public String deleteFile() { - return tomcatService.deleteFile(getNode(), getRequest()); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/openapi/InstallIdController.java b/modules/server/src/main/java/io/jpom/controller/openapi/InstallIdController.java deleted file mode 100644 index 61bc1a1b3e..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/openapi/InstallIdController.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.openapi; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.ServerOpenApi; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import io.jpom.util.JsonFileUtil; -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.io.File; -import java.io.FileNotFoundException; - -/** - * 获取当前服务端安装id - * - * @author bwcx_jzy - * @date 2019/8/7 - */ -@RestController -public class InstallIdController { - - @RequestMapping(value = ServerOpenApi.INSTALL_ID, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String install() throws FileNotFoundException { - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.INSTALL); - if (!file.exists()) { - return JsonMessage.getString(500, "服务端的安装信息文件不存在"); - } - JSONObject json = (JSONObject) JsonFileUtil.readJson(file.getAbsolutePath()); - String installId = json.getString("installId"); - if (StrUtil.isEmpty(installId)) { - return JsonMessage.getString(500, "服务端的安装Id为空"); - } - return JsonMessage.getString(200, "ok", installId); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/openapi/NodeInfoController.java b/modules/server/src/main/java/io/jpom/controller/openapi/NodeInfoController.java deleted file mode 100644 index a2af936225..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/openapi/NodeInfoController.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.openapi; - -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.base.AbstractController; -import io.jpom.common.ServerOpenApi; -import io.jpom.service.node.NodeService; -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; - -/** - * 节点管理 - * - * @author bwcx_jzy - * @date 2019/8/5 - */ -@RestController -public class NodeInfoController extends AbstractController { - - - private final NodeService nodeService; - - public NodeInfoController(NodeService nodeService) { - this.nodeService = nodeService; - } - - /** - * 添加或者更新节点信息 - * - * @return json - */ - @RequestMapping(value = ServerOpenApi.UPDATE_NODE_INFO, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String update() { - nodeService.update(getRequest()); - return JsonMessage.getString(200, "操作成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/openapi/ProjectInfoController.java b/modules/server/src/main/java/io/jpom/controller/openapi/ProjectInfoController.java deleted file mode 100644 index 6ef117dcc0..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/openapi/ProjectInfoController.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.openapi; -// -//import cn.jiangzeyin.common.JsonMessage; -//import io.jpom.common.ServerOpenApi; -//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; -// -///** -// * @author bwcx_jzy -// * @date 2019/11/18 -// */ -//@RestController -//@RequestMapping(value = ServerOpenApi.API + "project") -//public class ProjectInfoController { -// -// @RequestMapping(value = "sync", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// public JsonMessage sync() { -// -// } -//} diff --git a/modules/server/src/main/java/io/jpom/controller/openapi/build/BuildTriggerApiController.java b/modules/server/src/main/java/io/jpom/controller/openapi/build/BuildTriggerApiController.java deleted file mode 100644 index 0f0e627d7a..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/openapi/build/BuildTriggerApiController.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.openapi.build; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import io.jpom.common.ServerOpenApi; -import io.jpom.common.interceptor.NotLogin; -import io.jpom.controller.build.BuildInfoTriggerController; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.user.UserService; -import org.h2.util.StringUtils; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * @author bwcx_jzy - * @date 2019/9/4 - */ -@RestController -@NotLogin -public class BuildTriggerApiController { - - private final BuildInfoService buildInfoService; - - private final UserService userService; - - public BuildTriggerApiController(BuildInfoService buildInfoService, - UserService userService) { - this.buildInfoService = buildInfoService; - this.userService = userService; - } - - private UserModel getByUrlToken(String token) { - String digestCountStr = StrUtil.sub(token, 0, BuildInfoTriggerController.BUILD_INFO_TRIGGER_TOKEN_FILL_LEN); - String result = StrUtil.subSuf(token, BuildInfoTriggerController.BUILD_INFO_TRIGGER_TOKEN_FILL_LEN); - int digestCount = Convert.toInt(digestCountStr, 1); - - String sql = "select HASH('SHA256', id,?) as token,id from user_info"; - List query = userService.query(sql, digestCount); - if (query == null) { - return null; - } - String userId = query.stream() - .filter(entity -> { - Object token1 = entity.get("token"); - String sha256; - if (token1 instanceof byte[]) { - byte[] bytes = (byte[]) token1; - sha256 = StringUtils.convertBytesToHex(bytes); - } else { - sha256 = ObjectUtil.toString(token1); - } - return StrUtil.equals(result, sha256); - }) - .map(entity -> StrUtil.toString(entity.get("id"))) - .findFirst() - .orElseGet(() -> "没有对应数据:-2"); - return userService.getByKey(userId); - } - - - /** - * 构建触发器 - * - * @param id 构建ID - * @param token 构建的token - * @param delay 延迟时间(单位秒) - * @return json - */ - @RequestMapping(value = ServerOpenApi.BUILD_TRIGGER_BUILD2, produces = MediaType.APPLICATION_JSON_VALUE) - public String trigger2(@PathVariable String id, @PathVariable String token, String delay) { - BuildInfoModel item = buildInfoService.getByKey(id); - Assert.notNull(item, "没有对应数据"); - UserModel userModel = this.getByUrlToken(token); - // - Assert.notNull(userModel, "没有对应数据:-1"); - - Assert.state(StrUtil.equals(token, item.getTriggerToken()), "触发token错误,或者已经失效"); - - return buildInfoService.start(item, userModel, Convert.toInt(delay, 0)); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingController.java b/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingController.java deleted file mode 100644 index c61c9a3a0d..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingController.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.outgiving; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.AfterOpt; -import io.jpom.model.BaseEnum; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.OutGivingModel; -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.model.data.UserModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BuildInfoService; -import io.jpom.service.dblog.DbOutGivingLogService; -import io.jpom.service.node.OutGivingServer; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * 分发控制 - * - * @author jiangzeyin - * @date 2019/4/20 - */ -@RestController -@RequestMapping(value = "/outgiving") -@Feature(cls = ClassFeature.OUTGIVING) -public class OutGivingController extends BaseServerController { - - private final OutGivingServer outGivingServer; - private final BuildInfoService buildService; - private final DbOutGivingLogService dbOutGivingLogService; - private final ProjectInfoCacheService projectInfoCacheService; - - public OutGivingController(OutGivingServer outGivingServer, - BuildInfoService buildService, - DbOutGivingLogService dbOutGivingLogService, - ProjectInfoCacheService projectInfoCacheService) { - this.outGivingServer = outGivingServer; - this.buildService = buildService; - this.dbOutGivingLogService = dbOutGivingLogService; - this.projectInfoCacheService = projectInfoCacheService; - } - - /** - * load dispatch list - * 加载分发列表 - * - * @return json - * @author Hotstrip - */ - @PostMapping(value = "dispatch-list", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String dispatchList() { - PageResultDto pageResultDto = outGivingServer.listPage(getRequest()); - return JsonMessage.getString(200, "success", pageResultDto); - } - - /** - * load dispatch list - * 加载分发列表 - * - * @return json - * @author Hotstrip - */ - @GetMapping(value = "dispatch-list-all", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String dispatchListAll() { - List outGivingModels = outGivingServer.listByWorkspace(getRequest()); - return JsonMessage.getString(200, "", outGivingModels); - } - - - @RequestMapping(value = "save", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String save(String type, String id) throws IOException { - if ("add".equalsIgnoreCase(type)) { - boolean general = StringUtil.isGeneral(id, 2, 20); - Assert.state(general, "分发id 不能为空并且长度在2-20(英文字母 、数字和下划线)"); - return addOutGiving(id); - } else { - return updateGiving(id); - } - } - - private String addOutGiving(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id); - Assert.isNull(outGivingModel, "分发id已经存在啦"); - // - outGivingModel = new OutGivingModel(); - outGivingModel.setId(id); - this.doData(outGivingModel); - // - outGivingServer.insert(outGivingModel); - return JsonMessage.getString(200, "添加成功"); - } - - private String updateGiving(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id); - Assert.notNull(outGivingModel, "没有找到对应的分发id"); - doData(outGivingModel); - - outGivingServer.update(outGivingModel); - return JsonMessage.getString(200, "修改成功"); - } - - private void doData(OutGivingModel outGivingModel) { - outGivingModel.setName(getParameter("name")); - Assert.hasText(outGivingModel.getName(), "分发名称不能为空"); - HttpServletRequest request = getRequest(); - List outGivingModels = outGivingServer.list(); - // - Map paramMap = ServletUtil.getParamMap(request); - List outGivingNodeProjects = paramMap.entrySet() - .stream() - .filter(stringStringEntry -> StrUtil.startWith(stringStringEntry.getKey(), "node_")) - .map(stringStringEntry -> { - int lastIndexOf = StrUtil.lastIndexOfIgnoreCase(stringStringEntry.getKey(), StrUtil.UNDERLINE); - int indexOf = StrUtil.indexOfIgnoreCase(stringStringEntry.getKey(), StrUtil.UNDERLINE) + 1; - String nodeId = StrUtil.sub(stringStringEntry.getKey(), indexOf, lastIndexOf); - // - String nodeIdProject = stringStringEntry.getValue(); - NodeModel nodeModel = nodeService.getByKey(nodeId); - Assert.notNull(nodeModel, "不存在对应的节点"); - // - boolean exists = projectInfoCacheService.exists(nodeModel.getWorkspaceId(), nodeModel.getId(), nodeIdProject); - Assert.state(exists, "没有找到对应的项目id:" + nodeIdProject); - // - OutGivingNodeProject outGivingNodeProject = outGivingModel.getNodeProject(nodeModel.getId(), nodeIdProject); - if (outGivingNodeProject == null) { - outGivingNodeProject = new OutGivingNodeProject(); - } - outGivingNodeProject.setNodeId(nodeModel.getId()); - outGivingNodeProject.setProjectId(nodeIdProject); - return outGivingNodeProject; - }) - .peek(outGivingNodeProject -> { - // 判断项目是否已经被使用过啦 - if (outGivingModels != null) { - for (OutGivingModel outGivingModel1 : outGivingModels) { - if (outGivingModel1.getId().equalsIgnoreCase(outGivingModel.getId())) { - continue; - } - boolean checkContains = outGivingModel1.checkContains(outGivingNodeProject.getNodeId(), outGivingNodeProject.getProjectId()); - Assert.state(!checkContains, "已经存在相同的分发项目:" + outGivingNodeProject.getProjectId()); - } - } - }).collect(Collectors.toList()); - - Assert.state(CollUtil.size(outGivingNodeProjects) >= 2, "至少选择2个节点项目"); - - outGivingModel.outGivingNodeProjectList(outGivingNodeProjects); - // - String afterOpt = getParameter("afterOpt"); - AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); - Assert.notNull(afterOpt1, "请选择分发后的操作"); - outGivingModel.setAfterOpt(afterOpt1.getCode()); - // - int intervalTime = getParameterInt("intervalTime", 10); - outGivingModel.setIntervalTime(intervalTime); - // - outGivingModel.setClearOld(Convert.toBool(getParameter("clearOld"), false)); - - } - - /** - * 删除分发信息 - * - * @param id 分发id - * @return json - */ - @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String del(String id) throws IOException { - // 判断构建 - boolean releaseMethod = buildService.checkReleaseMethod(id, BuildReleaseMethod.Outgiving); - Assert.state(!releaseMethod, "当前分发存在构建项,不能删除"); - OutGivingModel outGivingServerItem = outGivingServer.getByKey(id); - if (outGivingServerItem.isOutGivingProject()) { - UserModel userModel = getUser(); - // 解除项目分发独立分发属性 - List outGivingNodeProjectList = outGivingServerItem.outGivingNodeProjectList(); - if (outGivingNodeProjectList != null) { - outGivingNodeProjectList.forEach(outGivingNodeProject -> { - NodeModel item = nodeService.getByKey(outGivingNodeProject.getNodeId()); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("id", outGivingNodeProject.getProjectId()); - NodeForward.request(item, NodeUrl.Manage_ReleaseOutGiving, userModel, jsonObject); - }); - } - } - int byKey = outGivingServer.delByKey(id, getRequest()); - if (byKey > 0) { - // 删除日志 - Entity where = new Entity(); - where.set("outGivingId", id); - dbOutGivingLogService.del(where); - } - return JsonMessage.getString(200, "操作成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingLogController.java b/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingLogController.java deleted file mode 100644 index 05ab144934..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingLogController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.outgiving; - -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.log.OutGivingLog; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.DbOutGivingLogService; -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; - -/** - * 分发日志 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@RestController -@RequestMapping(value = "/outgiving") -@Feature(cls = ClassFeature.OUTGIVING_LOG) -public class OutGivingLogController extends BaseServerController { - - private final DbOutGivingLogService dbOutGivingLogService; - - public OutGivingLogController(DbOutGivingLogService dbOutGivingLogService) { - this.dbOutGivingLogService = dbOutGivingLogService; - } - - - @RequestMapping(value = "log_list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String listData() { - PageResultDto pageResult = dbOutGivingLogService.listPage(getRequest()); - return JsonMessage.getString(200, "获取成功", pageResult); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingProjectController.java b/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingProjectController.java deleted file mode 100644 index 7753b15dd8..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingProjectController.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.outgiving; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.AfterOpt; -import io.jpom.model.BaseEnum; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.OutGivingModel; -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.model.data.ServerWhitelist; -import io.jpom.outgiving.OutGivingRun; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.OutGivingServer; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * 分发文件管理 - * - * @author jiangzeyin - * @date 2019/4/21 - */ -@RestController -@RequestMapping(value = "/outgiving") -@Feature(cls = ClassFeature.OUTGIVING) -public class OutGivingProjectController extends BaseServerController { - - private final OutGivingServer outGivingServer; - private final ProjectInfoCacheService projectInfoCacheService; - private final SystemParametersServer systemParametersServer; - - public OutGivingProjectController(OutGivingServer outGivingServer, - ProjectInfoCacheService projectInfoCacheService, - SystemParametersServer systemParametersServer) { - this.outGivingServer = outGivingServer; - this.projectInfoCacheService = projectInfoCacheService; - this.systemParametersServer = systemParametersServer; - } - - @RequestMapping(value = "getProjectStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getProjectStatus() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_GetProjectStatus).toString(); - } - - - @RequestMapping(value = "getItemData.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String getItemData(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "id error") String id) { - OutGivingModel outGivingServerItem = outGivingServer.getByKey(id, getRequest()); - Objects.requireNonNull(outGivingServerItem, "没有数据"); - List outGivingNodeProjectList = outGivingServerItem.outGivingNodeProjectList(); - JSONArray jsonArray = new JSONArray(); - outGivingNodeProjectList.forEach(outGivingNodeProject -> { - NodeModel nodeModel = nodeService.getByKey(outGivingNodeProject.getNodeId()); - JSONObject jsonObject = new JSONObject(); - JSONObject projectInfo = null; - try { - projectInfo = projectInfoCacheService.getItem(nodeModel, outGivingNodeProject.getProjectId()); - } catch (Exception e) { - jsonObject.put("errorMsg", "error " + e.getMessage()); - } - - jsonObject.put("nodeId", outGivingNodeProject.getNodeId()); - jsonObject.put("projectId", outGivingNodeProject.getProjectId()); - jsonObject.put("nodeName", nodeModel.getName()); - if (projectInfo != null) { - jsonObject.put("projectName", projectInfo.getString("name")); - } - - // set projectStatus property - //NodeModel node = nodeService.getItem(outGivingNodeProject.getNodeId()); - // Project Status: data.pid > 0 means running - JSONObject projectStatus = JsonMessage.toJson(200, "success"); - if (nodeModel.isOpenStatus()) { - projectStatus = NodeForward.requestBySys(nodeModel, NodeUrl.Manage_GetProjectStatus, "id", outGivingNodeProject.getProjectId()).toJson(); - } - if (projectStatus.getJSONObject("data") != null && projectStatus.getJSONObject("data").getInteger("pId") != null) { - jsonObject.put("projectStatus", projectStatus.getJSONObject("data").getIntValue("pId") > 0); - } else { - jsonObject.put("projectStatus", false); - } - - jsonObject.put("outGivingStatus", outGivingNodeProject.getStatusMsg()); - jsonObject.put("outGivingResult", outGivingNodeProject.getResult()); - jsonObject.put("lastTime", outGivingNodeProject.getLastOutGivingTime()); - jsonArray.add(jsonObject); - }); - return JsonMessage.getString(200, "", jsonArray); - } - - private File checkZip(String path, boolean unzip) { - File file = FileUtil.file(path); - return this.checkZip(file, unzip); - } - - private File checkZip(File path, boolean unzip) { - if (unzip) { - boolean zip = false; - for (String i : StringUtil.PACKAGE_EXT) { - if (FileUtil.pathEndsWith(path, i)) { - zip = true; - break; - } - } - Assert.state(zip, "不支持的文件类型:" + path.getName()); - } - return path; - } - - /** - * 节点分发文件 - * - * @param id 分发id - * @param afterOpt 之后的操作 - * @param autoUnzip 是否自动解压 - * @param clearOld 清空发布 - * @return json - * @throws IOException IO - */ - @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.UPLOAD) - public String upload(String id, String afterOpt, String clearOld, String autoUnzip) throws IOException { - OutGivingModel outGivingModel = this.check(id); - AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); - Assert.notNull(afterOpt1, "请选择分发后的操作"); - // - boolean unzip = Convert.toBool(autoUnzip, false); - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.OUTGIVING_FILE, id); - MultipartFileBuilder multipartFileBuilder = createMultipart(); - multipartFileBuilder - .setUseOriginalFilename(true) - // .setFileExt(StringUtil.PACKAGE_EXT) - .addFieldName("file") - .setSavePath(FileUtil.getAbsolutePath(file)); - String path = multipartFileBuilder.save(); - // - File dest = this.checkZip(path, unzip); - // - //outGivingModel = outGivingServer.getItem(id); - outGivingModel.setClearOld(Convert.toBool(clearOld, false)); - outGivingModel.setAfterOpt(afterOpt1.getCode()); - - outGivingServer.update(outGivingModel); - // 开启 - OutGivingRun.startRun(outGivingModel.getId(), dest, getUser(), unzip); - return JsonMessage.getString(200, "分发成功"); - } - - private OutGivingModel check(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id, getRequest()); - Assert.notNull(outGivingModel, "上传失败,没有找到对应的分发项目"); - // 检查状态 - List outGivingNodeProjectList = outGivingModel.outGivingNodeProjectList(); - Objects.requireNonNull(outGivingNodeProjectList); - for (OutGivingNodeProject outGivingNodeProject : outGivingNodeProjectList) { - Assert.state(outGivingNodeProject.getStatus() != OutGivingNodeProject.Status.Ing.getCode(), "当前还在分发中,请等待分发结束"); - } - return outGivingModel; - } - - /** - * 远程下载节点分发文件 - * - * @param id 分发id - * @param afterOpt 之后的操作 - * @return json - */ - @RequestMapping(value = "remote_download", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.REMOTE_DOWNLOAD) - public String remoteDownload(String id, String afterOpt, String clearOld, String url, String autoUnzip) { - OutGivingModel outGivingModel = this.check(id); - AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); - Assert.notNull(afterOpt1, "请选择分发后的操作"); - // 验证远程 地址 - ServerWhitelist whitelist = systemParametersServer.getConfigDefNewInstance(ServerWhitelist.ID, ServerWhitelist.class); - Set allowRemoteDownloadHost = whitelist.getAllowRemoteDownloadHost(); - Assert.state(CollUtil.isNotEmpty(allowRemoteDownloadHost), "还没有配置运行的远程地址"); - List collect = allowRemoteDownloadHost.stream().filter(s -> StrUtil.startWith(url, s)).collect(Collectors.toList()); - Assert.state(CollUtil.isNotEmpty(collect), "不允许下载当前地址的文件"); - try { - //outGivingModel = outGivingServer.getItem(id); - outGivingModel.setClearOld(Convert.toBool(clearOld, false)); - outGivingModel.setAfterOpt(afterOpt1.getCode()); - outGivingServer.update(outGivingModel); - //下载 - File file = FileUtil.file(ServerConfigBean.getInstance().getUserTempPath(), ServerConfigBean.OUTGIVING_FILE, id); - FileUtil.mkdir(file); - File downloadFile = HttpUtil.downloadFileFromUrl(url, file); - boolean unzip = BooleanUtil.toBoolean(autoUnzip); - // - this.checkZip(downloadFile, unzip); - // 开启 - OutGivingRun.startRun(outGivingModel.getId(), downloadFile, getUser(), unzip); - return JsonMessage.getString(200, "分发成功"); - } catch (Exception e) { - DefaultSystemLog.getLog().error("下载远程文件异常", e); - return JsonMessage.getString(500, "下载远程文件失败:" + e.getMessage()); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingProjectEditController.java b/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingProjectEditController.java deleted file mode 100644 index dd429726f9..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingProjectEditController.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.outgiving; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.AfterOpt; -import io.jpom.model.BaseEnum; -import io.jpom.model.RunMode; -import io.jpom.model.data.*; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.node.OutGivingServer; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.util.StringUtil; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * 节点分发编辑项目 - * - * @author jiangzeyin - * @date 2019/4/22 - */ -@RestController -@RequestMapping(value = "/outgiving") -@Feature(cls = ClassFeature.OUTGIVING) -public class OutGivingProjectEditController extends BaseServerController { - - private final SystemParametersServer systemParametersServer; - private final OutGivingServer outGivingServer; - private final ProjectInfoCacheService projectInfoCacheService; - - public OutGivingProjectEditController(SystemParametersServer systemParametersServer, - OutGivingServer outGivingServer, - ProjectInfoCacheService projectInfoCacheService) { - this.systemParametersServer = systemParametersServer; - this.outGivingServer = outGivingServer; - this.projectInfoCacheService = projectInfoCacheService; - } - - /** - * 保存节点分发项目 - * - * @param id id - * @param type 类型 - * @return json - */ - @RequestMapping(value = "save_project", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String save(String id, String type) { - if ("add".equalsIgnoreCase(type)) { - boolean general = StringUtil.isGeneral(id, 2, 20); - Assert.state(general, "分发id 不能为空并且长度在2-20(英文字母 、数字和下划线)"); - return addOutGiving(id); - } else { - return updateGiving(id); - } - } - - /** - * 删除分发项目 - * - * @param id 项目id - * @return json - */ - @RequestMapping(value = "delete_project", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @Feature(method = MethodFeature.DEL) - public String delete(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id); - Assert.notNull(outGivingModel, "没有对应的分发项目"); - - Assert.state(outGivingModel.isOutGivingProject(), "该项目不是节点分发项目,不能在此次删除"); - - UserModel userModel = getUser(); - List deleteNodeProject = outGivingModel.outGivingNodeProjectList(); - if (deleteNodeProject != null) { - // 删除实际的项目 - for (OutGivingNodeProject outGivingNodeProject1 : deleteNodeProject) { - NodeModel nodeModel = nodeService.getByKey(outGivingNodeProject1.getNodeId()); - JsonMessage jsonMessage = deleteNodeProject(nodeModel, userModel, outGivingNodeProject1.getProjectId()); - if (jsonMessage.getCode() != HttpStatus.HTTP_OK) { - return JsonMessage.getString(406, nodeModel.getName() + "节点失败:" + jsonMessage.getMsg()); - } - } - } - outGivingServer.delByKey(id, getRequest()); - return JsonMessage.getString(200, "删除成功"); - } - - private String addOutGiving(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id); - Assert.isNull(outGivingModel, "分发id已经存在啦"); - - outGivingModel = new OutGivingModel(); - outGivingModel.setOutGivingProject(true); - outGivingModel.setId(id); - // - List tuples = doData(outGivingModel, false); - - outGivingServer.insert(outGivingModel); - String error = saveNodeData(outGivingModel, tuples, false); - if (error != null) { - return error; - } - return JsonMessage.getString(200, "添加成功"); - } - - - private String updateGiving(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id, getRequest()); - Assert.notNull(outGivingModel, "没有找到对应的分发id"); - List tuples = doData(outGivingModel, true); - - outGivingServer.update(outGivingModel); - String error = saveNodeData(outGivingModel, tuples, true); - if (error != null) { - return error; - } - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 保存节点项目数据 - * - * @param outGivingModel 节点分发项目 - * @param edit 是否为编辑模式 - * @return 错误信息 - */ - private String saveNodeData(OutGivingModel outGivingModel, List tuples, boolean edit) { - -// if () { -// if (!edit) { -// outGivingServer.delByKey(outGivingModel.getId()); -// } -// return JsonMessage.getString(405, "数据异常,请重新操作"); -// } - UserModel userModel = getUser(); - List success = new ArrayList<>(); - boolean fail = false; - try { - JsonMessage jsonMessage; - for (Tuple tuple : tuples) { - NodeModel nodeModel = tuple.get(0); - JSONObject data = tuple.get(1); - - jsonMessage = sendData(nodeModel, userModel, data, true); - if (jsonMessage.getCode() != HttpStatus.HTTP_OK) { - if (!edit) { - fail = true; - outGivingServer.delByKey(outGivingModel.getId()); - } - return JsonMessage.getString(406, nodeModel.getName() + "节点失败:" + jsonMessage.getMsg()); - } - success.add(tuple); - // 同步项目信息 - projectInfoCacheService.syncNode(nodeModel, outGivingModel.getId()); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("保存分发项目失败", e); - if (!edit) { - fail = true; - outGivingServer.delByKey(outGivingModel.getId()); - } - return JsonMessage.getString(500, "保存节点数据失败:" + e.getMessage()); - } finally { - if (fail) { - try { - for (Tuple entry : success) { - deleteNodeProject(entry.get(0), userModel, outGivingModel.getId()); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("还原项目失败", e); - } - } - } - return null; - } - - /** - * 删除项目 - * - * @param nodeModel 节点 - * @param userModel 用户 - * @param project 判断id - * @return json - */ - private JsonMessage deleteNodeProject(NodeModel nodeModel, UserModel userModel, String project) { - JSONObject data = new JSONObject(); - data.put("id", project); - JsonMessage request = NodeForward.request(nodeModel, NodeUrl.Manage_DeleteProject, userModel, data); - if (request.getCode() == HttpStatus.HTTP_OK) { - // 同步项目信息 - projectInfoCacheService.syncNode(nodeModel, project); - } - return request; -// // 发起预检查数据 -// String url = nodeModel.getRealUrl(NodeUrl.Manage_DeleteProject); -// HttpRequest request = HttpUtil.createPost(url); -// // 授权信息 -// NodeForward.addUser(request, nodeModel, userModel); - -// request.form(data); -// // -// String body = request.execute() -// .body(); -// return NodeForward.toJsonMessage(body); - } - - /** - * 创建项目管理的默认数据 - * - * @param outGivingModel 分发实体 - * @param edit 是否为编辑模式 - * @return String为有异常 - */ - private JSONObject getDefData(OutGivingModel outGivingModel, boolean edit) { - JSONObject defData = new JSONObject(); - defData.put("id", outGivingModel.getId()); - defData.put("name", outGivingModel.getName()); - // - // 运行模式 - String runMode = getParameter("runMode"); - RunMode runMode1 = RunMode.ClassPath; - try { - runMode1 = RunMode.valueOf(runMode); - } catch (Exception ignored) { - } - defData.put("runMode", runMode1.name()); - if (runMode1 == RunMode.ClassPath || runMode1 == RunMode.JavaExtDirsCp) { - String mainClass = getParameter("mainClass"); - defData.put("mainClass", mainClass); - } - if (runMode1 == RunMode.JavaExtDirsCp) { - defData.put("javaExtDirsCp", getParameter("javaExtDirsCp")); - } - String whitelistDirectory = getParameter("whitelistDirectory"); - ServerWhitelist configDeNewInstance = systemParametersServer.getConfigDefNewInstance(ServerWhitelist.ID, ServerWhitelist.class); - 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), "请选择正确的日志路径,或者还没有配置白名单"); - defData.put("logPath", logPath); - } - String lib = getParameter("lib"); - defData.put("lib", lib); - if (edit) { - // 编辑模式 - defData.put("edit", "on"); - } - defData.put("previewData", true); - return defData; - } - - /** - * 处理页面数据 - * - * @param outGivingModel 分发实体 - * @param edit 是否为编辑模式 - */ - private List doData(OutGivingModel outGivingModel, boolean edit) { - outGivingModel.setName(getParameter("name")); - Assert.hasText(outGivingModel.getName(), "分发名称不能为空"); - // - int intervalTime = getParameterInt("intervalTime", 10); - outGivingModel.setIntervalTime(intervalTime); - outGivingModel.setClearOld(Convert.toBool(getParameter("clearOld"), false)); - // - List nodeModelList = nodeService.listByWorkspace(getRequest()); - Assert.notEmpty(nodeModelList, "没有任何节点信息"); - - // - String afterOpt = getParameter("afterOpt"); - AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); - Assert.notNull(afterOpt1, "请选择分发后的操作"); - outGivingModel.setAfterOpt(afterOpt1.getCode()); - JSONObject defData = getDefData(outGivingModel, edit); - - UserModel userModel = getUser(); - // - List outGivingModels = outGivingServer.list(); - List outGivingNodeProjects = new ArrayList<>(); - OutGivingNodeProject outGivingNodeProject; - // - Iterator iterator = nodeModelList.iterator(); - - - List tuples = new ArrayList<>(); - while (iterator.hasNext()) { - NodeModel nodeModel = iterator.next(); - String add = getParameter("add_" + nodeModel.getId()); - if (!nodeModel.getId().equals(add)) { - iterator.remove(); - continue; - } - // 判断项目是否已经被使用过啦 - if (outGivingModels != null) { - for (OutGivingModel outGivingModel1 : outGivingModels) { - if (outGivingModel1.getId().equalsIgnoreCase(outGivingModel.getId())) { - continue; - } - Assert.state(!outGivingModel1.checkContains(nodeModel.getId(), outGivingModel.getId()), "已经存在相同的分发项目:" + outGivingModel.getId()); - - } - } - outGivingNodeProject = outGivingModel.getNodeProject(nodeModel.getId(), outGivingModel.getId()); - if (outGivingNodeProject == null) { - outGivingNodeProject = new OutGivingNodeProject(); - } - outGivingNodeProject.setNodeId(nodeModel.getId()); - outGivingNodeProject.setProjectId(outGivingModel.getId()); - outGivingNodeProjects.add(outGivingNodeProject); - // 检查数据 - JSONObject allData = defData.clone(); - String token = getParameter(StrUtil.format("{}_token", nodeModel.getId())); - allData.put("token", token); - String jvm = getParameter(StrUtil.format("{}_jvm", nodeModel.getId())); - allData.put("jvm", jvm); - String args = getParameter(StrUtil.format("{}_args", nodeModel.getId())); - allData.put("args", args); - String autoStart = getParameter(StrUtil.format("{}_autoStart", nodeModel.getId())); - allData.put("autoStart", Convert.toBool(autoStart, false)); - // 项目副本 - String javaCopyIds = getParameter(StrUtil.format("{}_javaCopyIds", nodeModel.getId())); - allData.put("javaCopyIds", javaCopyIds); - if (StrUtil.isNotEmpty(javaCopyIds)) { - String[] split = StrUtil.splitToArray(javaCopyIds, StrUtil.COMMA); - for (String copyId : split) { - String copyJvm = getParameter(StrUtil.format("{}_jvm_{}", nodeModel.getId(), copyId)); - String copyArgs = getParameter(StrUtil.format("{}_args_{}", nodeModel.getId(), copyId)); - allData.put("jvm_" + copyId, copyJvm); - allData.put("args_" + copyId, copyArgs); - } - } - JsonMessage jsonMessage = this.sendData(nodeModel, userModel, allData, false); - Assert.state(jsonMessage.getCode() == HttpStatus.HTTP_OK, nodeModel.getName() + "节点失败:" + jsonMessage.getMsg()); - tuples.add(new Tuple(nodeModel, allData)); - } - // 删除已经删除的项目 - deleteProject(outGivingModel, outGivingNodeProjects, userModel); - - outGivingModel.outGivingNodeProjectList(outGivingNodeProjects); - - return tuples; - } - - /** - * 删除已经删除过的项目 - * - * @param outGivingModel 分发项目 - * @param outGivingNodeProjects 新的节点项目 - * @param userModel 用户 - */ - private void deleteProject(OutGivingModel outGivingModel, List outGivingNodeProjects, UserModel userModel) { - Assert.state(CollUtil.size(outGivingNodeProjects) >= 2, "至少选择两个节点及以上"); - // 删除 - List deleteNodeProject = outGivingModel.getDelete(outGivingNodeProjects); - if (deleteNodeProject != null) { - JsonMessage jsonMessage; - // 删除实际的项目 - for (OutGivingNodeProject outGivingNodeProject1 : deleteNodeProject) { - NodeModel nodeModel = outGivingNodeProject1.getNodeData(true); - jsonMessage = deleteNodeProject(nodeModel, userModel, outGivingNodeProject1.getProjectId()); - Assert.state(jsonMessage.getCode() == HttpStatus.HTTP_OK, nodeModel.getName() + "节点失败:" + jsonMessage.getMsg()); - } - } - } - - private JsonMessage sendData(NodeModel nodeModel, UserModel userModel, JSONObject data, boolean save) { - if (save) { - data.remove("previewData"); - } - data.put("outGivingProject", true); - // 发起预检查数据 - return NodeForward.request(nodeModel, NodeUrl.Manage_SaveProject, userModel, data); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingWhitelistController.java b/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingWhitelistController.java deleted file mode 100644 index c9c243252c..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/outgiving/OutGivingWhitelistController.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.outgiving; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.RegexPool; -import cn.hutool.core.util.ReUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.model.data.ServerWhitelist; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.service.system.SystemParametersServer; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.lang.reflect.Field; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 节点白名单 - * - * @author jiangzeyin - * @date 2019/4/22 - */ -@Controller -@RequestMapping(value = "/outgiving") -@Feature(cls = ClassFeature.OUTGIVING) -public class OutGivingWhitelistController extends BaseServerController { - - private final SystemParametersServer systemParametersServer; - - public OutGivingWhitelistController(SystemParametersServer systemParametersServer) { - this.systemParametersServer = systemParametersServer; - } - - - /** - * get whiteList data - * 白名单数据接口 - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String whiteList() { - ServerWhitelist serverWhitelist = systemParametersServer.getConfigDefNewInstance(ServerWhitelist.ID, ServerWhitelist.class); - Field[] fields = ReflectUtil.getFields(ServerWhitelist.class); - Map map = new HashMap<>(8); - for (Field field : fields) { - Object value = ReflectUtil.getFieldValue(serverWhitelist, field); - if (value instanceof Collection) { - Collection fieldValue = (Collection) value; - map.put(field.getName(), AgentWhitelist.convertToLine(fieldValue)); - map.put(field.getName() + "Array", fieldValue); - } - } - return JsonMessage.getString(200, "ok", map); - } - - /** - * 保存节点白名单 - * - * @param outGiving 数据 - * @return json - */ - @RequestMapping(value = "whitelistDirectory_submit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - @SystemPermission - public String whitelistDirectorySubmit(String outGiving, String allowRemoteDownloadHost) { - List list = AgentWhitelist.parseToList(outGiving, true, "项目路径白名单不能为空"); - list = AgentWhitelist.covertToArray(list, "项目路径白名单不能位于Jpom目录下"); - - ServerWhitelist serverWhitelist = systemParametersServer.getConfigDefNewInstance(ServerWhitelist.ID, ServerWhitelist.class); - serverWhitelist.setOutGiving(list); - // - List allowRemoteDownloadHostList = AgentWhitelist.parseToList(allowRemoteDownloadHost, "运行远程下载的 host 不能配置为空"); - // - if (CollUtil.isNotEmpty(allowRemoteDownloadHostList)) { - for (String s : allowRemoteDownloadHostList) { - Assert.state(ReUtil.isMatch(RegexPool.URL_HTTP, s), "配置的远程地址不规范,请重新填写:" + s); - } - } - serverWhitelist.setAllowRemoteDownloadHost(allowRemoteDownloadHostList == null ? null : CollUtil.newHashSet(allowRemoteDownloadHostList)); - systemParametersServer.upsert(ServerWhitelist.ID, serverWhitelist, ServerWhitelist.ID); - - String resultData = AgentWhitelist.convertToLine(list); - return JsonMessage.getString(200, "保存成功", resultData); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/package-info.java b/modules/server/src/main/java/io/jpom/controller/package-info.java deleted file mode 100644 index 71c5dc0d96..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/controller/system/BackupInfoController.java b/modules/server/src/main/java/io/jpom/controller/system/BackupInfoController.java deleted file mode 100644 index 840a787ef4..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/BackupInfoController.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSON; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.BackupInfoModel; -import io.jpom.model.enums.BackupStatusEnum; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.BackupInfoService; -import io.jpom.system.db.DbConfig; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * 数据库备份 controller - * - * @author Hotstrip - * @date 2021-11-18 - */ -@RestController -@Feature(cls = ClassFeature.SYSTEM_BACKUP) -@SystemPermission -public class BackupInfoController extends BaseServerController { - - private final BackupInfoService backupInfoService; - - public BackupInfoController(BackupInfoService backupInfoService) { - this.backupInfoService = backupInfoService; - } - - /** - * 分页加载备份列表数据 - * - * @return json - */ - @PostMapping(value = "/system/backup/list") - @Feature(method = MethodFeature.LIST) - public Object loadBackupList() { - // 查询数据库 - PageResultDto pageResult = backupInfoService.listPage(getRequest()); - - return JsonMessage.getString(200, "获取成功", pageResult); - } - - /** - * 删除备份数据 - * - * @param id 备份 ID - * @return json - */ - @PostMapping(value = "/system/backup/delete") - @Feature(method = MethodFeature.DEL) - @SystemPermission(superUser = true) - public Object deleteBackup(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "数据 id 不能为空") String id) { - // 根据 id 查询备份信息 - BackupInfoModel backupInfoModel = backupInfoService.getByKey(id); - Objects.requireNonNull(backupInfoModel, "备份数据不存在"); - - // 删除对应的文件 - FileUtil.del(backupInfoModel.getFilePath()); - - // 删除备份信息 - backupInfoService.delByKey(id); - return JsonMessage.toJson(200, "删除成功"); - } - - /** - * 还原备份数据 - * 还原的时候不能异步了,只能等待备份还原成功或者失败 - * - * @param id 备份 ID - * @return json - */ - @PostMapping(value = "/system/backup/restore") - @Feature(method = MethodFeature.EXECUTE) - public Object restoreBackup(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "数据 id 不能为空") String id) { - // 根据 id 查询备份信息 - BackupInfoModel backupInfoModel = backupInfoService.getByKey(id); - Objects.requireNonNull(backupInfoModel, "备份数据不存在"); - - // 检查备份文件是否存在 - File file = new File(backupInfoModel.getFilePath()); - if (!FileUtil.exist(file)) { - return JsonMessage.toJson(400, "备份文件不存在"); - } - - // 还原备份文件 - boolean flag = backupInfoService.restoreWithSql(backupInfoModel.getFilePath()); - if (flag) { - // 还原备份数据成功之后需要修改当前备份信息的状态(因为备份的时候该备份信息状态是备份中) - backupInfoModel.setFileSize(FileUtil.size(file)); - backupInfoModel.setSha1Sum(SecureUtil.sha1(file)); - backupInfoModel.setStatus(BackupStatusEnum.SUCCESS.getCode()); - backupInfoService.update(backupInfoModel); - return JsonMessage.toJson(200, "还原备份数据成功"); - } - return JsonMessage.toJson(400, "还原备份数据失败"); - } - - /** - * 创建备份任务 - * - * @param map 参数 map.tableNameList 选中备份的表名称 - * @return json - */ - @PostMapping(value = "/system/backup/create") - @Feature(method = MethodFeature.EDIT) - public Object backup(@RequestBody Map map) { - List tableNameList = JSON.parseArray(JSON.toJSONString(map.get("tableNameList")), String.class); - backupInfoService.backupToSql(tableNameList); - return JsonMessage.toJson(200, "操作成功,请稍后刷新查看备份状态"); - } - - /** - * 导入备份数据 - * - * @return json - */ - @PostMapping(value = "/system/backup/upload") - @Feature(method = MethodFeature.UPLOAD) - @SystemPermission(superUser = true) - public Object uploadBackupFile() throws IOException { - MultipartFileBuilder multipartFileBuilder = createMultipart() - .addFieldName("file"); - // 备份类型 - //int backupType = Integer.parseInt(getParameter("backupType")); - // 存储目录 - File directory = FileUtil.file(DbConfig.getInstance().dbLocalPath(), Const.BACKUP_DIRECTORY_NAME); - - // 保存文件 - multipartFileBuilder.setSavePath(FileUtil.getAbsolutePath(directory)) - .setUseOriginalFilename(false); - String backupSqlPath = multipartFileBuilder.save(); - // 记录到数据库 - final File file = new File(backupSqlPath); - String sha1Sum = SecureUtil.sha1(file); - BackupInfoModel backupInfoModel = new BackupInfoModel(); - backupInfoModel.setSha1Sum(sha1Sum); - boolean exists = backupInfoService.exists(backupInfoModel); - if (exists) { - FileUtil.del(file); - return JsonMessage.getString(400, "导入的数据已经存在啦"); - } - -// backupInfoModel.setId(IdUtil.fastSimpleUUID()); - backupInfoModel.setName(file.getName()); - backupInfoModel.setBackupType(2); - backupInfoModel.setStatus(BackupStatusEnum.SUCCESS.getCode()); - backupInfoModel.setFileSize(FileUtil.size(file)); - - backupInfoModel.setSha1Sum(sha1Sum); - backupInfoModel.setFilePath(backupSqlPath); - backupInfoService.insert(backupInfoModel); - - return JsonMessage.toJson(200, "导入成功"); - } - - /** - * 下载备份数据 - * - * @param id 备份 ID - */ - @GetMapping(value = "/system/backup/download") - @Feature(method = MethodFeature.DOWNLOAD) - public void downloadBackup(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "数据 id 不能为空") String id) { - // 根据 id 查询备份信息 - BackupInfoModel backupInfoModel = backupInfoService.getByKey(id); - Objects.requireNonNull(backupInfoModel, "备份数据不存在"); - - // 检查备份文件是否存在 - File file = new File(backupInfoModel.getFilePath()); - if (!FileUtil.exist(file)) { - DefaultSystemLog.getLog().error("文件不存在,无法下载...backupId: {}", id); - return; - } - - // 下载文件 - ServletUtil.write(getResponse(), file); - } - - /** - * 读取数据库表名称列表 - * - * @return json - */ - @PostMapping(value = "/system/backup/table-name-list") - @Feature(method = MethodFeature.LIST) - public Object loadTableNameList() { - List tableNameList = backupInfoService.h2TableNameList(); - return JsonMessage.toJson(200, "获取成功", tableNameList); - } - -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/CacheManageController.java b/modules/server/src/main/java/io/jpom/controller/system/CacheManageController.java deleted file mode 100644 index d3bf666b3d..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/CacheManageController.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.build.BuildUtil; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.controller.LoginControl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.socket.ServiceFileTailWatcher; -import io.jpom.system.ConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.Map; - -/** - * 缓存管理 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@RestController -@RequestMapping(value = "system") -@Feature(cls = ClassFeature.SYSTEM_CACHE) -@SystemPermission -public class CacheManageController extends BaseServerController { - - /** - * get server's cache data - * 获取 Server 的缓存数据 - * - * @return json - * @author Hotstrip - */ - @PostMapping(value = "server-cache", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String serverCache() { - Map map = new HashMap<>(10); - String fileSize = FileUtil.readableFileSize(BuildUtil.tempFileCacheSize); - map.put("cacheFileSize", fileSize); - - int size = LoginControl.LFU_CACHE.size(); - map.put("ipSize", size); - int oneLineCount = ServiceFileTailWatcher.getOneLineCount(); - map.put("readFileOnLineCount", oneLineCount); - - - fileSize = FileUtil.readableFileSize(BuildUtil.buildCacheSize); - map.put("cacheBuildFileSize", fileSize); - - return JsonMessage.getString(200, "ok", map); - } - - /** - * 获取节点中的缓存 - * - * @return json - */ - @RequestMapping(value = "node_cache.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String nodeCache() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Cache).toString(); - } - - /** - * 清空缓存 - * - * @param type 类型 - * @return json - */ - @RequestMapping(value = "clearCache.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String clearCache(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "类型错误") String type) { - switch (type) { - case "serviceCacheFileSize": - boolean clean = FileUtil.clean(ConfigBean.getInstance().getTempPath()); - if (!clean) { - return JsonMessage.getString(504, "清空文件缓存失败"); - } - break; - case "serviceIpSize": - LoginControl.LFU_CACHE.clear(); - break; - default: - return NodeForward.request(getNode(), getRequest(), NodeUrl.ClearCache).toString(); - - } - return JsonMessage.getString(200, "清空成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/LogManageController.java b/modules/server/src/main/java/io/jpom/controller/system/LogManageController.java deleted file mode 100644 index 653432388b..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/LogManageController.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.socket.ServiceFileTailWatcher; -import io.jpom.system.WebAopLog; -import io.jpom.util.LayuiTreeUtil; -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.io.File; -import java.util.concurrent.TimeUnit; - -/** - * 系统日志管理 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@RestController -@RequestMapping(value = "system") -@Feature(cls = ClassFeature.SYSTEM_LOG) -@SystemPermission -public class LogManageController extends BaseServerController { - - - @RequestMapping(value = "log_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String logData(String nodeId) { - if (StrUtil.isNotEmpty(nodeId)) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.SystemLog).toString(); - } - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - JSONArray data = LayuiTreeUtil.getTreeData(webAopLog.getPropertyValue()); - return JsonMessage.getString(200, "", data); - } - - /** - * 删除 需要验证是否最后修改时间 - * - * @param nodeId 节点 - * @param path 路径 - * @return json - */ - @RequestMapping(value = "log_del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String logData(String nodeId, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "path错误") String path) { - if (StrUtil.isNotEmpty(nodeId)) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.DelSystemLog).toString(); - } - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - File file = FileUtil.file(webAopLog.getPropertyValue(), path); - // 判断修改时间 - long modified = file.lastModified(); - if (System.currentTimeMillis() - modified < TimeUnit.DAYS.toMillis(1)) { - return JsonMessage.getString(405, "不能删除当天的日志"); - } - if (FileUtil.del(file)) { - // 离线上一个日志 - ServiceFileTailWatcher.offlineFile(file); - return JsonMessage.getString(200, "删除成功"); - } - return JsonMessage.getString(500, "删除失败"); - } - - - @RequestMapping(value = "log_download", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DOWNLOAD) - public void logDownload(String nodeId, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "path错误") String path) { - if (StrUtil.isNotEmpty(nodeId)) { - NodeForward.requestDownload(getNode(), getRequest(), getResponse(), NodeUrl.DownloadSystemLog); - return; - } - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - File file = FileUtil.file(webAopLog.getPropertyValue(), path); - if (file.isFile()) { - ServletUtil.write(getResponse(), file); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/SystemConfigController.java b/modules/server/src/main/java/io/jpom/controller/system/SystemConfigController.java deleted file mode 100644 index d5374a4cf3..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/SystemConfigController.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.common.JpomManifest; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.SystemIpConfigModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.system.ExtConfigBean; -import org.springframework.boot.env.YamlPropertySourceLoader; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 系统配置 - * - * @author bwcx_jzy - * @date 2019/08/08 - */ -@RestController -@RequestMapping(value = "system") -@Feature(cls = ClassFeature.SYSTEM_CONFIG) -@SystemPermission -public class SystemConfigController extends BaseServerController { - - private final SystemParametersServer systemParametersServer; - - public SystemConfigController(SystemParametersServer systemParametersServer) { - this.systemParametersServer = systemParametersServer; - } - - /** - * get server's config or node's config - * 加载服务端或者节点端配置 - * - * @param nodeId 节点ID - * @return json - * @throws IOException io - * @author Hotstrip - */ - @RequestMapping(value = "config-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String configData(String nodeId) throws IOException { - String content; - if (StrUtil.isNotEmpty(nodeId)) { - JSONObject jsonObject = NodeForward.requestData(getNode(), NodeUrl.SystemGetConfig, getRequest(), JSONObject.class); - content = jsonObject.getString("content"); - } else { - content = IoUtil.read(ExtConfigBean.getResource().getInputStream(), CharsetUtil.CHARSET_UTF_8); - } - return JsonMessage.getString(200, "加载成功", content); - } - - @RequestMapping(value = "save_config.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - @SystemPermission(superUser = true) - public String saveConfig(String nodeId, String content, String restart) { - if (StrUtil.isNotEmpty(nodeId)) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.SystemSaveConfig).toString(); - } - Assert.hasText(content, "内容不能为空"); - try { - YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); - // @author hjk 前端编辑器允许使用tab键,并设定为2个空格,再转换为yml时要把tab键换成2个空格 - ByteArrayResource resource = new ByteArrayResource(content.replace("\t", " ").getBytes(StandardCharsets.UTF_8)); - yamlPropertySourceLoader.load("test", resource); - } catch (Exception e) { - DefaultSystemLog.getLog().warn("内容格式错误,请检查修正", e); - return JsonMessage.getString(500, "内容格式错误,请检查修正:" + e.getMessage()); - } - Assert.state(!JpomManifest.getInstance().isDebug(), "调试模式下不支持在线修改,请到resources目录下的bin目录修改extConfig.yml"); - - File resourceFile = ExtConfigBean.getResourceFile(); - FileUtil.writeString(content, resourceFile, CharsetUtil.CHARSET_UTF_8); - - boolean restartBool = Convert.toBool(restart, false); - if (restartBool) { - // 重启 - ThreadUtil.execute(() -> { - ThreadUtil.sleep(2000); - JpomApplication.restart(); - }); - return JsonMessage.getString(200, Const.UPGRADE_MSG); - } - return JsonMessage.getString(200, "修改成功"); - } - - - /** - * 加载服务端的 ip 白名单配置 - * - * @return json - */ - @RequestMapping(value = "ip-config-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(cls = ClassFeature.SYSTEM_CONFIG_IP, method = MethodFeature.LIST) - public String ipConfigData() { - SystemIpConfigModel config = systemParametersServer.getConfig(SystemIpConfigModel.ID, SystemIpConfigModel.class); - JSONObject jsonObject = new JSONObject(); - if (config != null) { - jsonObject.put("allowed", config.getAllowed()); - jsonObject.put("prohibited", config.getProhibited()); - } - //jsonObject.put("path", FileUtil.getAbsolutePath(systemIpConfigService.filePath())); - jsonObject.put("ip", getIp()); - return JsonMessage.getString(200, "加载成功", jsonObject); - } - - @RequestMapping(value = "save_ip_config.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(cls = ClassFeature.SYSTEM_CONFIG_IP, method = MethodFeature.EDIT) - public String saveIpConfig(String allowed, String prohibited) { - SystemIpConfigModel systemIpConfigModel = new SystemIpConfigModel(); - systemIpConfigModel.setAllowed(StrUtil.emptyToDefault(allowed, StrUtil.EMPTY)); - systemIpConfigModel.setProhibited(StrUtil.emptyToDefault(prohibited, StrUtil.EMPTY)); - systemParametersServer.upsert(SystemIpConfigModel.ID, systemIpConfigModel, SystemIpConfigModel.ID); - // - return JsonMessage.getString(200, "修改成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/SystemMailConfigController.java b/modules/server/src/main/java/io/jpom/controller/system/SystemMailConfigController.java deleted file mode 100644 index e8bd8c4d51..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/SystemMailConfigController.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.mail.MailAccount; -import cn.hutool.extra.mail.MailUtil; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.MailAccountModel; -import io.jpom.monitor.EmailUtil; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.system.SystemParametersServer; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import javax.mail.Session; -import javax.mail.Transport; - -/** - * 监控邮箱配置 - * - * @author bwcx_jzy - * @date 2019/7/16 - */ -@RestController -@RequestMapping(value = "system") -@Feature(cls = ClassFeature.SYSTEM_EMAIL) -@SystemPermission -public class SystemMailConfigController extends BaseServerController { - - @Resource - private SystemParametersServer systemParametersServer; - - /** - * load mail config data - * 加载邮件配置 - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "mail-config-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String mailConfigData() { - MailAccountModel item = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); - if (item != null) { - item.setPass(null); - } - return JsonMessage.getString(200, "success", item); - } - - @RequestMapping(value = "mailConfig_save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String listData(MailAccountModel mailAccountModel) { - Assert.notNull(mailAccountModel, "请填写信息,并检查是否填写合法"); - Assert.hasText(mailAccountModel.getHost(), "请填写host"); - Assert.hasText(mailAccountModel.getUser(), "请填写user"); - Assert.hasText(mailAccountModel.getFrom(), "请填写from"); - // 验证是否正确 - MailAccountModel item = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); - if (item != null) { - mailAccountModel.setPass(StrUtil.emptyToDefault(mailAccountModel.getPass(), item.getPass())); - } else { - Assert.hasText(mailAccountModel.getPass(), "请填写pass"); - } - try { - MailAccount account = EmailUtil.getAccount(mailAccountModel); - Session session = MailUtil.getSession(account, false); - Transport transport = session.getTransport("smtp"); - transport.connect(); - transport.close(); - } catch (Exception e) { - return JsonMessage.getString(406, "验证邮箱信息失败,请检查配置的邮箱信息。端口号、授权码等。" + e.getMessage()); - } - systemParametersServer.upsert(MailAccountModel.ID, mailAccountModel, MailAccountModel.ID); - // - EmailUtil.refreshConfig(); - return JsonMessage.getString(200, "保存成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/SystemUpdateController.java b/modules/server/src/main/java/io/jpom/controller/system/SystemUpdateController.java deleted file mode 100644 index 23e516b205..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/SystemUpdateController.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.common.*; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Objects; - -/** - * 在线升级 - * - * @author bwcx_jzy - * @date 2019/7/22 - */ -@RestController -@RequestMapping(value = "system") -@Feature(cls = ClassFeature.SYSTEM_UPGRADE) -@SystemPermission(superUser = true) -public class SystemUpdateController extends BaseServerController { - - @PostMapping(value = "info", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String info() { - NodeModel nodeModel = tryGetNode(); - if (nodeModel != null) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Info).toString(); - } - JpomManifest instance = JpomManifest.getInstance(); - RemoteVersion remoteVersion = RemoteVersion.cacheInfo(); - // - JSONObject jsonObject = new JSONObject(); - jsonObject.put("manifest", instance); - jsonObject.put("remoteVersion", remoteVersion); - return JsonMessage.getString(200, "", jsonObject); - } - - /** - * 更新日志 - * - * @return changelog md - */ - @PostMapping(value = "change_log", produces = MediaType.APPLICATION_JSON_VALUE) - public String changeLog() { - NodeModel nodeModel = tryGetNode(); - if (nodeModel != null) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.CHANGE_LOG).toString(); - } - // - URL resource = ResourceUtil.getResource("CHANGELOG.md"); - String log = StrUtil.EMPTY; - if (resource != null) { - InputStream stream = URLUtil.getStream(resource); - log = IoUtil.readUtf8(stream); - } - return JsonMessage.getString(200, "", log); - } - - @PostMapping(value = "uploadJar.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public String uploadJar() throws IOException { - NodeModel nodeModel = tryGetNode(); - if (nodeModel != null) { - return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.SystemUploadJar).toString(); - } - // - Objects.requireNonNull(JpomManifest.getScriptFile()); - MultipartFileBuilder multipartFileBuilder = createMultipart(); - String absolutePath = ServerConfigBean.getInstance().getUserTempPath().getAbsolutePath(); - multipartFileBuilder - .setFileExt("jar", "zip") - .addFieldName("file") - .setUseOriginalFilename(true) - .setSavePath(absolutePath); - String path = multipartFileBuilder.save(); - // 解析压缩包 - File file = JpomManifest.zipFileFind(path, Type.Server, absolutePath); - path = FileUtil.getAbsolutePath(file); - // 基础检查 - JsonMessage error = JpomManifest.checkJpomJar(path, Type.Server); - if (error.getCode() != HttpStatus.HTTP_OK) { - return error.toString(); - } - Tuple data = error.getData(); - String version = data.get(0); - JpomManifest.releaseJar(path, version); - // - JpomApplication.restart(); - return JsonMessage.getString(200, Const.UPGRADE_MSG); - } - - /** - * 检查是否存在新版本 - * - * @return json - * @see RemoteVersion - */ - @PostMapping(value = "check_version.json", produces = MediaType.APPLICATION_JSON_VALUE) - public String checkVersion() { - NodeModel nodeModel = tryGetNode(); - if (nodeModel != null) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.CHECK_VERSION).toString(); - } - RemoteVersion remoteVersion = RemoteVersion.loadRemoteInfo(); - return JsonMessage.getString(200, "", remoteVersion); - } - - /** - * 远程下载升级 - * - * @return json - * @see RemoteVersion - */ - @GetMapping(value = "remote_upgrade.json", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DOWNLOAD) - public String upgrade() throws IOException { - NodeModel nodeModel = tryGetNode(); - if (nodeModel != null) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.REMOTE_UPGRADE).toString(); - } - RemoteVersion.upgrade(ConfigBean.getInstance().getTempPath().getAbsolutePath()); - return JsonMessage.getString(200, Const.UPGRADE_MSG); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/WorkspaceController.java b/modules/server/src/main/java/io/jpom/controller/system/WorkspaceController.java deleted file mode 100644 index 4700878fca..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/WorkspaceController.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.common.BaseServerController; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.model.log.UserOperateLogV1; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.h2db.TableName; -import io.jpom.service.system.WorkspaceService; -import io.jpom.service.user.UserBindWorkspaceService; -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 java.util.List; -import java.util.Set; - -/** - * @author bwcx_jzy - * @since 2021/12/3 - */ -@RestController -@Feature(cls = ClassFeature.SYSTEM_WORKSPACE) -@RequestMapping(value = "/system/workspace/") -@SystemPermission -public class WorkspaceController extends BaseServerController { - - private final WorkspaceService workspaceService; - private final UserBindWorkspaceService userBindWorkspaceService; - - public WorkspaceController(WorkspaceService workspaceService, - UserBindWorkspaceService userBindWorkspaceService) { - this.workspaceService = workspaceService; - this.userBindWorkspaceService = userBindWorkspaceService; - } - - /** - * 编辑工作空间 - * - * @param name 工作空间名称 - * @param description 描述 - * @return json - */ - @PostMapping(value = "/edit", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String create(String id, @ValidatorItem String name, @ValidatorItem String description) { - this.checkInfo(id, name); - // - WorkspaceModel workspaceModel = new WorkspaceModel(); - workspaceModel.setName(name); - workspaceModel.setDescription(description); - if (StrUtil.isEmpty(id)) { - // 创建 - workspaceService.insert(workspaceModel); - } else { - workspaceModel.setId(id); - workspaceService.update(workspaceModel); - } - return JsonMessage.getString(200, "操作成功"); - } - - private void checkInfo(String id, String name) { - Entity entity = Entity.create(); - entity.set("name", name); - if (StrUtil.isNotEmpty(id)) { - entity.set("id", StrUtil.format(" <> {}", id)); - } - boolean exists = workspaceService.exists(entity); - Assert.state(!exists, "对应的工作空间名称已经存在啦"); - } - - /** - * 工作空间分页列表 - * - * @return json - */ - @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String list() { - PageResultDto listPage = workspaceService.listPage(getRequest()); - return JsonMessage.getString(200, "", listPage); - } - - /** - * 查询工作空间列表 - * - * @return json - */ - @GetMapping(value = "/list_all") - @Feature(method = MethodFeature.LIST) - public String listAll() { - List list = workspaceService.list(); - return JsonMessage.getString(200, "", list); - } - - /** - * 删除工作空间 - * - * @param id 工作空间 ID - * @return json - */ - @GetMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - @SystemPermission(superUser = true) - public Object delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "数据 id 不能为空") String id) { - // - Assert.state(!StrUtil.equals(id, WorkspaceModel.DEFAULT_ID), "不能删除默认工作空间"); - // 判断是否存在关联数据 - Set> classes = ClassUtil.scanPackage("io.jpom.model", BaseWorkspaceModel.class::isAssignableFrom); - StringBuilder autoDelete = new StringBuilder(StrUtil.EMPTY); - for (Class aClass : classes) { - TableName tableName = aClass.getAnnotation(TableName.class); - if (tableName == null) { - continue; - } - if (aClass == UserOperateLogV1.class) { - // 用户操作日志 - String sql = "delete from " + tableName.value() + " where workspaceId=?"; - int execute = workspaceService.execute(sql, id); - if (execute > 0) { - autoDelete.append(StrUtil.format(" 自动删除 {} 表中数据 {} 条数据", execute)); - } - continue; - } - String sql = "select count(1) as cnt from " + tableName.value() + " where workspaceId=?"; - List query = workspaceService.query(sql, id); - Entity first = CollUtil.getFirst(query); - if (first != null) { - Assert.notEmpty(first, "没有对应的用户信息"); - Integer cnt = first.getInt("cnt"); - Assert.state(cnt == null || cnt <= 0, "当前工作空间下还存在关联数据:" + tableName.name()); - } - } - // 判断用户绑定关系 - boolean workspace = userBindWorkspaceService.existsWorkspace(id); - Assert.state(!workspace, "当前工作空间下还绑定着用户信息"); - // 删除信息 - workspaceService.delByKey(id); - return JsonMessage.toJson(200, "删除成功 " + autoDelete); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/system/WorkspaceEnvVarController.java b/modules/server/src/main/java/io/jpom/controller/system/WorkspaceEnvVarController.java deleted file mode 100644 index 9371d55bc2..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/system/WorkspaceEnvVarController.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.system; - -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.WorkspaceEnvVarModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.system.WorkspaceEnvVarService; -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; - -/** - * @author bwcx_jzy - * @since 2021/12/10 - */ - -@RestController -@Feature(cls = ClassFeature.SYSTEM_WORKSPACE) -@RequestMapping(value = "/system/workspace_env/") -@SystemPermission -public class WorkspaceEnvVarController extends BaseServerController { - - private final WorkspaceEnvVarService workspaceEnvVarService; - - public WorkspaceEnvVarController(WorkspaceEnvVarService workspaceEnvVarService) { - this.workspaceEnvVarService = workspaceEnvVarService; - } - - /** - * 分页列表 - * - * @return json - */ - @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String list() { - PageResultDto listPage = workspaceEnvVarService.listPage(getRequest()); - return JsonMessage.getString(200, "", listPage); - } - - /** - * 编辑变量 - * - * @param name 变量名称 - * @param value 值 - * @param description 描述 - * @return json - */ - @PostMapping(value = "/edit", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String edit(String id, @ValidatorItem String name, @ValidatorItem String value, @ValidatorItem String description) { - String workspaceId = workspaceEnvVarService.getCheckUserWorkspace(getRequest()); - this.checkInfo(id, name, workspaceId); - // - WorkspaceEnvVarModel workspaceModel = new WorkspaceEnvVarModel(); - workspaceModel.setName(name); - workspaceModel.setValue(value); - workspaceModel.setDescription(description); - if (StrUtil.isEmpty(id)) { - // 创建 - workspaceEnvVarService.insert(workspaceModel); - } else { - workspaceModel.setId(id); - workspaceModel.setWorkspaceId(workspaceId); - workspaceEnvVarService.update(workspaceModel); - } - return JsonMessage.getString(200, "操作成功"); - } - - private void checkInfo(String id, String name, String workspaceId) { - Validator.validateGeneral(name, 1, 50, "变量名称 1-50 英文字母 、数字和下划线"); - // - Entity entity = Entity.create(); - entity.set("name", name); - entity.set("workspaceId", workspaceId); - if (StrUtil.isNotEmpty(id)) { - entity.set("id", StrUtil.format(" <> {}", id)); - } - boolean exists = workspaceEnvVarService.exists(entity); - Assert.state(!exists, "对应的变量名称已经存在啦"); - } - - - /** - * 删除变量 - * - * @param id 变量 ID - * @return json - */ - @GetMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "数据 id 不能为空") String id) { - // 删除信息 - workspaceEnvVarService.delByKey(id, getRequest()); - return JsonMessage.getString(200, "删除成功"); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/user/UserBasicInfoController.java b/modules/server/src/main/java/io/jpom/controller/user/UserBasicInfoController.java deleted file mode 100644 index 713bcb04e3..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/user/UserBasicInfoController.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.user; - -import cn.hutool.cache.impl.TimedCache; -import cn.hutool.core.lang.RegexPool; -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.common.BaseServerController; -import io.jpom.common.interceptor.LoginInterceptor; -import io.jpom.model.data.MailAccountModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.monitor.EmailUtil; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.service.system.WorkspaceService; -import io.jpom.service.user.UserBindWorkspaceService; -import io.jpom.service.user.UserService; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * @author bwcx_jzy - * @date 2019/8/10 - */ -@RestController -@RequestMapping(value = "/user") -public class UserBasicInfoController extends BaseServerController { - - private static final TimedCache CACHE = new TimedCache<>(TimeUnit.MINUTES.toMillis(30)); - - private final SystemParametersServer systemParametersServer; - private final UserBindWorkspaceService userBindWorkspaceService; - private final UserService userService; - private final WorkspaceService workspaceService; - - public UserBasicInfoController(SystemParametersServer systemParametersServer, - UserBindWorkspaceService userBindWorkspaceService, - UserService userService, - WorkspaceService workspaceService) { - this.systemParametersServer = systemParametersServer; - this.userBindWorkspaceService = userBindWorkspaceService; - this.userService = userService; - this.workspaceService = workspaceService; - } - - - /** - * get user basic info - * 获取管理员基本信息接口 - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "user-basic-info", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String getUserBasicInfo() { - UserModel userModel = getUser(); - userModel = userService.getByKey(userModel.getId(), false); - // return basic info - Map map = new HashMap<>(); - map.put("id", userModel.getId()); - map.put("name", userModel.getName()); - map.put("systemUser", userModel.isSystemUser()); - map.put("email", userModel.getEmail()); - map.put("dingDing", userModel.getDingDing()); - map.put("workWx", userModel.getWorkWx()); - map.put("md5Token", userModel.getPassword()); - return JsonMessage.getString(200, "success", map); - } - - @RequestMapping(value = "save_basicInfo.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String saveBasicInfo(@ValidatorItem(value = ValidatorRule.EMAIL, msg = "邮箱格式不正确") String email, - String dingDing, String workWx, String code) { - UserModel userModel = getUser(); - userModel = userService.getByKey(userModel.getId()); - // 判断是否一样 - if (!StrUtil.equals(email, userModel.getEmail())) { - Integer cacheCode = CACHE.get(email); - if (cacheCode == null || !Objects.equals(cacheCode.toString(), code)) { - return JsonMessage.getString(405, "请输入正确验证码"); - } - } - userModel.setEmail(email); - // - if (StrUtil.isNotEmpty(dingDing) && !Validator.isUrl(dingDing)) { - Validator.validateMatchRegex(RegexPool.URL_HTTP, dingDing, "请输入正确钉钉地址"); - } - userModel.setDingDing(dingDing); - if (StrUtil.isNotEmpty(workWx)) { - Validator.validateMatchRegex(RegexPool.URL_HTTP, workWx, "请输入正确企业微信地址"); - } - userModel.setWorkWx(workWx); - userService.update(userModel); - setSessionAttribute(LoginInterceptor.SESSION_NAME, userModel); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 发送邮箱验证 - * - * @param email 邮箱 - * @return msg - */ - @RequestMapping(value = "sendCode.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody - public String sendCode(@ValidatorItem(value = ValidatorRule.EMAIL, msg = "邮箱格式不正确") String email) { - MailAccountModel config = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); - Assert.notNull(config, "管理员还没有配置系统邮箱,请联系管理配置发件信息"); - int randomInt = RandomUtil.randomInt(1000, 9999); - try { - EmailUtil.send(email, "Jpom 验证码", "验证码是:" + randomInt); - } catch (Exception e) { - DefaultSystemLog.getLog().error("发送失败", e); - return JsonMessage.getString(500, "发送邮件失败:" + e.getMessage()); - } - CACHE.put(email, randomInt); - return JsonMessage.getString(200, "发送成功"); - } - - /** - * 查询用户自己的工作空间 - * - * @return msg - */ - @GetMapping(value = "my_workspace", produces = MediaType.APPLICATION_JSON_VALUE) - public String myWorkspace() { - UserModel user = getUser(); - List models = userBindWorkspaceService.listUserWorkspaceInfo(user); - Assert.notEmpty(models, "当前账号没有绑定任何工作空间,请联系管理员处理"); - return JsonMessage.getString(200, "", models); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/user/UserInfoController.java b/modules/server/src/main/java/io/jpom/controller/user/UserInfoController.java deleted file mode 100644 index d6598c0f8d..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/user/UserInfoController.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.user; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.validator.ValidatorConfig; -import cn.jiangzeyin.common.validator.ValidatorItem; -import cn.jiangzeyin.common.validator.ValidatorRule; -import io.jpom.common.BaseServerController; -import io.jpom.common.interceptor.LoginInterceptor; -import io.jpom.model.data.UserBindWorkspaceModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.user.UserBindWorkspaceService; -import io.jpom.service.user.UserService; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 用户管理 - * - * @author jiangzeyin - * @date 2018/9/28 - */ -@RestController -@RequestMapping(value = "/user") -public class UserInfoController extends BaseServerController { - - private final UserService userService; - private final UserBindWorkspaceService userBindWorkspaceService; - - public UserInfoController(UserService userService, - UserBindWorkspaceService userBindWorkspaceService) { - this.userService = userService; - this.userBindWorkspaceService = userBindWorkspaceService; - } - - /** - * 修改密码 - * - * @param oldPwd 旧密码 - * @param newPwd 新密码 - * @return json - */ - @RequestMapping(value = "updatePwd", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String updatePwd(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "密码不能为空") String oldPwd, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "密码不能为空") String newPwd) { - Assert.state(!StrUtil.equals(oldPwd, newPwd), "新旧密码一致"); - UserModel userName = getUser(); - Assert.state(!userName.isDemoUser(), "当前账户为演示账号,不支持修改密码"); - try { - UserModel userModel = userService.simpleLogin(userName.getId(), oldPwd); - Assert.notNull(userModel, "旧密码不正确!"); - Assert.state(ObjectUtil.defaultIfNull(userModel.getPwdErrorCount(), 0) <= 0, "当前账号被锁定中,不能修改密码"); - - userService.updatePwd(userName.getId(), newPwd); - // 如果修改成功,则销毁会话 - getSession().invalidate(); - return JsonMessage.getString(200, "修改密码成功!"); - } catch (Exception e) { - DefaultSystemLog.getLog().error(e.getMessage(), e); - return JsonMessage.getString(500, "系统异常:" + e.getMessage()); - } - } - - /** - * 修改用户昵称 - * - * @param name 新昵称 - * @return json - */ - @RequestMapping(value = "updateName", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String updateName(@ValidatorConfig(value = { - @ValidatorItem(value = ValidatorRule.NOT_BLANK, range = "2:10", msg = "昵称长度只能是2-10") - }) String name) { - UserModel userModel = getUser(); - userModel = userService.getByKey(userModel.getId()); - userModel.setName(name); - userService.update(userModel); - setSessionAttribute(LoginInterceptor.SESSION_NAME, userModel); - return JsonMessage.getString(200, "修改成功"); - } - - /** - * 查询用户工作空间 - * - * @return json - */ - @GetMapping(value = "workspace_list", produces = MediaType.APPLICATION_JSON_VALUE) - public String workspaceList(@ValidatorItem String userId) { - List workspaceModels = userBindWorkspaceService.listUserWorkspace(userId); - return JsonMessage.getString(200, "", workspaceModels); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/user/UserListController.java b/modules/server/src/main/java/io/jpom/controller/user/UserListController.java deleted file mode 100644 index a950c8b729..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/user/UserListController.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.user; - -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.jiangzeyin.common.JsonMessage; -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.UserModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.user.UserBindWorkspaceService; -import io.jpom.service.user.UserService; -import io.jpom.system.ServerExtConfigBean; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 用户列表 - * - * @author Administrator - */ -@RestController -@RequestMapping(value = "/user") -@Feature(cls = ClassFeature.USER) -@SystemPermission -public class UserListController extends BaseServerController { - - private final UserService userService; - private final UserBindWorkspaceService userBindWorkspaceService; - - public UserListController(UserService userService, - UserBindWorkspaceService userBindWorkspaceService) { - this.userService = userService; - this.userBindWorkspaceService = userBindWorkspaceService; - } - - /** - * 查询所有用户 - * - * @return json - */ - @RequestMapping(value = "get_user_list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getUserList() { - PageResultDto userModelPageResultDto = userService.listPage(getRequest()); - return JsonMessage.getString(200, "", userModelPageResultDto); - } - - /** - * 获取所有管理员信息 - * get all admin user list - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "get_user_list_all", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String getUserListAll() { - List list = userService.list(); - return JsonMessage.getString(200, "success", list); - } - - /** - * 编辑用户 - * - * @param type 操作类型 - * @return String - */ - @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String addUser(String type) { - // - boolean create = StrUtil.equals(type, "add"); - UserModel userModel = this.parseUser(create); - - if (create) { - userService.insert(userModel); - } else { - UserModel model = userService.getByKey(userModel.getId()); - Assert.notNull(model, "不存在对应的用户"); - boolean systemUser = userModel.isSystemUser(); - if (!systemUser) { - Assert.state(!model.isSuperSystemUser(), "不能取消超级管理员的权限"); - } - UserModel optUser = getUser(); - if (StrUtil.equals(model.getId(), optUser.getId())) { - Assert.state(model.isSystemUser() == optUser.isSystemUser(), "不能修改自己的管理员权限"); - } - if (model.isSuperSystemUser()) { - Assert.state(optUser.isSuperSystemUser(), "超级管理员才能修改超级管理员的信息"); - } - userService.update(userModel); - } - // - String workspace = getParameter("workspace"); - JSONArray jsonArray = JSONArray.parseArray(workspace); - List workspaceList = jsonArray.toJavaList(String.class); - userBindWorkspaceService.updateUserWorkspace(userModel.getId(), workspaceList); - return JsonMessage.getString(200, "操作成功"); - } - - private UserModel parseUser(boolean create) { - String id = getParameter("id"); - Assert.hasText(id, "登录名不能为空"); - int length = id.length(); - Assert.state(length <= 20 && length >= UserModel.USER_NAME_MIN_LEN, "登录名不能为空,并且长度必须" + UserModel.USER_NAME_MIN_LEN + "-20"); - - Assert.state(!StrUtil.equalsAnyIgnoreCase(id, UserModel.SYSTEM_OCCUPY_NAME, UserModel.SYSTEM_ADMIN), "当前登录名已经被系统占用"); - - Validator.validateGeneral(id, "登录名不能包含特殊字符"); - UserModel userModel = new UserModel(); - UserModel optUser = getUser(); - if (create) { - long size = userService.count(); - Assert.state(size <= ServerExtConfigBean.getInstance().userMaxCount, "当前用户个数超过系统上限"); - // 登录名重复 - boolean exists = userService.exists(new UserModel(id)); - Assert.state(!exists, "登录名已经存在"); - userModel.setParent(optUser.getId()); - } - userModel.setId(id); - // - String name = getParameter("name"); - Assert.hasText(name, "请输入账户昵称"); - int len = name.length(); - Assert.state(len <= 10 && len >= 2, "昵称长度只能是2-10"); - - userModel.setName(name); - - String password = getParameter("password"); - if (create || StrUtil.isNotEmpty(password)) { - Assert.hasText(password, "密码不能为空"); - // 修改用户 - Assert.state(create || optUser.isSystemUser(), "只有系统管理员才能重置用户密码"); - userModel.setSalt(userService.generateSalt()); - userModel.setPassword(SecureUtil.sha1(password + userModel.getSalt())); - } - int systemUser = getParameterInt("systemUser", 0); - userModel.setSystemUser(systemUser); - return userModel; - } - - /** - * 删除用户 - * - * @param id 用户id - * @return String - */ - @RequestMapping(value = "deleteUser", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.DEL) - public String deleteUser(String id) { - UserModel userName = getUser(); - Assert.state(!StrUtil.equals(userName.getId(), id), "不能删除自己"); - - UserModel userModel = userService.getByKey(id); - Assert.notNull(userModel, "非法访问"); - if (userModel.isSystemUser()) { - // 如果是系统管理员,判断个数 - Assert.state(userService.systemUserCount() > 1, "系统中的系统管理员账号数量必须存在一个以上"); - } - // 非系统管理员不支持删除演示账号 - Assert.state(!userModel.isDemoUser(), "演示账号不支持删除"); - userService.delByKey(id); - // 删除工作空间 - userBindWorkspaceService.deleteByUserId(id); - return JsonMessage.getString(200, "删除成功"); - } - - /** - * 解锁用户锁定状态 - * - * @param id id - * @return json - */ - @RequestMapping(value = "unlock", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public String unlock(String id) { - UserModel userModel = userService.getByKey(id); - Assert.notNull(userModel, "修改失败:-1"); - userModel.unLock(); - userService.update(userModel); - return JsonMessage.getString(200, "解锁成功"); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/controller/user/log/UserOptLogController.java b/modules/server/src/main/java/io/jpom/controller/user/log/UserOptLogController.java deleted file mode 100644 index 1df388518d..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/user/log/UserOptLogController.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.user.log; - -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.common.BaseServerController; -import io.jpom.model.PageResultDto; -import io.jpom.model.log.UserOperateLogV1; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.DbUserOperateLogService; -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; - -/** - * 用户操作日志 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -@RestController -@RequestMapping(value = "/user/log") -@Feature(cls = ClassFeature.USER_LOG) -public class UserOptLogController extends BaseServerController { - - private final DbUserOperateLogService dbUserOperateLogService; - - public UserOptLogController(DbUserOperateLogService dbUserOperateLogService) { - this.dbUserOperateLogService = dbUserOperateLogService; - } - - /** - * 展示用户列表 - * - * @return json - */ - @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) - public String listData() { - PageResultDto pageResult = dbUserOperateLogService.listPage(getRequest()); - return JsonMessage.getString(200, "获取成功", pageResult); - } -} diff --git a/modules/server/src/main/java/io/jpom/controller/user/role/UserRoleDynamicController.java b/modules/server/src/main/java/io/jpom/controller/user/role/UserRoleDynamicController.java deleted file mode 100644 index 410131955a..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/user/role/UserRoleDynamicController.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 码之科技工作室 -// * -// * 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 io.jpom.controller.user.role; -// -//import cn.hutool.core.collection.CollUtil; -//import cn.jiangzeyin.common.JsonMessage; -//import cn.jiangzeyin.common.spring.SpringUtil; -//import com.alibaba.fastjson.JSONArray; -//import com.alibaba.fastjson.JSONObject; -//import io.jpom.common.BaseServerController; -//import io.jpom.common.interceptor.OptLog; -//import io.jpom.model.data.RoleModel; -//import io.jpom.model.log.UserOperateLogV1; -//import io.jpom.permission.BaseDynamicService; -//import io.jpom.permission.DynamicData; -//import io.jpom.plugin.ClassFeature; -//import io.jpom.plugin.Feature; -//import io.jpom.plugin.MethodFeature; -//import io.jpom.service.user.RoleService; -//import org.springframework.http.MediaType; -//import org.springframework.stereotype.Controller; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestMethod; -//import org.springframework.web.bind.annotation.ResponseBody; -// -//import javax.annotation.Resource; -//import java.util.ArrayList; -//import java.util.HashMap; -//import java.util.List; -//import java.util.Map; -//import java.util.function.Predicate; -//import java.util.stream.Collectors; -// -///** -// * @author bwcx_jzy -// * @date 2019/8/15 -// */ -//@Controller -//@RequestMapping(value = "/user/role") -//@Feature(cls = ClassFeature.USER_ROLE) -//public class UserRoleDynamicController extends BaseServerController { -// -// @Resource -// private RoleService roleService; -// -//// @RequestMapping(value = "dynamicData.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE) -//// @Feature(method = MethodFeature.EDIT) -//// public String list() { -//// Map dynamicDataMap = DynamicData.getDynamicDataMap(); -//// setAttribute("dynamicDataMap", dynamicDataMap); -//// return "user/role/dynamicData"; -//// } -// -// /** -// * @return -// * @author Hotstrip -// * load role dynamic data -// * 加载角色的动态资源数据 -// */ -// @RequestMapping(value = "dynamic-list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// public String roleDynamicData() { -// Map dynamicDataMap = DynamicData.getDynamicDataMap(); -// List> list = new ArrayList<>(); -// dynamicDataMap.keySet().forEach(key -> { -// HashMap valueMap = new HashMap<>(); -// valueMap.put("id", key.name()); -// valueMap.put("name", key.getName()); -// if (key.getParent() != null) { -// valueMap.put("parent", key.getParent().name()); -// } -// list.add(valueMap); -// }); -// return JsonMessage.getString(200, "success", list); -// } -// -// @RequestMapping(value = "getDynamic.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.EDIT) -// public String getDynamic(String id, String dynamic) { -// ClassFeature classFeature = ClassFeature.valueOf(dynamic); -// JSONArray jsonArray = roleService.listDynamic(id, classFeature, null); -// return JsonMessage.getString(200, "", jsonArray); -// } -// -// @RequestMapping(value = "saveDynamic.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.EDIT) -// @OptLog(value = UserOperateLogV1.OptType.EditRole) -// public String saveDynamic(String id, String dynamic) { -// RoleModel item = roleService.getItem(id); -// if (item == null) { -// return JsonMessage.getString(404, "角色信息错误"); -// } -// // -// JSONObject jsonObject = JSONObject.parseObject(dynamic); -// Map> dynamicData1 = new HashMap<>(jsonObject.keySet().size()); -// // -// List root = DynamicData.getRoot(); -// for (ClassFeature classFeature : root) { -// JSONArray value = jsonObject.getJSONArray(classFeature.name()); -// if (value == null || value.isEmpty()) { -// continue; -// } -// DynamicData dynamicData = DynamicData.getDynamicData(classFeature); -// if (dynamicData == null) { -// return JsonMessage.getString(404, classFeature.getName() + "没有配置对应动态数据"); -// } -// Class baseOperService = dynamicData.getBaseOperService(); -// BaseDynamicService bean = SpringUtil.getBean(baseOperService); -// List list = bean.parserValue(classFeature, value); -// if (CollUtil.isEmpty(list)) { -// continue; -// } -// if (classFeature.getParent() != null) { -// list = list.stream().filter(treeLevel -> CollUtil.isNotEmpty(treeLevel.getChildren())).collect(Collectors.toList()); -// } -// dynamicData1.put(classFeature, list); -// } -// item.setDynamicData2(dynamicData1); -// roleService.updateItem(item); -// return JsonMessage.getString(200, "保存成功"); -// } -//} diff --git a/modules/server/src/main/java/io/jpom/controller/user/role/UserRoleListController.java b/modules/server/src/main/java/io/jpom/controller/user/role/UserRoleListController.java deleted file mode 100644 index 4823880d22..0000000000 --- a/modules/server/src/main/java/io/jpom/controller/user/role/UserRoleListController.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 码之科技工作室 -// * -// * 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 io.jpom.controller.user.role; -// -//import cn.hutool.core.util.BooleanUtil; -//import cn.hutool.core.util.IdUtil; -//import cn.hutool.core.util.StrUtil; -//import cn.jiangzeyin.common.JsonMessage; -//import cn.jiangzeyin.common.validator.ValidatorItem; -//import cn.jiangzeyin.common.validator.ValidatorRule; -//import com.alibaba.fastjson.JSONArray; -//import com.alibaba.fastjson.JSONObject; -//import io.jpom.common.BaseServerController; -//import io.jpom.common.interceptor.OptLog; -//import io.jpom.model.data.RoleModel; -//import io.jpom.model.data.UserModel; -//import io.jpom.model.log.UserOperateLogV1; -//import io.jpom.permission.CacheControllerFeature; -//import io.jpom.plugin.ClassFeature; -//import io.jpom.plugin.Feature; -//import io.jpom.plugin.MethodFeature; -//import io.jpom.service.user.RoleService; -//import io.jpom.service.user.UserService; -//import org.springframework.http.MediaType; -//import org.springframework.stereotype.Controller; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestMethod; -//import org.springframework.web.bind.annotation.ResponseBody; -// -//import javax.annotation.Resource; -//import java.util.*; -// -///** -// * 用户权限基本管理 -// * -// * @author bwcx_jzy -// * @date 2019/8/15 -// */ -//@Controller -//@RequestMapping(value = "/user/role") -//@Feature(cls = ClassFeature.USER_ROLE) -//public class UserRoleListController extends BaseServerController { -// -// @Resource -// private RoleService roleService; -// private final UserService userService; -// -// public UserRoleListController(UserService userService) { -// this.userService = userService; -// } -// -//// @RequestMapping(value = "list.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE) -//// @Feature(method = MethodFeature.LIST) -//// public String list() { -//// return "user/role/list"; -//// } -// -//// @RequestMapping(value = "edit.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE) -//// @Feature(method = MethodFeature.EDIT) -//// public String edit(String id) { -//// if (StrUtil.isNotEmpty(id)) { -//// RoleModel item = roleService.getItem(id); -//// setAttribute("item", item); -//// } -//// return "user/role/edit"; -//// } -// -// -// /** -// * 查询所有用户 -// * -// * @return json -// */ -// @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.LIST) -// public String listData() { -// List list = roleService.list(); -// if (list != null) { -// // 统计用户角色信息 -// List userList = userService.list(); -// Map roleCount = new HashMap<>(list.size()); -// if (userList != null) { -// userList.forEach(userModel -> { -// Set roles = userModel.getRoles(); -// if (roles == null) { -// return; -// } -// roles.forEach(s -> { -// Integer integer = roleCount.computeIfAbsent(s, s1 -> 0); -// roleCount.put(s, integer + 1); -// }); -// }); -// } -// list.forEach(roleModel -> { -// Integer integer = roleCount.get(roleModel.getId()); -// if (integer == null) { -// integer = 0; -// } -// roleModel.setBindCount(integer); -// }); -// } -// return JsonMessage.getString(200, "", list); -// } -// -// @RequestMapping(value = "getFeature.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.EDIT) -// public String getFeature(String id) { -// // -// RoleModel item = roleService.getItem(id); -// -// Map> featureMap = CacheControllerFeature.getFeatureMap(); -// Set>> entries = featureMap.entrySet(); -// JSONArray jsonArray = new JSONArray(); -// entries.forEach(classFeatureSetEntry -> { -// ClassFeature classFeature = classFeatureSetEntry.getKey(); -// JSONObject jsonObject = new JSONObject(); -// jsonObject.put("title", classFeature.getName()); -// jsonObject.put("id", classFeature.name()); -// Set value = classFeatureSetEntry.getValue(); -// JSONArray children = new JSONArray(); -// value.forEach(methodFeature -> { -// JSONObject cJson = new JSONObject(); -// cJson.put("title", methodFeature.getName()); -// cJson.put("id", classFeature.name() + "_" + methodFeature.name()); -// // -// if (item != null) { -// List methodFeature1 = item.getMethodFeature(classFeature); -// if (methodFeature1 != null && methodFeature1.contains(methodFeature)) { -// cJson.put("checked", true); -// } -// } -// children.add(cJson); -// }); -// jsonObject.put("children", children); -// jsonObject.put("spread", true); -// jsonArray.add(jsonObject); -// }); -// return JsonMessage.getString(200, "", jsonArray); -// } -// -// @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.EDIT) -// @OptLog(value = UserOperateLogV1.OptType.EditRole) -// public String save(String id, -// @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "请输入角色名称") String name, -// @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "请输入选择权限") String feature, -// String canAdd) { -// JSONArray jsonArray = JSONArray.parseArray(feature); -// RoleModel item = roleService.getItem(id); -// if (item == null) { -// item = new RoleModel(); -// item.setId(IdUtil.fastSimpleUUID()); -// } -// item.setName(name); -// List roleFeatures = new ArrayList<>(); -// jsonArray.forEach(o -> { -// JSONObject jsonObject = (JSONObject) o; -// JSONArray children = jsonObject.getJSONArray("children"); -// if (children == null || children.isEmpty()) { -// return; -// } -// String id1 = jsonObject.getString("id"); -// ClassFeature classFeature = ClassFeature.valueOf(id1); -// RoleModel.RoleFeature roleFeature = new RoleModel.RoleFeature(); -// roleFeature.setFeature(classFeature); -// roleFeatures.add(roleFeature); -// // -// List methodFeatures = new ArrayList<>(); -// children.forEach(o1 -> { -// JSONObject childrenItem = (JSONObject) o1; -// String id11 = childrenItem.getString("id"); -// id11 = id11.substring(id1.length() + 1); -// MethodFeature methodFeature = MethodFeature.valueOf(id11); -// methodFeatures.add(methodFeature); -// }); -// roleFeature.setMethodFeatures(methodFeatures); -// }); -// item.setCanAdd(BooleanUtil.toBoolean(canAdd)); -// item.setFeatures(roleFeatures); -// // -// if (StrUtil.isNotEmpty(id)) { -// roleService.updateItem(item); -// } else { -// roleService.addItem(item); -// } -// return JsonMessage.getString(200, "操作成功"); -// } -// -// @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) -// @ResponseBody -// @Feature(method = MethodFeature.DEL) -// @OptLog(value = UserOperateLogV1.OptType.DelRole) -// public String del(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id) { -// List userList = userService.list(); -// if (userList != null) { -// for (UserModel userModel : userList) { -// Set roles = userModel.getRoles(); -// if (roles == null) { -// continue; -// } -// if (roles.contains(id)) { -// return JsonMessage.getString(100, "当前角色存在关联用户不能删除"); -// } -// } -// } -// roleService.deleteItem(id); -// return JsonMessage.getString(200, "删除成功"); -// } -//} diff --git a/modules/server/src/main/java/io/jpom/model/BaseDbModel.java b/modules/server/src/main/java/io/jpom/model/BaseDbModel.java deleted file mode 100644 index dba9cd848c..0000000000 --- a/modules/server/src/main/java/io/jpom/model/BaseDbModel.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -import cn.hutool.core.date.SystemClock; - -/** - * 数据基础实体 - * - * @author jzy - * @since 2021-08-13 - */ -public abstract class BaseDbModel extends BaseJsonModel { - - /** - * 主键 - */ - private String id; - - /** - * 数据创建时间 - * - * @see SystemClock#now() - */ - private Long createTimeMillis; - - /** - * 数据修改时间 - */ - private Long modifyTimeMillis; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public Long getCreateTimeMillis() { - return createTimeMillis; - } - - public void setCreateTimeMillis(Long createTimeMillis) { - this.createTimeMillis = createTimeMillis; - } - - public Long getModifyTimeMillis() { - return modifyTimeMillis; - } - - public void setModifyTimeMillis(Long modifyTimeMillis) { - this.modifyTimeMillis = modifyTimeMillis; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/BaseNodeModel.java b/modules/server/src/main/java/io/jpom/model/BaseNodeModel.java deleted file mode 100644 index 37405322f4..0000000000 --- a/modules/server/src/main/java/io/jpom/model/BaseNodeModel.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * 节点 数据 - * - * @author bwcx_jzy - * @since 2021/12/05 - */ -public abstract class BaseNodeModel extends BaseWorkspaceModel { - - /** - * 节点Id - * - * @see io.jpom.model.data.NodeModel - */ - private String nodeId; - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/BaseStrikeDbModel.java b/modules/server/src/main/java/io/jpom/model/BaseStrikeDbModel.java deleted file mode 100644 index dec3256571..0000000000 --- a/modules/server/src/main/java/io/jpom/model/BaseStrikeDbModel.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * 带逻辑删除 数据表实体 - * - * @author bwcx_jzy - * @since 2021/12/2 - */ -public abstract class BaseStrikeDbModel extends BaseUserModifyDbModel { - - /** - * 逻辑删除 1 删除 0 未删除 - */ - private Integer strike; - - public Integer getStrike() { - return strike; - } - - public void setStrike(Integer strike) { - this.strike = strike; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/BaseUserModifyDbModel.java b/modules/server/src/main/java/io/jpom/model/BaseUserModifyDbModel.java deleted file mode 100644 index 09e3642ada..0000000000 --- a/modules/server/src/main/java/io/jpom/model/BaseUserModifyDbModel.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * 带最后修改人字段 数据表实体 - * - * @author bwcx_jzy - * @since 2021/8/24 - */ -public abstract class BaseUserModifyDbModel extends BaseDbModel { - /** - * 修改人 - */ - private String modifyUser; - - public String getModifyUser() { - return modifyUser; - } - - public void setModifyUser(String modifyUser) { - this.modifyUser = modifyUser; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/BaseWorkspaceModel.java b/modules/server/src/main/java/io/jpom/model/BaseWorkspaceModel.java deleted file mode 100644 index 6a401876fe..0000000000 --- a/modules/server/src/main/java/io/jpom/model/BaseWorkspaceModel.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -/** - * 工作空间 数据 - * - * @author bwcx_jzy - * @since 2021/12/04 - */ -public abstract class BaseWorkspaceModel extends BaseStrikeDbModel { - - /** - * 工作空间ID - * - * @see io.jpom.model.data.WorkspaceModel - * @see io.jpom.common.Const#WORKSPACEID_REQ_HEADER - */ - private String workspaceId; - - public String getWorkspaceId() { - return workspaceId; - } - - public void setWorkspaceId(String workspaceId) { - this.workspaceId = workspaceId; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/Cycle.java b/modules/server/src/main/java/io/jpom/model/Cycle.java deleted file mode 100644 index b89a3157f6..0000000000 --- a/modules/server/src/main/java/io/jpom/model/Cycle.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -import cn.hutool.cron.pattern.CronPattern; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * 周期 - * - * @author bwcx_jzy - * @date 2019/9/16 - */ -public enum Cycle implements BaseEnum { - /** - * 监控周期,code 代表周期时间,单位:分钟、秒 - */ - seconds30(-30, "30秒"), - none(0, "不开启"), - one(1, "1分钟"), - five(5, "5分钟"), - ten(10, "10分钟"), - thirty(30, "30分钟"); - - final int code; - final String desc; - CronPattern cronPattern; - long millis; - - Cycle(int code, String desc) { - this.code = code; - this.desc = desc; - if (code > 0) { - this.cronPattern = new CronPattern(String.format("0 0/%s * * * ?", code)); - this.millis = TimeUnit.MINUTES.toMillis(code); - } else if (code == 0) { - // - - } else { - code = -code; - this.cronPattern = new CronPattern(String.format("0/%s * * * * ?", code)); - this.millis = TimeUnit.SECONDS.toMillis(code); - } - } - - public long getMillis() { - return millis; - } - - public CronPattern getCronPattern() { - return cronPattern; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - - public static JSONArray getAllJSONArray() { - //监控周期 - JSONArray jsonArray = BaseEnum.toJSONArray(Cycle.class); - jsonArray = jsonArray.stream().filter(o -> { - JSONObject jsonObject = (JSONObject) o; - int code = jsonObject.getIntValue("code"); - return code != none.getCode(); - }).collect(Collectors.toCollection(JSONArray::new)); - try { - JSONObject jsonObject = BaseEnum.toJSONObject(Cycle.none); - jsonArray.add(0, jsonObject); - } catch (InvocationTargetException | IllegalAccessException ignored) { - } - return jsonArray; - } - - public static JSONArray getJSONArray() { - //监控周期 - JSONArray cycleArray = getAllJSONArray(); - return cycleArray.stream().filter(o -> { - JSONObject jsonObject = (JSONObject) o; - int code = jsonObject.getIntValue("code"); - return code > none.getCode(); - }).collect(Collectors.toCollection(JSONArray::new)); - } -} diff --git a/modules/server/src/main/java/io/jpom/model/PageResultDto.java b/modules/server/src/main/java/io/jpom/model/PageResultDto.java deleted file mode 100644 index 7edd8c2678..0000000000 --- a/modules/server/src/main/java/io/jpom/model/PageResultDto.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.PageUtil; -import cn.hutool.db.PageResult; - -import java.io.Serializable; -import java.util.List; - -/** - * 分页查询结果对象 - * - * @author bwcx_jzy - * @since 2021/12/3 - */ -public class PageResultDto implements Serializable { - - public static final PageResultDto EMPTY = new PageResultDto<>(1, 10, 0); - - /** - * 结果 - */ - private List result; - /** - * 页码 - */ - private Integer page; - /** - * 每页结果数 - */ - private Integer pageSize; - /** - * 总页数 - */ - private Integer totalPage; - /** - * 总数 - */ - private Integer total; - - public PageResultDto(PageResult pageResult) { - this.setPage(pageResult.getPage()); - this.setPageSize(pageResult.getPageSize()); - this.setTotalPage(pageResult.getTotalPage()); - this.setTotal(pageResult.getTotal()); - } - - public PageResultDto(PageResultDto pageResult) { - this.setPage(pageResult.getPage()); - this.setPageSize(pageResult.getPageSize()); - this.setTotalPage(pageResult.getTotalPage()); - this.setTotal(pageResult.getTotal()); - } - - public PageResultDto(int page, int pageSize, int total) { - this.setPage(page); - this.setPageSize(pageSize); - this.setTotalPage(PageUtil.totalPage(total, pageSize)); - this.setTotal(total); - } - - public List getResult() { - return result; - } - - public void setResult(List result) { - this.result = result; - } - - public Integer getPage() { - return page; - } - - public void setPage(Integer page) { - this.page = page; - } - - public Integer getPageSize() { - return pageSize; - } - - public void setPageSize(Integer pageSize) { - this.pageSize = pageSize; - } - - public Integer getTotalPage() { - return totalPage; - } - - public void setTotalPage(Integer totalPage) { - this.totalPage = totalPage; - } - - public Integer getTotal() { - return total; - } - - public void setTotal(Integer total) { - this.total = total; - } - - public boolean isEmpty() { - return CollUtil.isEmpty(getResult()); - } - - public T get(int index) { - return CollUtil.get(getResult(), index); - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/BackupInfoModel.java b/modules/server/src/main/java/io/jpom/model/data/BackupInfoModel.java deleted file mode 100644 index 0a6e2d753f..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/BackupInfoModel.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseDbModel; -import io.jpom.service.h2db.TableName; - -/** - * @author Hotstrip - * @since 2021-11-18 - * Backup info with H2 database - */ -@TableName(value = "BACKUP_INFO", name = "数据备份") -public class BackupInfoModel extends BaseDbModel { - - /** - * 备份名称 - */ - private String name; - /** - * 文件地址 - */ - private String filePath; - /** - * 备份类型{0: 全量, 1: 部分, 2: 导入, 3 自动} - */ - private Integer backupType; - /** - * 文件大小 - */ - private Long fileSize; - /** - * SHA1SUM - */ - private String sha1Sum; - - /** - * 状态{0: 默认; 1: 成功; 2: 失败} - */ - private Integer status; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public Integer getBackupType() { - return backupType; - } - - public void setBackupType(Integer backupType) { - this.backupType = backupType; - } - - public Long getFileSize() { - return fileSize; - } - - public void setFileSize(Long fileSize) { - this.fileSize = fileSize; - } - - public String getSha1Sum() { - return sha1Sum; - } - - public void setSha1Sum(String sha1Sum) { - this.sha1Sum = sha1Sum; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/BuildInfoModel.java b/modules/server/src/main/java/io/jpom/model/data/BuildInfoModel.java deleted file mode 100644 index 368101ea77..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/BuildInfoModel.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; - -/** - * @author Hotstrip - * new BuildModel class, for replace old BuildModel - */ -@TableName(value = "BUILD_INFO",name = "构建信息") -public class BuildInfoModel extends BaseWorkspaceModel { - - /** - * 仓库 ID - */ - private String repositoryId; - /** - * 名称 - */ - private String name; - /** - * 构建 ID - */ - private Integer buildId; - /** - * 分组名称 - */ - @Deprecated - private String group; - /** - * 分支 - */ - private String branchName; - /** - * 标签 - */ - private String branchTagName; - /** - * 构建命令 - */ - private String script; - /** - * 构建产物目录 - */ - private String resultDirFile; - /** - * 发布方法{0: 不发布, 1: 节点分发, 2: 分发项目, 3: SSH} - */ - private Integer releaseMethod; - /** - * 发布方法执行数据关联字段 - */ - private String releaseMethodDataId; - /** - * 状态 - */ - private Integer status; - /** - * 触发器token - */ - private String triggerToken; - /** - * 额外信息,JSON 字符串格式 - */ - private String extraData; - - - private String webhook; - - public String getRepositoryId() { - return repositoryId; - } - - public void setRepositoryId(String repositoryId) { - this.repositoryId = repositoryId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getBuildId() { - return buildId; - } - - public void setBuildId(Integer buildId) { - this.buildId = buildId; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getWebhook() { - return webhook; - } - - public void setWebhook(String webhook) { - this.webhook = webhook; - } - - public String getBranchName() { - return branchName; - } - - public void setBranchName(String branchName) { - this.branchName = branchName; - } - - public String getScript() { - return script; - } - - public void setScript(String script) { - this.script = script; - } - - public String getResultDirFile() { - return resultDirFile; - } - - public void setResultDirFile(String resultDirFile) { - this.resultDirFile = resultDirFile; - } - - public Integer getReleaseMethod() { - return releaseMethod; - } - - public void setReleaseMethod(Integer releaseMethod) { - this.releaseMethod = releaseMethod; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public String getTriggerToken() { - return triggerToken; - } - - public void setTriggerToken(String triggerToken) { - this.triggerToken = triggerToken; - } - - public String getExtraData() { - return extraData; - } - - public void setExtraData(String extraData) { - this.extraData = extraData; - } - - public String getReleaseMethodDataId() { - return releaseMethodDataId; - } - - public void setReleaseMethodDataId(String releaseMethodDataId) { - this.releaseMethodDataId = releaseMethodDataId; - } - - public static String getBuildIdStr(int buildId) { - return String.format("#%s", buildId); - } - - public String getBranchTagName() { - return branchTagName; - } - - public void setBranchTagName(String branchTagName) { - this.branchTagName = branchTagName; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/BuildModel.java b/modules/server/src/main/java/io/jpom/model/data/BuildModel.java deleted file mode 100644 index faa1afa8bc..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/BuildModel.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; -// -//import io.jpom.build.BaseBuildModule; -// -///** -// * 在线构建 -// * -// * @author bwcx_jzy -// * @date 2019/7/10 -// **/ -//@Deprecated -//public class BuildModel extends BaseBuildModule { -// -// /** -// * 仓库类型 -// */ -// private int repoType; -// /** -// * 仓库地址 git 或者 svn -// */ -// private String gitUrl; -// /** -// * 拉取的分支名称 -// */ -// private String branchName; -// /** -// * 账号 -// */ -// private String userName; -// /** -// * 密码 -// */ -// private String password; -// /** -// * 修改人 -// */ -// private String modifyUser; -// /** -// * 修改时间 -// */ -// private String modifyTime; -// /** -// * 构建状态 -// */ -// private int status; -// /** -// * 构建命令 -// */ -// private String script; -// /** -// * 构建id -// */ -// private int buildId; -// /** -// * 触发器token -// */ -// private String triggerToken; -// -// /** -// * 分组 -// */ -// private String group; -// -// public String getTriggerToken() { -// return triggerToken; -// } -// -// public void setTriggerToken(String triggerToken) { -// this.triggerToken = triggerToken; -// } -// -// public int getBuildId() { -// return buildId; -// } -// -// public String getBuildIdStr() { -// return getBuildIdStr(buildId); -// } -// -// public static String getBuildIdStr(int buildId) { -// return String.format("#%s", buildId); -// } -// -// public void setBuildId(int buildId) { -// this.buildId = buildId; -// } -// -// public String getScript() { -// return script; -// } -// -// public void setScript(String script) { -// this.script = script; -// } -// -// public int getStatus() { -// return status; -// } -// -// public void setStatus(int status) { -// this.status = status; -// } -// -// public String getModifyUser() { -// return modifyUser; -// } -// -// public void setModifyUser(String modifyUser) { -// this.modifyUser = modifyUser; -// } -// -// public String getModifyTime() { -// return modifyTime; -// } -// -// public void setModifyTime(String modifyTime) { -// this.modifyTime = modifyTime; -// } -// -// public String getGitUrl() { -// return gitUrl; -// } -// -// public void setGitUrl(String gitUrl) { -// this.gitUrl = gitUrl; -// } -// -// public String getBranchName() { -// return branchName; -// } -// -// public void setBranchName(String branchName) { -// this.branchName = branchName; -// } -// -// public String getUserName() { -// return userName; -// } -// -// public void setUserName(String userName) { -// this.userName = userName; -// } -// -// public String getPassword() { -// return password; -// } -// -// public void setPassword(String password) { -// this.password = password; -// } -// -// public int getRepoType() { -// return repoType; -// } -// -// public void setRepoType(int repoType) { -// this.repoType = repoType; -// } -// -// public String getGroup() { -// return group; -// } -// -// public void setGroup(String group) { -// this.group = group; -// } -//} diff --git a/modules/server/src/main/java/io/jpom/model/data/MailAccountModel.java b/modules/server/src/main/java/io/jpom/model/data/MailAccountModel.java deleted file mode 100644 index 3432214955..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/MailAccountModel.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseJsonModel; - -/** - * 系统邮箱配置 - * - * @author bwcx_jzy - * @date 2019/7/16 - **/ -public class MailAccountModel extends BaseJsonModel { - - public static final String ID = "MAIL_CONFIG"; - - /** - * SMTP服务器域名 - */ - private String host; - /** - * SMTP服务端口 - */ - private Integer port; - /** - * 用户名 - */ - private String user; - /** - * 密码 - */ - private String pass; - /** - * 发送方,遵循RFC-822标准 - */ - private String from; - /** - * 使用 SSL安全连接 - */ - private Boolean sslEnable; - /** - * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 - */ - private Integer socketFactoryPort; - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getPass() { - return pass; - } - - public void setPass(String pass) { - this.pass = pass; - } - - public String getFrom() { - return from; - } - - public void setFrom(String from) { - this.from = from; - } - - public Boolean getSslEnable() { - return sslEnable; - } - - public void setSslEnable(Boolean sslEnable) { - this.sslEnable = sslEnable; - } - - public Integer getSocketFactoryPort() { - return socketFactoryPort; - } - - public void setSocketFactoryPort(Integer socketFactoryPort) { - this.socketFactoryPort = socketFactoryPort; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/MonitorModel.java b/modules/server/src/main/java/io/jpom/model/data/MonitorModel.java deleted file mode 100644 index 418f76d478..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/MonitorModel.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import com.alibaba.fastjson.JSON; -import io.jpom.model.BaseEnum; -import io.jpom.model.BaseJsonModel; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; -import io.jpom.util.StringUtil; - -import java.util.List; - -/** - * 监控管理实体 - * - * @author Arno - */ -@TableName(value = "MONITOR_INFO", name = "监控信息") -public class MonitorModel extends BaseWorkspaceModel { - private String name; - /** - * 监控的项目 - */ - private String projects; - /** - * 报警联系人 - */ - private String notifyUser; - /** - * 异常后是否自动重启 - */ - private Boolean autoRestart; - /** - * 监控周期 - */ - private Integer cycle; - - /** - * 监控开启状态 - */ - private Boolean status; - /** - * 报警状态 - */ - private Boolean alarm; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - - public Integer getCycle() { - return cycle; - } - - public void setCycle(Integer cycle) { - this.cycle = cycle; - } - - public Boolean getAutoRestart() { - return autoRestart; - } - - public boolean autoRestart() { - return autoRestart != null && autoRestart; - } - - public void setAutoRestart(Boolean autoRestart) { - this.autoRestart = autoRestart; - } - - public Boolean getStatus() { - return status; - } - - - public boolean status() { - return status != null && status; - } - - public void setStatus(Boolean status) { - this.status = status; - } - - public Boolean getAlarm() { - return alarm; - } - - public void setAlarm(Boolean alarm) { - this.alarm = alarm; - } - - public List projects() { - return StringUtil.jsonConvertArray(projects, NodeProject.class); - } - - - public String getProjects() { - List projects = projects(); - return projects == null ? null : JSON.toJSONString(projects); - } - - public void setProjects(String projects) { - this.projects = projects; - } - - public void projects(List projects) { - if (projects == null) { - this.projects = null; - } else { - this.projects = JSON.toJSONString(projects); - } - } - - public List notifyUser() { - return StringUtil.jsonConvertArray(notifyUser, String.class); - } - - public String getNotifyUser() { - List object = notifyUser(); - return object == null ? null : JSON.toJSONString(object); - } - - public void setNotifyUser(String notifyUser) { - this.notifyUser = notifyUser; - } - - public void notifyUser(List notifyUser) { - if (notifyUser == null) { - this.notifyUser = null; - } else { - this.notifyUser = JSON.toJSONString(notifyUser); - } - } - - public boolean checkNodeProject(String nodeId, String projectId) { - List projects = projects(); - if (projects == null) { - return false; - } - for (NodeProject project : projects) { - if (project.getNode().equals(nodeId)) { - List projects1 = project.getProjects(); - if (projects1 == null) { - return false; - } - for (String s : projects1) { - if (projectId.equals(s)) { - return true; - } - } - } - } - return false; - } - - public enum NotifyType implements BaseEnum { - /** - * 通知方式 - */ - dingding(0, "钉钉"), - mail(1, "邮箱"), - workWx(2, "企业微信"), -// sms(2, "短信"), - ; - - private final int code; - private final String desc; - - NotifyType(int code, String desc) { - this.code = code; - this.desc = desc; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - } - - /** - * 通知 - */ - public static class Notify extends BaseJsonModel { - private int style; - private String value; - - public Notify() { - } - - public Notify(NotifyType style, String value) { - this.style = style.getCode(); - this.value = value; - } - - public int getStyle() { - return style; - } - - public void setStyle(int style) { - this.style = style; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - } - - public static class NodeProject extends BaseJsonModel { - private String node; - private List projects; - - public String getNode() { - return node; - } - - public void setNode(String node) { - this.node = node; - } - - public List getProjects() { - return projects; - } - - public void setProjects(List projects) { - this.projects = projects; - } - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/MonitorUserOptModel.java b/modules/server/src/main/java/io/jpom/model/data/MonitorUserOptModel.java deleted file mode 100644 index 7a0302ce46..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/MonitorUserOptModel.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import com.alibaba.fastjson.JSON; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.h2db.TableName; -import io.jpom.util.StringUtil; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 监控用户操作实体 - * - * @author Arno - */ -@TableName(value = "MONITOR_USER_OPT",name = "监控用户操作") -public class MonitorUserOptModel extends BaseWorkspaceModel { - /** - * - */ - private String name; - /** - * 监控的人员 - */ - private String monitorUser; - /** - * 监控的功能 - * - * @see ClassFeature - */ - private String monitorFeature; - /** - * 监控的操作 - * - * @see MethodFeature - */ - private String monitorOpt; - /** - * 报警联系人 - */ - private String notifyUser; - - /** - * 监控开启状态 - */ - private Boolean status; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public void setMonitorUser(String monitorUser) { - this.monitorUser = monitorUser; - } - - public String getMonitorFeature() { - List object = monitorFeature(); - return object == null ? null : JSON.toJSONString(object); - } - - public List monitorFeature() { - return StringUtil.jsonConvertArray(monitorFeature, ClassFeature.class); - } - - public void setMonitorFeature(String monitorFeature) { - this.monitorFeature = monitorFeature; - } - - public void monitorFeature(List monitorFeature) { - if (monitorFeature == null) { - this.monitorFeature = null; - } else { - this.monitorFeature = JSON.toJSONString(monitorFeature.stream().map(Enum::name).collect(Collectors.toList())); - } - } - - public String getMonitorOpt() { - List object = monitorOpt(); - return object == null ? null : JSON.toJSONString(object); - } - - - public List monitorOpt() { - return StringUtil.jsonConvertArray(monitorOpt, MethodFeature.class); - } - - public void setMonitorOpt(String monitorOpt) { - this.monitorOpt = monitorOpt; - } - - public void monitorOpt(List monitorOpt) { - if (monitorOpt == null) { - this.monitorOpt = null; - } else { - this.monitorOpt = JSON.toJSONString(monitorOpt.stream().map(Enum::name).collect(Collectors.toList())); - } - } - - public void setNotifyUser(String notifyUser) { - this.notifyUser = notifyUser; - } - - public Boolean getStatus() { - return status; - } - - public String getMonitorUser() { - List object = monitorUser(); - return object == null ? null : JSON.toJSONString(object); - } - - public String getNotifyUser() { - List object = notifyUser(); - return object == null ? null : JSON.toJSONString(object); - - } - - public List monitorUser() { - return StringUtil.jsonConvertArray(monitorUser, String.class); - } - - public void monitorUser(List monitorUser) { - if (monitorUser == null) { - this.monitorUser = null; - } else { - this.monitorUser = JSON.toJSONString(monitorUser); - } - } - - public List notifyUser() { - return StringUtil.jsonConvertArray(notifyUser, String.class); - } - - public void notifyUser(List notifyUser) { - if (notifyUser == null) { - this.notifyUser = null; - } else { - this.notifyUser = JSON.toJSONString(notifyUser); - } - } - - - public boolean isStatus() { - return status != null && status; - } - - public void setStatus(Boolean status) { - this.status = status; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/NodeModel.java b/modules/server/src/main/java/io/jpom/model/data/NodeModel.java deleted file mode 100644 index 5544639714..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/NodeModel.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; - -/** - * 节点实体 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@TableName(value = "NODE_INFO", name = "节点信息") -public class NodeModel extends BaseWorkspaceModel { - - private String url; - private String loginName; - private String loginPwd; - private String name; - - /** - * 节点协议 - */ - private String protocol; - /** - * 开启状态,如果关闭状态就暂停使用节点 - */ - private Integer openStatus; - /** - * 节点超时时间 - */ - private Integer timeOut; - /** - * 绑定的sshId - */ - private String sshId; - -// /** -// * 节点分组 -// */ -// private String group; - - /** - * 监控周期 - */ - private Integer cycle; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getCycle() { - return cycle; - } - - public void setCycle(Integer cycle) { - this.cycle = cycle; - } - - public String getSshId() { - return sshId; - } - - public void setSshId(String sshId) { - this.sshId = sshId; - } - - public Integer getTimeOut() { - return timeOut; - } - - public void setTimeOut(Integer timeOut) { - this.timeOut = timeOut; - } - - public Integer getOpenStatus() { - return openStatus; - } - - public void setOpenStatus(Integer openStatus) { - this.openStatus = openStatus; - } - - public boolean isOpenStatus() { - return openStatus != null && openStatus == 1; - } - - public NodeModel() { - } - - public NodeModel(String id) { - this.setId(id); - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol.toLowerCase(); - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getLoginName() { - return loginName; - } - - public void setLoginName(String loginName) { - this.loginName = loginName; - } - - public String getLoginPwd() { - return loginPwd; - } - - public void setLoginPwd(String loginPwd) { - this.loginPwd = loginPwd; - } - - /** - * 获取 授权的信息 - * - * @return sha1 - */ - public String toAuthorize() { - return SecureUtil.sha1(loginName + "@" + loginPwd); - } - - public String getRealUrl(NodeUrl nodeUrl) { - return StrUtil.format("{}://{}{}", getProtocol(), getUrl(), nodeUrl.getUrl()); - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/NodeVersionModel.java b/modules/server/src/main/java/io/jpom/model/data/NodeVersionModel.java deleted file mode 100644 index 2ac89083b9..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/NodeVersionModel.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 码之科技工作室 -// * -// * 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 io.jpom.model.data; -// -//import io.jpom.model.BaseModel; -// -///** -// * @author lf -// */ -//public class NodeVersionModel extends BaseModel { -// private String version; -// /** -// * 节点分组 -// */ -// private String group; -// -// -// -// public String getVersion() { -// return version; -// } -// -// public void setVersion(String version) { -// this.version = version; -// } -// -// public String getGroup() { -// return group; -// } -// -// public void setGroup(String group) { -// this.group = group; -// } -//} diff --git a/modules/server/src/main/java/io/jpom/model/data/OutGivingModel.java b/modules/server/src/main/java/io/jpom/model/data/OutGivingModel.java deleted file mode 100644 index 0b493121b6..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/OutGivingModel.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSON; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; -import io.jpom.util.StringUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * 分发实体 - * - * @author jiangzeyin - * @date 2019/4/21 - */ -@TableName(value = "OUT_GIVING", name = "节点分发") -public class OutGivingModel extends BaseWorkspaceModel { - /** - * 名称 - */ - private String name; - /** - * 分发间隔时间 - */ - private Integer intervalTime; - /** - * 节点下的项目列表 - */ - private String outGivingNodeProjectList; - /** - * 分发后的操作 - */ - private Integer afterOpt; - /** - * 是否清空旧包发布 - */ - private Boolean clearOld; - /** - * 是否为单独创建的分发项目 - */ - private Boolean outGivingProject; - - public Integer getIntervalTime() { - return intervalTime; - } - - public void setIntervalTime(Integer intervalTime) { - this.intervalTime = intervalTime; - } - - public Boolean getClearOld() { - return clearOld; - } - - public void setClearOld(Boolean clearOld) { - this.clearOld = clearOld; - } - - public Boolean getOutGivingProject() { - return outGivingProject; - } - - public void setOutGivingProject(Boolean outGivingProject) { - this.outGivingProject = outGivingProject; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean isClearOld() { - return clearOld != null && clearOld; - } - - public boolean isOutGivingProject() { - return outGivingProject != null && outGivingProject; - } - - - public void setAfterOpt(Integer afterOpt) { - this.afterOpt = afterOpt; - } - - - public Integer getAfterOpt() { - return afterOpt; - } - - public void setAfterOpt(int afterOpt) { - this.afterOpt = afterOpt; - } - - public String getOutGivingNodeProjectList() { - return outGivingNodeProjectList; - } - - public void setOutGivingNodeProjectList(String outGivingNodeProjectList) { - this.outGivingNodeProjectList = outGivingNodeProjectList; - } - - public List outGivingNodeProjectList() { - return StringUtil.jsonConvertArray(outGivingNodeProjectList, OutGivingNodeProject.class); - } - - public void outGivingNodeProjectList(List outGivingNodeProjectList) { - if (outGivingNodeProjectList == null) { - this.outGivingNodeProjectList = null; - } else { - this.outGivingNodeProjectList = JSON.toJSONString(outGivingNodeProjectList); - } - } - - /** - * 判断是否包含某个项目id - * - * @param projectId 项目id - * @return true 包含 - */ - public boolean checkContains(String nodeId, String projectId) { - return getNodeProject(nodeId, projectId) != null; - } - - /** - * 获取节点的项目信息 - * - * @param nodeId 节点 - * @param projectId 项目 - * @return outGivingNodeProject - */ - public OutGivingNodeProject getNodeProject(String nodeId, String projectId) { - List thisPs = outGivingNodeProjectList(); - return getNodeProject(thisPs, nodeId, projectId); - } - - /** - * 从指定数组中获取对应信息 - * - * @param outGivingNodeProjects 节点项目列表 - * @param nodeId 节点id - * @param projectId 项目id - * @return 实体 - */ - private OutGivingNodeProject getNodeProject(List outGivingNodeProjects, String nodeId, String projectId) { - if (outGivingNodeProjects == null) { - return null; - } - for (OutGivingNodeProject outGivingNodeProject1 : outGivingNodeProjects) { - if (StrUtil.equalsIgnoreCase(outGivingNodeProject1.getProjectId(), projectId) && StrUtil.equalsIgnoreCase(outGivingNodeProject1.getNodeId(), nodeId)) { - return outGivingNodeProject1; - } - } - return null; - } - - /** - * 获取已经删除的节点项目 - * - * @param newsProject 要比较的分发项目 - * @return 已经删除过的 - */ - public List getDelete(List newsProject) { - List old = outGivingNodeProjectList(); - if (old == null || old.isEmpty()) { - return null; - } - List delete = new ArrayList<>(); - old.forEach(outGivingNodeProject -> { - if (getNodeProject(newsProject, outGivingNodeProject.getNodeId(), outGivingNodeProject.getProjectId()) != null) { - return; - } - delete.add(outGivingNodeProject); - }); - return delete; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/OutGivingNodeProject.java b/modules/server/src/main/java/io/jpom/model/data/OutGivingNodeProject.java deleted file mode 100644 index 75cbe383e1..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/OutGivingNodeProject.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.BaseEnum; -import io.jpom.model.BaseJsonModel; -import io.jpom.service.node.NodeService; - -/** - * 节点项目 - * - * @author jiangzeyin - * @date 2019/4/22 - */ -public class OutGivingNodeProject extends BaseJsonModel { - private static NodeService nodeService; - - private String nodeId; - private String projectId; - private String lastOutGivingTime; - private Integer status = Status.No.getCode(); - private String result; - - - public String getResult() { - return result; - } - - public void setResult(String result) { - this.result = result; - } - - public Integer getStatus() { - return status; - } - - public String getStatusMsg() { - return BaseEnum.getDescByCode(Status.class, getStatus()); - } - - public void setStatus(Integer status) { - this.status = status; - } - - public String getLastOutGivingTime() { - return StrUtil.emptyToDefault(lastOutGivingTime, StrUtil.DASHED); - } - - public void setLastOutGivingTime(String lastOutGivingTime) { - this.lastOutGivingTime = lastOutGivingTime; - } - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - /** - * 获取节点的数据 - * - * @param get 防止自动读取 - * @return 实体 - */ - public NodeModel getNodeData(boolean get) { - if (nodeService == null) { - nodeService = SpringUtil.getBean(NodeService.class); - } - return nodeService.getByKey(this.nodeId); - } - - /** - * 状态 - */ - public enum Status implements BaseEnum { - /** - * - */ - No(0, "未分发"), - Ing(1, "分发中"), - Ok(2, "分发成功"), - Fail(3, "分发失败"), - Cancel(4, "取消分发"), - ; - private final int code; - private final String desc; - - Status(int code, String desc) { - this.code = code; - this.desc = desc; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/ProjectInfoModel.java b/modules/server/src/main/java/io/jpom/model/data/ProjectInfoModel.java deleted file mode 100644 index b6a202ed40..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/ProjectInfoModel.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.crypto.SecureUtil; -import io.jpom.model.BaseNodeModel; -import io.jpom.service.h2db.TableName; -import org.springframework.util.Assert; - -/** - * @author bwcx_jzy - * @since 2021/12/5 - */ -@TableName(value = "PROJECT_INFO", name = "项目信息") -public class ProjectInfoModel extends BaseNodeModel { - - private String projectId; - - - private String name; - - private String mainClass; - private String lib; - /** - * 白名单目录 - */ - private String whitelistDirectory; - /** - * 日志目录 - */ - private String logPath; - /** - * jvm 参数 - */ - private String jvm; - /** - * java main 方法参数 - */ - private String args; - - private String javaCopyItemList; - /** - * WebHooks - */ - private String token; - - private String jdkId; - - private String runMode; - /** - * 节点分发项目,不允许在项目管理中编辑 - */ - private Integer outGivingProject; - /** - * -Djava.ext.dirs=lib -cp conf:run.jar - * 填写【lib:conf】 - */ - private String javaExtDirsCp; - - public String fullId() { - String workspaceId = this.getWorkspaceId(); - - String nodeId = this.getNodeId(); - - String projectId = this.getProjectId(); - - return ProjectInfoModel.fullId(workspaceId, nodeId, projectId); - } - - public static String fullId(String workspaceId, String nodeId, String projectId) { - - Assert.hasText(workspaceId, "workspaceId"); - - Assert.hasText(workspaceId, "nodeId"); - - Assert.hasText(workspaceId, "projectId"); - return SecureUtil.sha1(workspaceId + nodeId + projectId); - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getMainClass() { - return mainClass; - } - - public void setMainClass(String mainClass) { - this.mainClass = mainClass; - } - - public String getLib() { - return lib; - } - - public void setLib(String lib) { - this.lib = lib; - } - - public String getWhitelistDirectory() { - return whitelistDirectory; - } - - public void setWhitelistDirectory(String whitelistDirectory) { - this.whitelistDirectory = whitelistDirectory; - } - - - public String getLogPath() { - return logPath; - } - - public void setLogPath(String logPath) { - this.logPath = logPath; - } - - public String getJvm() { - return jvm; - } - - public void setJvm(String jvm) { - this.jvm = jvm; - } - - public String getArgs() { - return args; - } - - public void setArgs(String args) { - this.args = args; - } - - public String getJavaCopyItemList() { - return javaCopyItemList; - } - - public void setJavaCopyItemList(String javaCopyItemList) { - this.javaCopyItemList = javaCopyItemList; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public String getJdkId() { - return jdkId; - } - - public void setJdkId(String jdkId) { - this.jdkId = jdkId; - } - - public String getRunMode() { - return runMode; - } - - public void setRunMode(String runMode) { - this.runMode = runMode; - } - - public Integer getOutGivingProject() { - return outGivingProject; - } - - public void setOutGivingProject(Integer outGivingProject) { - this.outGivingProject = outGivingProject; - } - - public String getJavaExtDirsCp() { - return javaExtDirsCp; - } - - public void setJavaExtDirsCp(String javaExtDirsCp) { - this.javaExtDirsCp = javaExtDirsCp; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/RepositoryModel.java b/modules/server/src/main/java/io/jpom/model/data/RepositoryModel.java deleted file mode 100644 index 6c680ab6bb..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/RepositoryModel.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import io.jpom.model.BaseEnum; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.service.h2db.TableName; - -/** - * @author Hotstrip - * 仓库地址实体类 - */ -@TableName(value = "REPOSITORY", name = "仓库信息") -public class RepositoryModel extends BaseWorkspaceModel { - /** - * 名称 - */ - private String name; - /** - * 仓库地址 - */ - private String gitUrl; - /** - * 仓库类型{0: GIT, 1: SVN} - */ - private Integer repoType; - /** - * 拉取代码的协议{0: http, 1: ssh} - * - * @see GitProtocolEnum - */ - private Integer protocol; - /** - * 登录用户 - */ - private String userName; - /** - * 登录密码 - */ - private String password; - /** - * SSH RSA 公钥 - */ - @Deprecated - private String rsaPub; - /** - * SSH RSA 私钥 - */ - private String rsaPrv; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getGitUrl() { - return gitUrl; - } - - public void setGitUrl(String gitUrl) { - this.gitUrl = gitUrl; - } - - public Integer getRepoType() { - return repoType; - } - - public void setRepoType(Integer repoType) { - this.repoType = repoType; - } - - /** - * 返回协议类型,如果为 null 会尝试识别 http - * - * @return 枚举的值(1/0) - * @see GitProtocolEnum - */ - public Integer getProtocol() { - if (protocol != null) { - return protocol; - } - String gitUrl = this.getGitUrl(); - if (StrUtil.isEmpty(gitUrl)) { - return null; - } - if (HttpUtil.isHttps(gitUrl) || HttpUtil.isHttp(gitUrl)) { - return GitProtocolEnum.HTTP.getCode(); - } - return null; - } - - public void setProtocol(Integer protocol) { - this.protocol = protocol; - } - - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Deprecated - public String getRsaPub() { - return rsaPub; - } - - @Deprecated - public void setRsaPub(String rsaPub) { - this.rsaPub = rsaPub; - } - - public String getRsaPrv() { - return rsaPrv; - } - - public void setRsaPrv(String rsaPrv) { - this.rsaPrv = rsaPrv; - } - - /** - * 仓库类型 - */ - public enum RepoType implements BaseEnum { - /** - * git - */ - Git(0, "Git"), - Svn(1, "Svn"), - ; - private final int code; - private final String desc; - - RepoType(int code, String desc) { - this.code = code; - this.desc = desc; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/ServerWhitelist.java b/modules/server/src/main/java/io/jpom/model/data/ServerWhitelist.java deleted file mode 100644 index f2f4497828..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/ServerWhitelist.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseJsonModel; - -import java.util.List; -import java.util.Set; - -/** - * 节点分发白名单 - * - * @author jiangzeyin - * @date 2019/4/22 - */ -public class ServerWhitelist extends BaseJsonModel { - - public static final String ID = "OUTGIVING_WHITELIST"; - - /** - * 项目的白名单 - */ - private List outGiving; - - /** - * 允许远程下载的 host - */ - private Set allowRemoteDownloadHost; - - public List getOutGiving() { - return outGiving; - } - - public void setOutGiving(List outGiving) { - this.outGiving = outGiving; - } - - public Set getAllowRemoteDownloadHost() { - return allowRemoteDownloadHost; - } - - public void setAllowRemoteDownloadHost(Set allowRemoteDownloadHost) { - this.allowRemoteDownloadHost = allowRemoteDownloadHost; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/SshModel.java b/modules/server/src/main/java/io/jpom/model/data/SshModel.java deleted file mode 100644 index 4fe5fb2d56..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/SshModel.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSONArray; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; -import io.jpom.util.StringUtil; - -import java.nio.charset.Charset; -import java.util.List; - -/** - * ssh 信息 - * - * @author bwcx_jzy - * @date 2019/8/9 - */ -@TableName(value = "SSH_INFO", name = "SSH 信息") -public class SshModel extends BaseWorkspaceModel { - - private String name; - private String host; - private Integer port; - private String user; - private String password; - /** - * 编码格式 - */ - private String charset; - - /** - * 文件目录 - */ - private String fileDirs; - - /** - * ssh 私钥 - */ - private String privateKey; - - private String connectType; - - /** - * 不允许执行的命令 - */ - private String notAllowedCommand; - - /** - * 允许编辑的后缀文件 - */ - private String allowEditSuffix; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getNotAllowedCommand() { - return notAllowedCommand; - } - - public void setNotAllowedCommand(String notAllowedCommand) { - this.notAllowedCommand = notAllowedCommand; - } - - public ConnectType connectType() { - return EnumUtil.fromString(ConnectType.class, this.connectType, ConnectType.PASS); - } - - public String getConnectType() { - return connectType; - } - - public void setConnectType(String connectType) { - this.connectType = connectType; - } - - public String getPrivateKey() { - return privateKey; - } - - public void setPrivateKey(String privateKey) { - this.privateKey = privateKey; - } - - public String getFileDirs() { - return fileDirs; - } - - public void setFileDirs(String fileDirs) { - this.fileDirs = fileDirs; - } - - public List fileDirs() { - return StringUtil.jsonConvertArray(this.fileDirs, String.class); - } - - public void fileDirs(List fileDirs) { - if (fileDirs != null) { - for (int i = fileDirs.size() - 1; i >= 0; i--) { - String s = fileDirs.get(i); - fileDirs.set(i, FileUtil.normalize(s)); - } - this.fileDirs = JSONArray.toJSONString(fileDirs); - } else { - this.fileDirs = null; - } - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getCharset() { - return charset; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public Charset getCharsetT() { - Charset charset; - try { - charset = Charset.forName(this.getCharset()); - } catch (Exception e) { - charset = CharsetUtil.CHARSET_UTF_8; - } - return charset; - } - - public List allowEditSuffix() { - return StringUtil.jsonConvertArray(this.allowEditSuffix, String.class); - } - - public void allowEditSuffix(List allowEditSuffix) { - if (allowEditSuffix == null) { - this.allowEditSuffix = null; - } else { - this.allowEditSuffix = JSONArray.toJSONString(allowEditSuffix); - } - } - - public String getAllowEditSuffix() { - return allowEditSuffix; - } - - public void setAllowEditSuffix(String allowEditSuffix) { - this.allowEditSuffix = allowEditSuffix; - } - - /** - * 检查是否包含禁止命令 - * - * @param sshItem 实体 - * @param inputItem 输入的命令 - * @return false 存在禁止输入的命令 - */ - public static boolean checkInputItem(SshModel sshItem, String inputItem) { - // 检查禁止执行的命令 - String notAllowedCommand = StrUtil.emptyToDefault(sshItem.getNotAllowedCommand(), StrUtil.EMPTY).toLowerCase(); - if (StrUtil.isEmpty(notAllowedCommand)) { - return true; - } - List split = StrUtil.split(notAllowedCommand, StrUtil.COMMA); - inputItem = inputItem.toLowerCase(); - List commands = StrUtil.split(inputItem, StrUtil.CR); - commands.addAll(StrUtil.split(inputItem, "&")); - for (String s : split) { - // - boolean anyMatch = commands.stream().anyMatch(item -> StrUtil.startWithAny(item, s + StrUtil.SPACE, ("&" + s + StrUtil.SPACE), StrUtil.SPACE + s + StrUtil.SPACE)); - if (anyMatch) { - return false; - } - // - anyMatch = commands.stream().anyMatch(item -> StrUtil.equals(item, s)); - if (anyMatch) { - return false; - } - } - return true; - } - - public enum ConnectType { - /** - * 账号密码 - */ - PASS, - /** - * 密钥 - */ - PUBKEY - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/SystemIpConfigModel.java b/modules/server/src/main/java/io/jpom/model/data/SystemIpConfigModel.java deleted file mode 100644 index 0d81b55f2b..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/SystemIpConfigModel.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseJsonModel; - -/** - * @author bwcx_jzy - * @date 2021/4/18 - */ -public class SystemIpConfigModel extends BaseJsonModel { - - public static final String ID = "IP_CONFIG"; - - /** - * ip 白名单 允许访问 - */ - private String allowed; - - /** - * 禁止 - */ - private String prohibited; - - public String getAllowed() { - return allowed; - } - - public void setAllowed(String allowed) { - this.allowed = allowed; - } - - public String getProhibited() { - return prohibited; - } - - public void setProhibited(String prohibited) { - this.prohibited = prohibited; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/SystemParametersModel.java b/modules/server/src/main/java/io/jpom/model/data/SystemParametersModel.java deleted file mode 100644 index 9c5b5ab600..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/SystemParametersModel.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseStrikeDbModel; -import io.jpom.service.h2db.TableName; -import io.jpom.util.StringUtil; - -/** - * 系统参数 - * - * @author bwcx_jzy - * @since 2021/12/2 - */ -@TableName(value = "SYSTEM_PARAMETERS", name = "系统参数") -public class SystemParametersModel extends BaseStrikeDbModel { - - /** - * 参数值 - */ - private String value; - /** - * 参数描述 - */ - private String description; - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public T jsonToBean(Class cls) { - return StringUtil.jsonConvert(this.getValue(), cls); - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/UserBindWorkspaceModel.java b/modules/server/src/main/java/io/jpom/model/data/UserBindWorkspaceModel.java deleted file mode 100644 index 7d7a580580..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/UserBindWorkspaceModel.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.crypto.SecureUtil; -import io.jpom.model.BaseDbModel; -import io.jpom.service.h2db.TableName; - -/** - * @author bwcx_jzy - * @since 2021/12/4 - */ -@TableName(value = "USER_BIND_WORKSPACE", name = "用户工作空间关系表") -public class UserBindWorkspaceModel extends BaseDbModel { - - private String userId; - - private String workspaceId; - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getWorkspaceId() { - return workspaceId; - } - - public void setWorkspaceId(String workspaceId) { - this.workspaceId = workspaceId; - } - - /** - * 生产绑定关系表 主键 ID - * - * @param userId 用户ID - * @param workspaceId 工作空间ID - * @return id - */ - public static String getId(String userId, String workspaceId) { - return SecureUtil.sha1(userId + workspaceId); - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/UserModel.java b/modules/server/src/main/java/io/jpom/model/data/UserModel.java deleted file mode 100644 index 105c7293cb..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/UserModel.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.model.BaseStrikeDbModel; -import io.jpom.service.h2db.TableName; -import io.jpom.system.ServerExtConfigBean; - -import java.util.concurrent.TimeUnit; - -/** - * 用户实体 - * - * @author jiangzeyin - * @date 2019/1/16 - */ -@TableName(value = "USER_INFO", name = "用户账号") -public class UserModel extends BaseStrikeDbModel { - /** - * 系统管理员 - */ - public static String SYSTEM_ADMIN = "sys"; - /** - * - */ - public static final UserModel EMPTY = new UserModel(UserModel.SYSTEM_ADMIN); - /** - * 系统占用名 - */ - public static String SYSTEM_OCCUPY_NAME = "系统管理员"; - /** - * 用户名限制 - */ - public static int USER_NAME_MIN_LEN = 3; - /** - * 盐值长度 - */ - public static int SALT_LEN = 8; - /** - * 昵称 - */ - private String name; - /** - * 密码 - */ - private String password; - private String salt; - /** - * 创建此用户的人 - */ - private String parent; - /** - * 系统管理员 - */ - private Integer systemUser; - /** - * 连续登录失败次数 - */ - private Integer pwdErrorCount; - /** - * 最后失败时间 - */ - private Long lastPwdErrorTime; - /** - * 账号被锁定的时长 - */ - private Long lockTime; - /** - * 邮箱 - */ - private String email; - /** - * 钉钉 - */ - private String dingDing; - /** - * 企业微信 - */ - private String workWx; - - public UserModel(String id) { - this.setId(id); - } - - public UserModel() { - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSalt() { - return salt; - } - - public void setSalt(String salt) { - this.salt = salt; - } - - public Integer getSystemUser() { - return systemUser; - } - - public void setSystemUser(Integer systemUser) { - this.systemUser = ObjectUtil.defaultIfNull(systemUser, 0) == 1 ? systemUser : 0; - } - - public Long getLockTime() { - return lockTime; - } - - public Long getLastPwdErrorTime() { - return lastPwdErrorTime; - } - - public void setLastPwdErrorTime(Long lastPwdErrorTime) { - this.lastPwdErrorTime = lastPwdErrorTime; - } - - public void setLockTime(long lockTime) { - this.lockTime = lockTime; - } - - public Integer getPwdErrorCount() { - return pwdErrorCount; - } - - public void setPwdErrorCount(int pwdErrorCount) { - this.pwdErrorCount = pwdErrorCount; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getDingDing() { - return dingDing; - } - - public void setDingDing(String dingDing) { - this.dingDing = dingDing; - } - - public String getWorkWx() { - return workWx; - } - - public void setWorkWx(String workWx) { - this.workWx = workWx; - } - - /** - * 解锁 - */ - public void unLock() { - setPwdErrorCount(0); - setLockTime(0); - setLastPwdErrorTime(0L); - } - - /** - * 剩余解锁时间 - * - * @return 0是未锁定 - */ - public long overLockTime() { - if (ServerExtConfigBean.getInstance().userAlwaysLoginError <= 0) { - return 0; - } - // 不限制演示账号的登录 - if (isDemoUser()) { - return 0; - } - // 最后一次失败时间 - Long lastTime = getLastPwdErrorTime(); - if (lastTime == null || lastTime <= 0) { - return 0; - } - // 当前锁定时间 - Long lockTime = getLockTime(); - if (lockTime == null || lockTime <= 0) { - return 0; - } - // 解锁时间 - lastTime += lockTime; - long nowTime = DateUtil.currentSeconds(); - // 剩余解锁时间 - lastTime -= nowTime; - if (lastTime > 0) { - return lastTime; - } - return 0; - } - - /** - * 登录失败,重新计算锁定时间 - */ - public void errorLock() { - // 未开启锁定功能 - if (ServerExtConfigBean.getInstance().userAlwaysLoginError <= 0) { - return; - } - setPwdErrorCount(getPwdErrorCount() + 1); - int count = getPwdErrorCount(); - // 记录错误时间 - setLastPwdErrorTime(DateUtil.currentSeconds()); - if (count < ServerExtConfigBean.getInstance().userAlwaysLoginError) { - // 还未达到锁定条件 - return; - } - int level = count / ServerExtConfigBean.getInstance().userAlwaysLoginError; - switch (level) { - case 1: - // 在错误倍数 为1 锁定 30分钟 - setLockTime(TimeUnit.MINUTES.toSeconds(30)); - break; - case 2: - // 在错误倍数 为2 锁定 1小时 - setLockTime(TimeUnit.HOURS.toSeconds(1)); - break; - default: - // 其他情况 10小时 - setLockTime(TimeUnit.HOURS.toSeconds(10)); - break; - } - } - - public String getParent() { - return parent; - } - - public void setParent(String parent) { - this.parent = parent; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - // 记录修改时间,如果在线用户线退出 - //this.setModifyTime(DateUtil.current()); - } - - public boolean isSystemUser() { - return systemUser != null && systemUser == 1; - } - - /** - * 是否为超级管理员 - * - * @return true 是 - */ - public boolean isSuperSystemUser() { - return StrUtil.equals(getParent(), SYSTEM_ADMIN); - } - - /** - * demo 登录名默认为系统验证账号 - * - * @return true - */ - public boolean isDemoUser() { - return "demo".equals(getId()); - } - - /** - * 隐藏系统管理的真实id - * - * @param userModel 实体 - * @return 系统管理员返回默认 - */ - public static String getOptUserName(UserModel userModel) { - String userId; - if (userModel.isSystemUser()) { - userId = UserModel.SYSTEM_OCCUPY_NAME; - } else { - userId = userModel.getId(); - } - return userId; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/WorkspaceEnvVarModel.java b/modules/server/src/main/java/io/jpom/model/data/WorkspaceEnvVarModel.java deleted file mode 100644 index 0fd0b4ec0f..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/WorkspaceEnvVarModel.java +++ /dev/null @@ -1,67 +0,0 @@ - -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; - -/** - * 工作空间环境变量 - * - * @author bwcx_jzy - * @date 2021/12/10 - */ -@TableName(value = "WORKSPACE_ENV_VAR", name = "工作空间环境变量") -public class WorkspaceEnvVarModel extends BaseWorkspaceModel { - - private String name; - - private String value; - - private String description; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/WorkspaceModel.java b/modules/server/src/main/java/io/jpom/model/data/WorkspaceModel.java deleted file mode 100644 index 8f341c02e8..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/WorkspaceModel.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; - -import io.jpom.model.BaseStrikeDbModel; -import io.jpom.service.h2db.TableName; - -/** - * 工作空间 - * - * @author bwcx_jzy - * @since 2021/12/3 - */ -@TableName(value = "WORKSPACE", name = "工作空间") -public class WorkspaceModel extends BaseStrikeDbModel { - - /** - * 默认的工作空间 - */ - public static final String DEFAULT_ID = "DEFAULT"; - - - /** - * 名称 - */ - private String name; - - /** - * 描述 - */ - private String description; - - public WorkspaceModel() { - } - - public WorkspaceModel(String id) { - this.setId(id); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/data/package-info.java b/modules/server/src/main/java/io/jpom/model/data/package-info.java deleted file mode 100644 index 5095590403..0000000000 --- a/modules/server/src/main/java/io/jpom/model/data/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.data; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/model/dto/UserLoginDto.java b/modules/server/src/main/java/io/jpom/model/dto/UserLoginDto.java deleted file mode 100644 index 76d47913da..0000000000 --- a/modules/server/src/main/java/io/jpom/model/dto/UserLoginDto.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.dto; - -/** - * @author bwcx_jzy - * @date 2020/11/2 - */ -public class UserLoginDto { - - private String token; - - private String longTermToken; - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public String getLongTermToken() { - return longTermToken; - } - - public void setLongTermToken(String longTermToken) { - this.longTermToken = longTermToken; - } - - public UserLoginDto() { - - } - - public UserLoginDto(String token, String jwtId) { - this.setLongTermToken(jwtId); - this.setToken(token); - } -} diff --git a/modules/server/src/main/java/io/jpom/model/enums/BackupStatusEnum.java b/modules/server/src/main/java/io/jpom/model/enums/BackupStatusEnum.java deleted file mode 100644 index 62326546ac..0000000000 --- a/modules/server/src/main/java/io/jpom/model/enums/BackupStatusEnum.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.enums; - -import io.jpom.model.BaseEnum; - -/** - * backup type - * - * @author Hotstrip - * @since 2021-11-27 - */ -public enum BackupStatusEnum implements BaseEnum { - /** - * 状态{0: 默认; 1: 成功; 2: 失败} - */ - DEFAULT(0, "默认"), - SUCCESS(1, "备份成功"), - FAILED(2, "备份失败"), - ; - - BackupStatusEnum(int code, String desc) { - this.code = code; - this.desc = desc; - } - - int code; - String desc; - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - -} diff --git a/modules/server/src/main/java/io/jpom/model/enums/BackupTypeEnum.java b/modules/server/src/main/java/io/jpom/model/enums/BackupTypeEnum.java deleted file mode 100644 index 11087d7753..0000000000 --- a/modules/server/src/main/java/io/jpom/model/enums/BackupTypeEnum.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.enums; - -import io.jpom.model.BaseEnum; - -/** - * backup type - * - * @author Hotstrip - * @since 2021-11-24 - */ -public enum BackupTypeEnum implements BaseEnum { - /** - * 备份类型{0: 全量, 1: 部分} - */ - ALL(0, "全量备份"), - PART(1, "部分备份"), - ; - - BackupTypeEnum(int code, String desc) { - this.code = code; - this.desc = desc; - } - - int code; - String desc; - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - -} diff --git a/modules/server/src/main/java/io/jpom/model/enums/BuildReleaseMethod.java b/modules/server/src/main/java/io/jpom/model/enums/BuildReleaseMethod.java deleted file mode 100644 index 01b473173b..0000000000 --- a/modules/server/src/main/java/io/jpom/model/enums/BuildReleaseMethod.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.enums; - -import io.jpom.model.BaseEnum; - -/** - * @author bwcx_jzy - * @since 2021/8/27 - */ -public enum BuildReleaseMethod implements BaseEnum { - /** - * 发布 - */ - No(0, "不发布"), - Outgiving(1, "节点分发"), - Project(2, "项目"), - Ssh(3, "SSH"), - LocalCommand(4, "本地命令行"), - ; - private final int code; - private final String desc; - - BuildReleaseMethod(int code, String desc) { - this.code = code; - this.desc = desc; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/enums/BuildStatus.java b/modules/server/src/main/java/io/jpom/model/enums/BuildStatus.java deleted file mode 100644 index 7bb3a2450d..0000000000 --- a/modules/server/src/main/java/io/jpom/model/enums/BuildStatus.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.enums; - -import io.jpom.model.BaseEnum; - -/** - * @author bwcx_jzy - * @since 2021/8/27 - */ -public enum BuildStatus implements BaseEnum { - /** - * - */ - No(0, "未构建"), - - Ing(1, "构建中"), - - Success(2, "构建完成"), - - Error(3, "构建失败"), - - PubIng(4, "发布中"), - - PubSuccess(5, "发布成功"), - - PubError(6, "发布失败"), - - Cancel(7, "取消构建"), - ; - - private final int code; - private final String desc; - - BuildStatus(int code, String desc) { - this.code = code; - this.desc = desc; - } - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - -} diff --git a/modules/server/src/main/java/io/jpom/model/enums/GitProtocolEnum.java b/modules/server/src/main/java/io/jpom/model/enums/GitProtocolEnum.java deleted file mode 100644 index 26366b0b54..0000000000 --- a/modules/server/src/main/java/io/jpom/model/enums/GitProtocolEnum.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.enums; - -import io.jpom.model.BaseEnum; - -/** - * Git protocol - * - * @author Hotstrip - * @since 2021-08-26 - */ -public enum GitProtocolEnum implements BaseEnum { - /** - * http - */ - HTTP(0, "HTTP(s)"), - SSH(1, "SSH"), - ; - - GitProtocolEnum(int code, String desc) { - this.code = code; - this.desc = desc; - } - - int code; - String desc; - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - -} diff --git a/modules/server/src/main/java/io/jpom/model/log/BuildHistoryLog.java b/modules/server/src/main/java/io/jpom/model/log/BuildHistoryLog.java deleted file mode 100644 index 1c702aef08..0000000000 --- a/modules/server/src/main/java/io/jpom/model/log/BuildHistoryLog.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.log; - -import cn.hutool.core.annotation.PropIgnore; -import cn.hutool.core.io.FileUtil; -import io.jpom.build.BuildExtraModule; -import io.jpom.build.BuildUtil; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.model.enums.BuildStatus; -import io.jpom.service.h2db.TableName; - -import java.io.File; - -/** - * 构建历史记录 - * - * @author bwcx_jzy - * @date 2019/7/17 - * @see BuildExtraModule - **/ -@TableName(value = "BUILDHISTORYLOG", name = "构建历史") -public class BuildHistoryLog extends BaseWorkspaceModel { - /** - * 发布方式 - * - * @see BuildReleaseMethod - * @see BuildInfoModel#getReleaseMethod() - */ - private Integer releaseMethod; - /** - * 发布方法的数据id - * - * @see BuildInfoModel#getReleaseMethodDataId() - */ - private String releaseMethodDataId; - /** - * 分发后的操作 - * 仅在项目发布类型生效 - * - * @see io.jpom.model.AfterOpt - * @see BuildInfoModel#getExtraData() - */ - private Integer afterOpt; - /** - * 是否清空旧包发布 - */ - private Boolean clearOld; - /** - * 构建产物目录 - */ - private String resultDirFile; - /** - * 发布命令 ssh 才能用上 - */ - private String releaseCommand; - /** - * 发布到ssh中的目录 - */ - private String releasePath; - /** - * 关联的构建id - * - * @see BuildInfoModel#getId() - */ - private String buildDataId; - /** - * 构建名称 - */ - private String buildName; - /** - * 构建编号 - * - * @see BuildInfoModel#getBuildId() - */ - private Integer buildNumberId; - /** - * 状态 - * - * @see BuildStatus - */ - private Integer status; - /** - * 开始时间 - */ - private Long startTime; - /** - * 结束时间 - */ - private Long endTime; - /** - * 是否存在构建产物 - */ - @PropIgnore - private Boolean hashFile; - /** - * 是否存在日志 - */ - @PropIgnore - private Boolean hasLog; - - public Boolean getHashFile() { - File file = BuildUtil.getHistoryPackageFile(getBuildDataId(), getBuildNumberId(), getResultDirFile()); - hashFile = FileUtil.exist(file); - return hashFile; - } - - public void setHashFile(Boolean hashFile) { - this.hashFile = hashFile; - } - - public Boolean getHasLog() { - File file = BuildUtil.getLogFile(getBuildDataId(), getBuildNumberId()); - hasLog = FileUtil.exist(file); - return hasLog; - } - - public void setHasLog(Boolean hasLog) { - this.hasLog = hasLog; - } - - public Integer getReleaseMethod() { - return releaseMethod; - } - - public void setReleaseMethod(Integer releaseMethod) { - this.releaseMethod = releaseMethod; - } - - public String getReleaseMethodDataId() { - return releaseMethodDataId; - } - - public void setReleaseMethodDataId(String releaseMethodDataId) { - this.releaseMethodDataId = releaseMethodDataId; - } - - public Integer getAfterOpt() { - return afterOpt; - } - - public void setAfterOpt(Integer afterOpt) { - this.afterOpt = afterOpt; - } - - public Boolean getClearOld() { - return clearOld; - } - - public void setClearOld(Boolean clearOld) { - this.clearOld = clearOld; - } - - public String getResultDirFile() { - return resultDirFile; - } - - public void setResultDirFile(String resultDirFile) { - this.resultDirFile = resultDirFile; - } - - public String getReleaseCommand() { - return releaseCommand; - } - - public void setReleaseCommand(String releaseCommand) { - this.releaseCommand = releaseCommand; - } - - public String getReleasePath() { - return releasePath; - } - - public void setReleasePath(String releasePath) { - this.releasePath = releasePath; - } - - - public String getBuildDataId() { - return buildDataId; - } - - public void setBuildDataId(String buildDataId) { - this.buildDataId = buildDataId; - } - - public Integer getBuildNumberId() { - return buildNumberId; - } - - public void setBuildNumberId(Integer buildNumberId) { - this.buildNumberId = buildNumberId; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public Long getStartTime() { - return startTime; - } - - public void setStartTime(Long startTime) { - this.startTime = startTime; - } - - public Long getEndTime() { - return endTime; - } - - public void setEndTime(Long endTime) { - this.endTime = endTime; - } - - public String getBuildName() { - return buildName; - } - - public void setBuildName(String buildName) { - this.buildName = buildName; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/log/MonitorNotifyLog.java b/modules/server/src/main/java/io/jpom/model/log/MonitorNotifyLog.java deleted file mode 100644 index 2a9bd39c92..0000000000 --- a/modules/server/src/main/java/io/jpom/model/log/MonitorNotifyLog.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.log; - -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.data.MonitorModel; -import io.jpom.service.h2db.TableName; - -/** - * 监控日志 - * - * @author bwcx_jzy - * @date 2019/7/13 - */ -@TableName(value = "MONITORNOTIFYLOG", name = "监控通知") -public class MonitorNotifyLog extends BaseWorkspaceModel { - - /** - * 是否包含旧字段 - */ - public static boolean HAS_LOG_ID = false; - - /** - * - */ - @Deprecated - private String logId; - private String nodeId; - private String projectId; - /** - * 异常发生时间 - */ - private Long createTime; - private String title; - private String content; - /** - * 项目状态状态 - */ - private Boolean status; - /** - * 通知方式 - * - * @see MonitorModel.NotifyType - */ - private Integer notifyStyle; - /** - * 通知发送状态 - */ - private Boolean notifyStatus; - /** - * 监控id - */ - private String monitorId; - /** - * 通知对象 - */ - private String notifyObject; - /** - * 通知异常消息 - */ - private String notifyError; - - @Deprecated - public String getLogId() { - return logId; - } - - @Deprecated - public void setLogId(String logId) { - this.logId = logId; - } - - public String getNotifyObject() { - return notifyObject; - } - - public void setNotifyObject(String notifyObject) { - this.notifyObject = notifyObject; - } - - public String getNotifyError() { - return notifyError; - } - - public void setNotifyError(String notifyError) { - this.notifyError = notifyError; - } - - public Boolean getStatus() { - return status; - } - - public void setStatus(Boolean status) { - this.status = status; - } - - public Boolean getNotifyStatus() { - return notifyStatus; - } - - public void setNotifyStatus(Boolean notifyStatus) { - this.notifyStatus = notifyStatus; - } - - public void setNotifyStatus(boolean notifyStatus) { - this.notifyStatus = notifyStatus; - } - - public String getMonitorId() { - return monitorId; - } - - public void setMonitorId(String monitorId) { - this.monitorId = monitorId; - } - - public Integer getNotifyStyle() { - return notifyStyle; - } - - public void setNotifyStyle(Integer notifyStyle) { - this.notifyStyle = notifyStyle; - } - - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public Long getCreateTime() { - return createTime; - } - - public void setCreateTime(Long createTime) { - this.createTime = createTime; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public Boolean isStatus() { - return status; - } - - public void setStatus(boolean status) { - this.status = status; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/log/OutGivingLog.java b/modules/server/src/main/java/io/jpom/model/log/OutGivingLog.java deleted file mode 100644 index acbb9a393b..0000000000 --- a/modules/server/src/main/java/io/jpom/model/log/OutGivingLog.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.log; - -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.service.h2db.TableName; - -/** - * 项目分发日志 - * - * @author bwcx_jzy - * @date 2019/7/19 - **/ -@TableName(value = "OUTGIVINGLOG", name = "分发日志") -public class OutGivingLog extends BaseWorkspaceModel { - /** - * 分发id - */ - private String outGivingId; - /** - * 状态 - * - * @see OutGivingNodeProject.Status - */ - private Integer status; - /** - * 开始时间 - */ - private Long startTime; - /** - * 结束时间 - */ - private Long endTime; - /** - * 处理消息 - */ - private String result; - /** - * 节点id - */ - private String nodeId; - /** - * 项目id - */ - private String projectId; - - - public String getOutGivingId() { - return outGivingId; - } - - public void setOutGivingId(String outGivingId) { - this.outGivingId = outGivingId; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public Long getStartTime() { - return startTime; - } - - public void setStartTime(Long startTime) { - this.startTime = startTime; - } - - public Long getEndTime() { - return endTime; - } - - public void setEndTime(Long endTime) { - this.endTime = endTime; - } - - public String getResult() { - return result; - } - - public void setResult(String result) { - this.result = result; - } - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/log/SshTerminalExecuteLog.java b/modules/server/src/main/java/io/jpom/model/log/SshTerminalExecuteLog.java deleted file mode 100644 index 68d33fa87b..0000000000 --- a/modules/server/src/main/java/io/jpom/model/log/SshTerminalExecuteLog.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.log; - -import cn.hutool.core.util.StrUtil; -import io.jpom.JpomApplication; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.h2db.TableName; - -/** - * ssh 终端执行日志 - * - * @author jiangzeyin - * @date 2021/08/04 - */ -@TableName(value = "SSHTERMINALEXECUTELOG",name = "ssh 终端执行日志") -public class SshTerminalExecuteLog extends BaseWorkspaceModel { - /** - * 操作ip - */ - private String ip; - /** - * 用户ip - */ - private String userId; - /** - * sshid - */ - private String sshId; - /** - * 名称 - */ - private String sshName; -// /** -// * 操作时间 -// */ -// private long optTime; - /** - * 执行的命令 - */ - private String commands; - /** - * 浏览器标识 - */ - private String userAgent; - - /** - * 是否拒绝执行 - */ - private Boolean refuse; - - public Boolean getRefuse() { - return refuse; - } - - public void setRefuse(Boolean refuse) { - this.refuse = refuse; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = StrUtil.maxLength(userAgent, 280); - } - - - /** - * 操作id - */ - public SshTerminalExecuteLog() { - } - - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - if (UserModel.SYSTEM_OCCUPY_NAME.equals(userId)) { - this.userId = JpomApplication.SYSTEM_ID; - } else { - this.userId = userId; - } - } - -// public long getOptTime() { -// return optTime; -// } -// -// public void setOptTime(long optTime) { -// this.optTime = optTime; -// } - - public String getSshId() { - return sshId; - } - - public void setSshId(String sshId) { - this.sshId = sshId; - } - - public String getCommands() { - return commands; - } - - public void setCommands(String commands) { - this.commands = commands; - } - - - public String getSshName() { - return sshName; - } - - public void setSshName(String sshName) { - this.sshName = sshName; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/log/SystemMonitorLog.java b/modules/server/src/main/java/io/jpom/model/log/SystemMonitorLog.java deleted file mode 100644 index 55e6d21301..0000000000 --- a/modules/server/src/main/java/io/jpom/model/log/SystemMonitorLog.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.log; - -import io.jpom.model.BaseDbModel; -import io.jpom.service.h2db.TableName; - -/** - * 系统监控记录 - * - * @author Arno - * @date 2019/9/16 - */ -@TableName(value = "SYSTEMMONITORLOG", name = "节点监控记录") -public class SystemMonitorLog extends BaseDbModel { - - /** - * 节点id - */ - private String nodeId; - /** - * 监控时间 - */ - private long monitorTime; - /** - * 占用cpu - */ - private double occupyCpu; - /** - * 占用内存 (总共) - */ - private double occupyMemory; - /** - * 占用内存 (使用) @author jzy - */ - private double occupyMemoryUsed; - /** - * 占用磁盘 - */ - private double occupyDisk; - - public String getNodeId() { - return nodeId; - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public long getMonitorTime() { - return monitorTime; - } - - public void setMonitorTime(long monitorTime) { - this.monitorTime = monitorTime; - } - - public double getOccupyCpu() { - return occupyCpu; - } - - public void setOccupyCpu(double occupyCpu) { - this.occupyCpu = occupyCpu; - } - - public double getOccupyMemory() { - return occupyMemory; - } - - public void setOccupyMemory(double occupyMemory) { - this.occupyMemory = occupyMemory; - } - - public double getOccupyDisk() { - return occupyDisk; - } - - public void setOccupyDisk(double occupyDisk) { - this.occupyDisk = occupyDisk; - } - - public double getOccupyMemoryUsed() { - return occupyMemoryUsed; - } - - public void setOccupyMemoryUsed(double occupyMemoryUsed) { - this.occupyMemoryUsed = occupyMemoryUsed; - } -} diff --git a/modules/server/src/main/java/io/jpom/model/log/UserOperateLogV1.java b/modules/server/src/main/java/io/jpom/model/log/UserOperateLogV1.java deleted file mode 100644 index 29f8c1f01e..0000000000 --- a/modules/server/src/main/java/io/jpom/model/log/UserOperateLogV1.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.log; - -import cn.hutool.core.util.StrUtil; -import io.jpom.model.BaseEnum; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.service.h2db.TableName; - -/** - * 用户操作日志 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -@TableName(value = "USEROPERATELOGV1", name = "用户操作日志") -public class UserOperateLogV1 extends BaseWorkspaceModel { - /** - * 操作ip - */ - private String ip; - /** - * 用户ip - */ - private String userId; - /** - * 节点id - */ - private String nodeId; - /** - * 操作时间 - */ - private Long optTime; - /** - * 操作状态 - */ - private Integer optStatus = Status.Fail.getCode(); - /** - * 完整消息 - */ - private String resultMsg; - /** - * 操作id - * 用于socket 回话回调更新 - */ - @Deprecated - private String reqId; - /** - * 请求参数 - */ - private String reqData; - /** - * 数据id - */ - private String dataId; - /** - * 浏览器标识 - */ - private String userAgent; - - private String classFeature; - private String methodFeature; - - public String getClassFeature() { - return classFeature; - } - - public void setClassFeature(String classFeature) { - this.classFeature = classFeature; - } - - public String getMethodFeature() { - return methodFeature; - } - - public void setMethodFeature(String methodFeature) { - this.methodFeature = methodFeature; - } - - public String getReqData() { - return reqData; - } - - public void setReqData(String reqData) { - this.reqData = StrUtil.maxLength(reqData, 999999990); - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = StrUtil.maxLength(userAgent, 280); - } - - public String getDataId() { - return StrUtil.emptyToDefault(dataId, StrUtil.DASHED); - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - @Override - public void setId(String id) { - super.setId(id); - this.setReqId(id); - } - - // public UserOperateLogV1(String reqId) { -// if (reqId == null) { -// this.reqId = IdUtil.fastUUID(); -// } else { -// this.reqId = reqId; -// } -// } - -// /** -// * 操作id -// */ -// public UserOperateLogV1() { -// } - - public String getReqId() { - return reqId; - } - - public void setReqId(String reqId) { - this.reqId = reqId; - } - - public String getNodeId() { - return StrUtil.emptyToDefault(nodeId, StrUtil.DASHED); - } - - public void setNodeId(String nodeId) { - this.nodeId = nodeId; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public Long getOptTime() { - return optTime; - } - - public void setOptTime(Long optTime) { - this.optTime = optTime; - } - - public int getOptStatus() { - return optStatus; - } - - /** - * 获取执行结果的描述消息 - * - * @return 成功/ 失败:状态码 - */ - public String getOptStatusMsg() { - if (getOptStatus() == Status.Success.getCode()) { - return Status.Success.getDesc(); - } - return Status.Fail.getDesc() + ":" + getOptStatus(); - } - - public void setOptStatus(int optStatus) { - this.optStatus = optStatus; - } - - public String getResultMsg() { - return resultMsg; - } - - public void setResultMsg(String resultMsg) { - this.resultMsg = StrUtil.maxLength(resultMsg, 999999990); - } - - /** - * 状态状态 - */ - public enum Status implements BaseEnum { - /** - * 请求状态码200 为成功 - */ - Success(200, "成功"), - Fail(0, "失败"); - private final int code; - private final String desc; - - @Override - public int getCode() { - return code; - } - - @Override - public String getDesc() { - return desc; - } - - - Status(int code, String desc) { - this.code = code; - this.desc = desc; - } - } -} diff --git a/modules/server/src/main/java/io/jpom/model/package-info.java b/modules/server/src/main/java/io/jpom/model/package-info.java deleted file mode 100644 index b4920ffad1..0000000000 --- a/modules/server/src/main/java/io/jpom/model/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/model/vo/BuildHistoryLogVo.java b/modules/server/src/main/java/io/jpom/model/vo/BuildHistoryLogVo.java deleted file mode 100644 index 280c45bd7d..0000000000 --- a/modules/server/src/main/java/io/jpom/model/vo/BuildHistoryLogVo.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 码之科技工作室 -// * -// * 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 io.jpom.model.vo; -// -//import cn.hutool.core.io.FileUtil; -//import io.jpom.build.BuildUtil; -//import io.jpom.model.data.BuildInfoModel; -//import io.jpom.model.log.BuildHistoryLog; -// -//import java.io.File; -// -///** -// * 构建产物vo -// * -// * @author bwcx_jzy -// * @date 2019/7/17 -// */ -//public class BuildHistoryLogVo extends BuildHistoryLog { -// -// private String releaseDesc; -// /** -// * 是否存在构建产物 -// */ -// private boolean hashFile; -// /** -// * 是否存在日志 -// */ -// private boolean hasLog; -// -// public boolean isHasLog() { -// File file = BuildUtil.getLogFile(getBuildDataId(), getBuildNumberId()); -// hasLog = FileUtil.exist(file); -// return hasLog; -// } -// -// public void setHasLog(boolean hasLog) { -// this.hasLog = hasLog; -// } -// -// public boolean isHashFile() { -// File file = BuildUtil.getHistoryPackageFile(getBuildDataId(), getBuildNumberId(), getResultDirFile()); -// hashFile = FileUtil.exist(file); -// return hashFile; -// } -// -// public void setHashFile(boolean hashFile) { -// this.hashFile = hashFile; -// } -// -// public String getBuildIdStr() { -// return BuildInfoModel.getBuildIdStr(getBuildNumberId()); -// } -// -// public void setReleaseDesc(String releaseDesc) { -// this.releaseDesc = releaseDesc; -// } -// -//// /** -//// * 发布描述 -//// * -//// * @return 描述 -//// */ -//// public String getReleaseDesc() { -//// if (releaseDesc == null) { -//// int releaseMethod = getReleaseMethod(); -//// BuildReleaseMethod releaseMethod1 = BaseEnum.getEnum(BuildReleaseMethod.class, releaseMethod); -//// if (releaseMethod1 == null) { -//// return BuildReleaseMethod.No.getDesc(); -//// } -//// String releaseMethodDataId = getReleaseMethodDataId(); -//// switch (releaseMethod1) { -//// case Project: { -//// String[] datas = releaseMethodDataId.split(":"); -//// return String.format("【%s】节点【%s】项目", datas[0], datas[1]); -//// } -//// case Outgiving: { -//// OutGivingServer outGivingServer = SpringUtil.getBean(OutGivingServer.class); -//// OutGivingModel item = outGivingServer.getItem(releaseMethodDataId); -//// if (item == null) { -//// return "-"; -//// } -//// return "【" + item.getName() + "】分发"; -//// } -//// case No: -//// default: -//// return releaseMethod1.getDesc(); -//// } -//// } -//// return releaseDesc; -//// } -// -//} diff --git a/modules/server/src/main/java/io/jpom/model/vo/BuildModelVo.java b/modules/server/src/main/java/io/jpom/model/vo/BuildModelVo.java deleted file mode 100644 index fcae09761e..0000000000 --- a/modules/server/src/main/java/io/jpom/model/vo/BuildModelVo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.model.vo; -// -//import io.jpom.build.BuildUtil; -//import io.jpom.model.data.BuildModel; -// -//import java.io.File; -// -///** -// * vo -// * -// * @author bwcx_jzy -// * @date 2019/8/14 -// */ -//@Deprecated -//public class BuildModelVo extends BuildModel { -// -// /** -// * 代码是否存在 -// */ -// private boolean sourceExist; -// -// public boolean isSourceExist() { -// File source = BuildUtil.getSource(this); -// sourceExist = source.exists(); -// return sourceExist; -// } -// -// public void setSourceExist(boolean sourceExist) { -// this.sourceExist = sourceExist; -// } -//} diff --git a/modules/server/src/main/java/io/jpom/monitor/EmailUtil.java b/modules/server/src/main/java/io/jpom/monitor/EmailUtil.java deleted file mode 100644 index a8a9cc8178..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/EmailUtil.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.mail.MailAccount; -import cn.hutool.extra.mail.MailUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.data.MailAccountModel; -import io.jpom.model.data.MonitorModel; -import io.jpom.service.system.SystemParametersServer; - -import java.util.Objects; - -/** - * 邮件工具 - * - * @author Arno - */ -public class EmailUtil implements INotify { - - private static SystemParametersServer systemParametersServer; - private static MailAccountModel config; - - @Override - public void send(MonitorModel.Notify notify, String title, String context) { - MailAccount mailAccount = getAccount(); - MailUtil.send(mailAccount, StrUtil.split(notify.getValue(), StrUtil.COMMA), title, context, false); - } - - private static void init() { - if (systemParametersServer == null) { - systemParametersServer = SpringUtil.getBean(SystemParametersServer.class); - } - } - - /** - * 加载配置信息 - */ - public static void refreshConfig() { - if (config == null) { - init(); - } - config = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); - } - - public static MailAccount getAccount() { - if (config == null) { - // 没有数据才加载 - refreshConfig(); - } - return getAccount(config); - } - - public static MailAccount getAccount(MailAccountModel config) { - Objects.requireNonNull(config, "获取邮箱信息失败"); - MailAccount mailAccount = new MailAccount(); - mailAccount.setUser(config.getUser()); - mailAccount.setPass(config.getPass()); - mailAccount.setFrom(config.getFrom()); - mailAccount.setPort(config.getPort()); - mailAccount.setHost(config.getHost()); - // - mailAccount.setTimeout(10 * 1000); - mailAccount.setConnectionTimeout(10 * 1000); - // - if (config.getSslEnable() != null && config.getSslEnable()) { - mailAccount.setSslEnable(config.getSslEnable()); - if (config.getSocketFactoryPort() != null) { - mailAccount.setSocketFactoryPort(config.getSocketFactoryPort()); - } - } - mailAccount.setAuth(true); - return mailAccount; - } - - /** - * 发送邮箱 - * - * @param email 收件人 - * @param title 标题 - * @param context 内容 - */ - public static void send(String email, String title, String context) { - MailAccount mailAccount = getAccount(); - MailUtil.send(mailAccount, email, title, context, false); - } -} diff --git a/modules/server/src/main/java/io/jpom/monitor/INotify.java b/modules/server/src/main/java/io/jpom/monitor/INotify.java deleted file mode 100644 index abeca14e4f..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/INotify.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import io.jpom.model.data.MonitorModel; - -/** - * 通知接口 - * - * @author bwcx_jzy - * @date 2019/7/13 - */ -public interface INotify { - - /** - * 发送通知 - * - * @param notify 通知方式 - * @param title 标题 - * @param context 内容 - */ - void send(MonitorModel.Notify notify, String title, String context); -} diff --git a/modules/server/src/main/java/io/jpom/monitor/Monitor.java b/modules/server/src/main/java/io/jpom/monitor/Monitor.java deleted file mode 100644 index 9ac9c903aa..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/Monitor.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.cron.CronUtil; -import cn.hutool.cron.task.Task; -import cn.hutool.db.sql.Direction; -import cn.hutool.db.sql.Order; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.Cycle; -import io.jpom.model.data.MonitorModel; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.log.MonitorNotifyLog; -import io.jpom.service.dblog.DbMonitorNotifyLogService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.node.NodeService; -import io.jpom.service.user.UserService; -import io.jpom.util.CronUtils; - -import java.util.List; - -/** - * 监听调度 - * - * @author bwcx_jzy - * @date 2019/7/12 - **/ -public class Monitor implements Task { - - private static final String CRON_ID = "Monitor"; - - private static DbMonitorNotifyLogService dbMonitorNotifyLogService; - - - /** - * 开启调度 - */ - public static void start() { - Task task = CronUtil.getScheduler().getTask(CRON_ID); - if (task == null) { - CronUtil.schedule(CRON_ID, Cycle.one.getCronPattern().toString(), new Monitor()); - CronUtils.start(); - } - dbMonitorNotifyLogService = SpringUtil.getBean(DbMonitorNotifyLogService.class); - } - - public static void stop() { - CronUtil.remove(CRON_ID); - } - - @Override - public void execute() { - long time = System.currentTimeMillis(); - MonitorService monitorService = SpringUtil.getBean(MonitorService.class); - // - List monitorModels = monitorService.listRunByCycle(Cycle.one); - // - if (Cycle.five.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - monitorModels.addAll(monitorService.listRunByCycle(Cycle.five)); - } - // - if (Cycle.ten.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - monitorModels.addAll(monitorService.listRunByCycle(Cycle.ten)); - } - // - if (Cycle.thirty.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - monitorModels.addAll(monitorService.listRunByCycle(Cycle.thirty)); - } - // - this.checkList(monitorModels); - } - - private void checkList(List monitorModels) { - if (monitorModels == null || monitorModels.isEmpty()) { - return; - } - monitorModels.forEach(monitorModel -> { - List nodeProjects = monitorModel.projects(); - List notifyUser = monitorModel.notifyUser(); - if (CollUtil.isEmpty(nodeProjects) || CollUtil.isEmpty(notifyUser)) { - return; - } - // - this.checkNode(monitorModel); - }); - } - - private void checkNode(MonitorModel monitorModel) { - List nodeProjects = monitorModel.projects(); - NodeService nodeService = SpringUtil.getBean(NodeService.class); - nodeProjects.forEach(nodeProject -> { - String nodeId = nodeProject.getNode(); - NodeModel nodeModel = nodeService.getByKey(nodeId); - if (nodeModel == null) { - return; - } - this.reqNodeStatus(monitorModel, nodeModel, nodeProject.getProjects()); - }); - } - - private void reqNodeStatus(MonitorModel monitorModel, NodeModel nodeModel, List projects) { - if (projects == null || projects.isEmpty()) { - return; - } - projects.forEach(id -> { - // 获取上次状态 - boolean pre = getPreStatus(monitorModel.getId(), nodeModel.getId(), id); - // - String title = null; - String context = null; - try { - //查询项目运行状态 - JsonMessage jsonMessage = NodeForward.requestBySys(nodeModel, NodeUrl.Manage_GetProjectStatus, "id", id, "getCopy", true); - if (jsonMessage.getCode() == HttpStatus.HTTP_OK) { - JSONObject jsonObject = jsonMessage.getData(); - int pid = jsonObject.getIntValue("pId"); - boolean runStatus = pid > 0; - this.checkNotify(monitorModel, nodeModel, id, null, runStatus); - // 检查副本 - JSONArray copys = jsonObject.getJSONArray("copys"); - if (CollUtil.isNotEmpty(copys)) { - copys.forEach(o -> { - JSONObject jsonObject1 = (JSONObject) o; - String copyId = jsonObject1.getString("copyId"); - boolean status = jsonObject1.getBooleanValue("status"); - Monitor.this.checkNotify(monitorModel, nodeModel, id, copyId, status); - }); - } - } else { - title = StrUtil.format("【{}】节点的状态码异常:{}", nodeModel.getName(), jsonMessage.getCode()); - context = jsonMessage.toString(); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("监控 {} 节点异常 {}", nodeModel.getName(), e.getMessage()); - // - title = StrUtil.format("【{}】节点的运行状态异常", nodeModel.getName()); - context = ExceptionUtil.stacktraceToString(e); - } - if (!pre) { - // 上一次也是异常,并且当前也是异常 - return; - } - MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); - monitorNotifyLog.setStatus(false); - monitorNotifyLog.setTitle(title); - monitorNotifyLog.setContent(context); - monitorNotifyLog.setCreateTime(System.currentTimeMillis()); - monitorNotifyLog.setNodeId(nodeModel.getId()); - monitorNotifyLog.setProjectId(id); - monitorNotifyLog.setMonitorId(monitorModel.getId()); - // - List notify = monitorModel.notifyUser(); - this.notifyMsg(notify, monitorNotifyLog); - }); - } - - /** - * 检查状态 - * - * @param monitorModel 监控信息 - * @param nodeModel 节点信息 - * @param id 项目id - * @param copyId 副本id - * @param runStatus 当前运行状态 - */ - private void checkNotify(MonitorModel monitorModel, NodeModel nodeModel, String id, String copyId, boolean runStatus) { - // 获取上次状态 - String projectCopyId = id; - String copyMsg = StrUtil.EMPTY; - if (StrUtil.isNotEmpty(copyId)) { - projectCopyId = StrUtil.format("{}:{}", id, copyId); - copyMsg = StrUtil.format("副本:{}、", copyId); - } - boolean pre = getPreStatus(monitorModel.getId(), nodeModel.getId(), projectCopyId); - String title = null; - String context = null; - //查询项目运行状态 - if (runStatus) { - if (!pre) { - // 上次是异常状态 - title = StrUtil.format("【{}】节点的【{}】项目{}已经恢复正常运行", nodeModel.getName(), id, copyMsg); - context = ""; - } - } else { - // - if (monitorModel.autoRestart()) { - // 执行重启 - try { - JsonMessage reJson = NodeForward.requestBySys(nodeModel, NodeUrl.Manage_Restart, "id", id, "copyId", copyId); - if (reJson.getCode() == HttpStatus.HTTP_OK) { - // 重启成功 - runStatus = true; - title = StrUtil.format("【{}】节点的【{}】项目{}已经停止,已经执行重启操作,结果成功", nodeModel.getName(), id, copyMsg); - } else { - title = StrUtil.format("【{}】节点的【{}】项目{}已经停止,已经执行重启操作,结果失败", nodeModel.getName(), id, copyMsg); - } - context = "重启结果:" + reJson.toString(); - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行重启操作", e); - title = StrUtil.format("【{}】节点的【{}】项目{}已经停止,重启操作异常", nodeModel.getName(), id, copyMsg); - context = ExceptionUtil.stacktraceToString(e); - } - } else { - title = StrUtil.format("【{}】节点的【{}】项目{}已经没有运行", nodeModel.getName(), id, copyMsg); - context = "请及时检查"; - } - } - if (!pre && !runStatus) { - // 上一次也是异常,并且当前也是异常 - return; - } - MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); - monitorNotifyLog.setStatus(runStatus); - monitorNotifyLog.setTitle(title); - monitorNotifyLog.setContent(context); - monitorNotifyLog.setCreateTime(System.currentTimeMillis()); - monitorNotifyLog.setNodeId(nodeModel.getId()); - monitorNotifyLog.setProjectId(projectCopyId); - monitorNotifyLog.setMonitorId(monitorModel.getId()); - // - List notify = monitorModel.notifyUser(); - this.notifyMsg(notify, monitorNotifyLog); - } - - /** - * 获取上次是否也为异常状态 - * - * @param monitorId 监控id - * @param nodeId 节点id - * @param projectId 项目id - * @return true 为正常状态,false 异常状态 - */ - private boolean getPreStatus(String monitorId, String nodeId, String projectId) { - // 检查是否已经触发通知 - - MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); - monitorNotifyLog.setNodeId(nodeId); - monitorNotifyLog.setProjectId(projectId); - monitorNotifyLog.setMonitorId(monitorId); - - List queryList = dbMonitorNotifyLogService.queryList(monitorNotifyLog, 1, new Order("createTime", Direction.DESC)); - MonitorNotifyLog entity1 = CollUtil.getFirst(queryList); - return entity1 == null || entity1.isStatus(); - } - - private void notifyMsg(final List notify, final MonitorNotifyLog monitorNotifyLog) { - UserService userService = SpringUtil.getBean(UserService.class); - // 发送通知 - if (monitorNotifyLog.getTitle() != null) { - // 报警状态 - MonitorService monitorService = SpringUtil.getBean(MonitorService.class); - monitorService.setAlarm(monitorNotifyLog.getMonitorId(), !monitorNotifyLog.isStatus()); - // - notify.forEach(notifyUser -> { - UserModel item = userService.getByKey(notifyUser); - boolean success = false; - if (item != null) { - // 邮箱 - String email = item.getEmail(); - if (StrUtil.isNotEmpty(email)) { - monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); - MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.mail, email); - monitorNotifyLog.setNotifyStyle(notify1.getStyle()); - monitorNotifyLog.setNotifyObject(notify1.getValue()); - // - dbMonitorNotifyLogService.insert(monitorNotifyLog); - send(notify1, monitorNotifyLog.getId(), monitorNotifyLog.getTitle(), monitorNotifyLog.getContent()); - success = true; - } - // dingding - String dingDing = item.getDingDing(); - if (StrUtil.isNotEmpty(dingDing)) { - monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); - MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.dingding, dingDing); - monitorNotifyLog.setNotifyStyle(notify1.getStyle()); - monitorNotifyLog.setNotifyObject(notify1.getValue()); - // - dbMonitorNotifyLogService.insert(monitorNotifyLog); - send(notify1, monitorNotifyLog.getId(), monitorNotifyLog.getTitle(), monitorNotifyLog.getContent()); - success = true; - } - // 企业微信 - String workWx = item.getWorkWx(); - if (StrUtil.isNotEmpty(workWx)) { - monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); - MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.workWx, workWx); - monitorNotifyLog.setNotifyStyle(notify1.getStyle()); - monitorNotifyLog.setNotifyObject(notify1.getValue()); - // - dbMonitorNotifyLogService.insert(monitorNotifyLog); - send(notify1, monitorNotifyLog.getId(), monitorNotifyLog.getTitle(), monitorNotifyLog.getContent()); - success = true; - } - } - if (success) { - return; - } - monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); - monitorNotifyLog.setNotifyObject("报警联系人异常"); - monitorNotifyLog.setNotifyStyle(MonitorModel.NotifyType.mail.getCode()); - monitorNotifyLog.setNotifyStatus(false); - monitorNotifyLog.setNotifyError("报警联系人异常:" + (item == null ? "联系人不存在" : "")); - dbMonitorNotifyLogService.insert(monitorNotifyLog); - }); - } - } - - private void send(MonitorModel.Notify notify, String logId, String title, String context) { - // 异常发送 - ThreadUtil.execute(() -> { - try { - NotifyUtil.send(notify, title, context); - dbMonitorNotifyLogService.updateStatus(logId, true, null); - } catch (Exception e) { - DefaultSystemLog.getLog().error("发送报警通知异常", e); - dbMonitorNotifyLogService.updateStatus(logId, false, ExceptionUtil.stacktraceToString(e)); - } - }); - } -} diff --git a/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java b/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java deleted file mode 100644 index 6eb0791a54..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.cron.CronUtil; -import cn.hutool.cron.pattern.CronPattern; -import cn.hutool.cron.task.Task; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.Cycle; -import io.jpom.model.data.NodeModel; -import io.jpom.model.log.SystemMonitorLog; -import io.jpom.service.dblog.DbSystemMonitorLogService; -import io.jpom.service.node.NodeService; -import io.jpom.util.CronUtils; - -import java.util.List; - -/** - * 节点监控 - * - * @author bwcx_jzy - * @date 2019/9/17 - */ -public class NodeMonitor implements Task { - - private static final String CRON_ID = "NodeMonitor"; - - private static DbSystemMonitorLogService dbSystemMonitorLogService; - - /** - * 开启调度 - */ - public static void start() { - Task task = CronUtil.getScheduler().getTask(CRON_ID); - if (task == null) { - CronPattern cronPattern = Cycle.seconds30.getCronPattern(); - CronUtil.schedule(CRON_ID, cronPattern.toString(), new NodeMonitor()); - CronUtils.start(); - } - dbSystemMonitorLogService = SpringUtil.getBean(DbSystemMonitorLogService.class); - } - - public static void stop() { - CronUtil.remove(CRON_ID); - } - - @Override - public void execute() { - long time = System.currentTimeMillis(); - NodeService nodeService = SpringUtil.getBean(NodeService.class); - // - List nodeModels = nodeService.listByCycle(Cycle.seconds30); - // - if (Cycle.one.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.one)); - } - // - if (Cycle.five.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.five)); - } - // - if (Cycle.ten.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.ten)); - } - // - if (Cycle.thirty.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.thirty)); - } - // - this.checkList(nodeModels); - } - - private void checkList(List nodeModels) { - if (nodeModels == null || nodeModels.isEmpty()) { - return; - } - nodeModels.forEach(nodeModel -> ThreadUtil.execute(() -> { - try { - getNodeInfo(nodeModel); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取节点监控信息失败:{}", e.getMessage()); - } - })); - } - - private void getNodeInfo(NodeModel nodeModel) { - JsonMessage message = NodeForward.request(nodeModel, null, NodeUrl.GetDirectTop); - JSONObject jsonObject = message.getData(); - if (jsonObject == null) { - return; - } - double disk = jsonObject.getDoubleValue("disk"); - if (disk <= 0) { - return; - } - // - SystemMonitorLog log = new SystemMonitorLog(); - log.setId(IdUtil.fastSimpleUUID()); - log.setOccupyMemory(jsonObject.getDoubleValue("memory")); - log.setOccupyMemoryUsed(jsonObject.getDoubleValue("memoryUsed")); - log.setOccupyDisk(disk); - log.setOccupyCpu(jsonObject.getDoubleValue("cpu")); - log.setMonitorTime(jsonObject.getLongValue("time")); - log.setNodeId(nodeModel.getId()); - dbSystemMonitorLogService.insert(log); - } -} diff --git a/modules/server/src/main/java/io/jpom/monitor/NotifyUtil.java b/modules/server/src/main/java/io/jpom/monitor/NotifyUtil.java deleted file mode 100644 index 2c10a078a6..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/NotifyUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import io.jpom.model.BaseEnum; -import io.jpom.model.data.MonitorModel; - -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 通知util - * - * @author bwcx_jzy - * @date 2019/7/13 - */ -public class NotifyUtil { - - private static final Map NOTIFY_MAP = new ConcurrentHashMap<>(); - - static { - NOTIFY_MAP.put(MonitorModel.NotifyType.dingding, new WebHookUtil()); - NOTIFY_MAP.put(MonitorModel.NotifyType.mail, new EmailUtil()); - NOTIFY_MAP.put(MonitorModel.NotifyType.workWx, new WebHookUtil()); - } - - /** - * 发送报警消息 - * - * @param notify 通知方式 - * @param title 描述 - * @param context 内容 - */ - public static void send(MonitorModel.Notify notify, String title, String context) { - int style = notify.getStyle(); - MonitorModel.NotifyType notifyType = BaseEnum.getEnum(MonitorModel.NotifyType.class, style); - Objects.requireNonNull(notifyType); - // - INotify iNotify = NOTIFY_MAP.get(notifyType); - Objects.requireNonNull(iNotify); - iNotify.send(notify, title, context); - } - -} diff --git a/modules/server/src/main/java/io/jpom/monitor/WebHookUtil.java b/modules/server/src/main/java/io/jpom/monitor/WebHookUtil.java deleted file mode 100644 index 7936d61ab5..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/WebHookUtil.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.data.MonitorModel; -import org.springframework.http.MediaType; - -/** - * 钉钉工具 - * - * @author Arno - */ -public class WebHookUtil implements INotify { - - /** - * 发送钉钉群自定义机器人消息 - * - * @param notify 通知对象 - * @param title 描述标签 - * @param context 消息内容 - */ - @Override - public void send(MonitorModel.Notify notify, String title, String context) { - JSONObject text = new JSONObject(); - JSONObject param = new JSONObject(); - //消息内容 - text.put("content", title + "\n" + context); - param.put("msgtype", "text"); - param.put("text", text); - HttpRequest request = HttpUtil. - createPost(notify.getValue()). - contentType(MediaType.APPLICATION_JSON_VALUE). - body(param.toJSONString()); - request.execute(); - } -} diff --git a/modules/server/src/main/java/io/jpom/monitor/package-info.java b/modules/server/src/main/java/io/jpom/monitor/package-info.java deleted file mode 100644 index f417f241a4..0000000000 --- a/modules/server/src/main/java/io/jpom/monitor/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; diff --git a/modules/server/src/main/java/io/jpom/outgiving/OutGivingItemRun.java b/modules/server/src/main/java/io/jpom/outgiving/OutGivingItemRun.java deleted file mode 100644 index d7cb0992c9..0000000000 --- a/modules/server/src/main/java/io/jpom/outgiving/OutGivingItemRun.java +++ /dev/null @@ -1,168 +0,0 @@ -package io.jpom.outgiving; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.AfterOpt; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.OutGivingModel; -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.model.data.UserModel; -import io.jpom.model.log.OutGivingLog; -import io.jpom.service.dblog.DbOutGivingLogService; -import io.jpom.service.node.NodeService; -import io.jpom.service.node.OutGivingServer; -import org.springframework.util.Assert; - -import java.io.File; -import java.util.List; -import java.util.concurrent.Callable; - -/** - * @author bwcx_jzy - * @since 2021/12/10 - */ -public class OutGivingItemRun implements Callable { - - private final String outGivingId; - private final OutGivingNodeProject outGivingNodeProject; - private final NodeModel nodeModel; - private final File file; - private final AfterOpt afterOpt; - private final UserModel userModel; - private final boolean unzip; - private final boolean clearOld; - /** - * 数据库记录id - */ - private final String logId; - - public OutGivingItemRun(OutGivingModel item, - OutGivingNodeProject outGivingNodeProject, - File file, - UserModel userModel, - boolean unzip) { - this.outGivingId = item.getId(); - this.unzip = unzip; - this.clearOld = item.isClearOld(); - this.outGivingNodeProject = outGivingNodeProject; - this.file = file; - this.afterOpt = ObjectUtil.defaultIfNull(EnumUtil.likeValueOf(AfterOpt.class, item.getAfterOpt()), AfterOpt.No); - // - NodeService nodeService = SpringUtil.getBean(NodeService.class); - this.nodeModel = nodeService.getByKey(outGivingNodeProject.getNodeId()); - // - this.userModel = userModel; - this.logId = IdUtil.fastSimpleUUID(); - } - - @Override - public OutGivingNodeProject.Status call() { - OutGivingNodeProject.Status result; - try { - this.updateStatus(this.outGivingId, this.outGivingNodeProject, - OutGivingNodeProject.Status.Ing, "开始分发"); - // - JsonMessage jsonMessage = OutGivingRun.fileUpload(file, - this.outGivingNodeProject.getProjectId(), - unzip, - afterOpt, - this.nodeModel, this.userModel, this.clearOld); - if (jsonMessage.getCode() == HttpStatus.HTTP_OK) { - result = OutGivingNodeProject.Status.Ok; - } else { - result = OutGivingNodeProject.Status.Fail; - } - this.updateStatus(this.outGivingId, this.outGivingNodeProject, result, jsonMessage.toString()); - } catch (Exception e) { - DefaultSystemLog.getLog().error(this.outGivingNodeProject.getNodeId() + " " + this.outGivingNodeProject.getProjectId() + " " + "分发异常保存", e); - result = OutGivingNodeProject.Status.Fail; - this.updateStatus(this.outGivingId, this.outGivingNodeProject, result, "error:" + e.getMessage()); - } - return result; - } - - /** - * 更新状态 - * - * @param outGivingId 分发id - * @param outGivingNodeProjectItem 分发项 - * @param status 状态 - * @param msg 消息描述 - */ - public void updateStatus(String outGivingId, - OutGivingNodeProject outGivingNodeProjectItem, - OutGivingNodeProject.Status status, - String msg) { - updateStatus(this.logId, outGivingId, outGivingNodeProjectItem, status, msg, this.userModel.getId()); - } - - /** - * 更新状态 - * - * @param logId 日志ID - * @param outGivingId 分发id - * @param outGivingNodeProjectItem 分发项 - * @param status 状态 - * @param msg 消息描述 - */ - public static void updateStatus(String logId, - String outGivingId, - OutGivingNodeProject outGivingNodeProjectItem, - OutGivingNodeProject.Status status, - String msg, - String userId) { - synchronized (OutGivingRun.class) { - OutGivingServer outGivingServer = SpringUtil.getBean(OutGivingServer.class); - OutGivingModel outGivingModel = outGivingServer.getByKey(outGivingId); - - List outGivingNodeProjects = outGivingModel.outGivingNodeProjectList(); - Assert.notEmpty(outGivingNodeProjects, "没有分发项目"); - OutGivingNodeProject finOutGivingNodeProject = null; - for (OutGivingNodeProject outGivingNodeProject : outGivingNodeProjects) { - if (!outGivingNodeProject.getProjectId().equalsIgnoreCase(outGivingNodeProjectItem.getProjectId()) || - !outGivingNodeProject.getNodeId().equalsIgnoreCase(outGivingNodeProjectItem.getNodeId())) { - continue; - } - outGivingNodeProject.setStatus(status.getCode()); - outGivingNodeProject.setResult(msg); - outGivingNodeProject.setLastOutGivingTime(DateUtil.now()); - // - finOutGivingNodeProject = outGivingNodeProject; - } - { - OutGivingModel outGivingModel1 = new OutGivingModel(); - outGivingModel1.setId(outGivingId); - outGivingModel1.outGivingNodeProjectList(outGivingNodeProjects); - outGivingServer.update(outGivingModel1); - } - // - OutGivingLog outGivingLog = new OutGivingLog(); - outGivingLog.setId(StrUtil.emptyToDefault(logId, IdUtil.fastSimpleUUID())); - - if (finOutGivingNodeProject != null) { - outGivingLog.setNodeId(finOutGivingNodeProject.getNodeId()); - outGivingLog.setProjectId(finOutGivingNodeProject.getProjectId()); - } - outGivingLog.setModifyUser(userId); - outGivingLog.setOutGivingId(outGivingId); - outGivingLog.setResult(msg); - outGivingLog.setStatus(status.getCode()); - DbOutGivingLogService dbOutGivingLogService = SpringUtil.getBean(DbOutGivingLogService.class); - if (status == OutGivingNodeProject.Status.Ing || status == OutGivingNodeProject.Status.Cancel) { - // 开始或者 取消都还没有记录 - dbOutGivingLogService.insert(outGivingLog); - } else { - outGivingLog.setEndTime(SystemClock.now()); - dbOutGivingLogService.update(outGivingLog); - } - } - } -} diff --git a/modules/server/src/main/java/io/jpom/outgiving/OutGivingRun.java b/modules/server/src/main/java/io/jpom/outgiving/OutGivingRun.java deleted file mode 100644 index ace456a720..0000000000 --- a/modules/server/src/main/java/io/jpom/outgiving/OutGivingRun.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.outgiving; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.AfterOpt; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.OutGivingModel; -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.model.data.UserModel; -import io.jpom.service.node.OutGivingServer; - -import java.io.File; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * 分发线程 - * - * @author bwcx_jzy - * @date 2019/7/18 - **/ -public class OutGivingRun { - - /** - * 开始异步执行分发任务 - * - * @param id 分发id - * @param file 文件 - * @param userModel 操作的用户 - * @param unzip 解压 - */ - public static void startRun(String id, - File file, - UserModel userModel, - boolean unzip) { - OutGivingServer outGivingServer = SpringUtil.getBean(OutGivingServer.class); - OutGivingModel item = outGivingServer.getByKey(id); - Objects.requireNonNull(item, "不存在分发"); - AfterOpt afterOpt = ObjectUtil.defaultIfNull(EnumUtil.likeValueOf(AfterOpt.class, item.getAfterOpt()), AfterOpt.No); - - // - List outGivingNodeProjects = item.outGivingNodeProjectList(); - // 开启线程 - if (afterOpt == AfterOpt.Order_Restart || afterOpt == AfterOpt.Order_Must_Restart) { - ThreadUtil.execute(() -> { - // 截取睡眠时间 - int sleepTime = ObjectUtil.defaultIfNull(item.getIntervalTime(), 10); - // - boolean cancel = false; - for (OutGivingNodeProject outGivingNodeProject : outGivingNodeProjects) { - if (cancel) { - OutGivingItemRun.updateStatus(null, id, outGivingNodeProject, OutGivingNodeProject.Status.Cancel, "前一个节点分发失败,取消分发", userModel.getId()); - } else { - OutGivingItemRun outGivingRun = new OutGivingItemRun(item, outGivingNodeProject, file, userModel, unzip); - OutGivingNodeProject.Status status = outGivingRun.call(); - if (status != OutGivingNodeProject.Status.Ok) { - if (afterOpt == AfterOpt.Order_Must_Restart) { - // 完整重启,不再继续剩余的节点项目 - cancel = true; - } - } - // 休眠x秒 等待之前项目正常启动 - ThreadUtil.sleep(sleepTime, TimeUnit.SECONDS); - } - } - }); - } else if (afterOpt == AfterOpt.Restart || afterOpt == AfterOpt.No) { - outGivingNodeProjects.forEach(outGivingNodeProject -> - ThreadUtil.execAsync(new OutGivingItemRun(item, outGivingNodeProject, file, userModel, unzip)) - ); - } else { - // - throw new IllegalArgumentException("Not implemented " + afterOpt.getDesc()); - } - } - - - /** - * 上传项目文件 - * - * @param file 需要上传的文件 - * @param projectId 项目id - * @param unzip 是否需要解压 - * @param afterOpt 是否需要重启 - * @param nodeModel 节点 - * @param userModel 操作用户 - * @return json - */ - public static JsonMessage fileUpload(File file, String projectId, - boolean unzip, - AfterOpt afterOpt, - NodeModel nodeModel, - UserModel userModel, - boolean clearOld) { - JSONObject data = new JSONObject(); - data.put("file", file); - data.put("id", projectId); - if (unzip) { - // 解压 - data.put("type", "unzip"); - } - if (clearOld) { - // 清空 - data.put("clearType", "clear"); - } - // 操作 - if (afterOpt != AfterOpt.No) { - data.put("after", afterOpt.getCode()); - } - return NodeForward.request(nodeModel, NodeUrl.Manage_File_Upload, userModel, data); - } -} diff --git a/modules/server/src/main/java/io/jpom/package-info.java b/modules/server/src/main/java/io/jpom/package-info.java deleted file mode 100644 index d32d9d1b42..0000000000 --- a/modules/server/src/main/java/io/jpom/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/permission/SystemPermission.java b/modules/server/src/main/java/io/jpom/permission/SystemPermission.java deleted file mode 100644 index ef0b287d7a..0000000000 --- a/modules/server/src/main/java/io/jpom/permission/SystemPermission.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.permission; - -import java.lang.annotation.*; - -/** - * 系统管理的权限 - * - * @author bwcx_jzy - * @date 2019/8/17 - */ -@Documented -@Target({ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface SystemPermission { - - /** - * 超级管理员 - * - * @return true 超级管理员 - * @see io.jpom.model.data.UserModel#SYSTEM_ADMIN - */ - boolean superUser() default false; -} diff --git a/modules/server/src/main/java/io/jpom/plugin/ThymeleafUtil.java b/modules/server/src/main/java/io/jpom/plugin/ThymeleafUtil.java deleted file mode 100644 index 6ac89e08d9..0000000000 --- a/modules/server/src/main/java/io/jpom/plugin/ThymeleafUtil.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.plugin; -// -//import cn.hutool.core.io.FileUtil; -//import cn.jiangzeyin.common.spring.SpringUtil; -//import io.jpom.common.BaseServerController; -//import io.jpom.common.interceptor.LoginInterceptor; -//import io.jpom.model.data.UserModel; -//import org.springframework.context.annotation.Configuration; -//import org.thymeleaf.context.Context; -//import org.thymeleaf.spring5.SpringTemplateEngine; -// -//import javax.annotation.Resource; -//import java.util.HashMap; -//import java.util.Map; -// -///** -// * 模板工具 -// * -// * @author bwcx_jzy -// * @date 2019/8/13 -// */ -//@Configuration -//public class ThymeleafUtil { -// /** -// * 页面变量 -// */ -// public static final String PAGE_VARIABLE = "pagePluginHtml"; -// -// @Resource -// private SpringTemplateEngine springTemplateEngine; -// -// /** -// * 模板名称需要在 classpath:templates/plugin 下 -// * -// * @param template 模板名称 -// * @param variables 变量 -// * @return 转换后的 -// */ -// public static String process(String template, Map variables) { -// Context context = new Context(); -// if (variables == null) { -// variables = new HashMap<>(10); -// } -// String normalize = FileUtil.normalize("plugin/" + template); -// // 用户变量 -// UserModel userModel = BaseServerController.getUserModel(); -// variables.put(LoginInterceptor.SESSION_NAME, userModel); -// context.setVariables(variables); -// ThymeleafUtil thymeleafUtil = SpringUtil.getBean(ThymeleafUtil.class); -// return thymeleafUtil.springTemplateEngine.process(normalize, context); -// } -//} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/BackupInfoService.java b/modules/server/src/main/java/io/jpom/service/dblog/BackupInfoService.java deleted file mode 100644 index 5e73976539..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/BackupInfoService.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.db.Entity; -import cn.hutool.db.sql.Direction; -import cn.hutool.db.sql.Order; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.common.Const; -import io.jpom.model.data.BackupInfoModel; -import io.jpom.model.enums.BackupStatusEnum; -import io.jpom.model.enums.BackupTypeEnum; -import io.jpom.service.h2db.BaseDbService; -import io.jpom.service.h2db.H2BackupService; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.system.db.DbConfig; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -import java.io.File; -import java.sql.SQLException; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 备份数据库 service - * - * @author Hotstrip - * @date 2021-11-18 - **/ -@Service -public class BackupInfoService extends BaseDbService { - - private final H2BackupService h2BackupService; - - public BackupInfoService(H2BackupService h2BackupService) { - this.h2BackupService = h2BackupService; - } - - /** - * 检查数据库备份 - */ - public void checkAutoBackup() { - Integer autoBackupIntervalDay = ServerExtConfigBean.getInstance().getAutoBackupIntervalDay(); - if (autoBackupIntervalDay == null || autoBackupIntervalDay <= 0) { - return; - } - BackupInfoModel backupInfoModel = new BackupInfoModel(); - backupInfoModel.setBackupType(3); - List infoModels = super.queryList(backupInfoModel, 1, new Order("createTimeMillis", Direction.DESC)); - BackupInfoModel first = CollUtil.getFirst(infoModels); - if (first != null) { - Long createTimeMillis = first.getCreateTimeMillis(); - long interval = SystemClock.now() - createTimeMillis; - if (interval < TimeUnit.DAYS.toMillis(autoBackupIntervalDay)) { - return; - } - } - // 执行数据库备份 - this.backupToSql(null, 3); - } - - /** - * 备份数据库 SQL 文件 - * - * @param tableNameList 需要备份的表名称列表,如果是全库备份,则不需要 - */ - public void backupToSql(final List tableNameList) { - // 判断备份类型 - int backupType = BackupTypeEnum.ALL.getCode(); - if (!CollectionUtils.isEmpty(tableNameList)) { - backupType = BackupTypeEnum.PART.getCode(); - } - this.backupToSql(tableNameList, backupType); - } - - /** - * 备份数据库 SQL 文件 - * - * @param tableNameList 需要备份的表名称列表,如果是全库备份,则不需要 - */ - public void backupToSql(final List tableNameList, int backupType) { - final String fileName = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), DatePattern.PURE_DATETIME_PATTERN); - - // 设置默认备份 SQL 的文件地址 - File file = FileUtil.file(DbConfig.getInstance().dbLocalPath(), Const.BACKUP_DIRECTORY_NAME, fileName + Const.SQL_FILE_SUFFIX); - final String backupSqlPath = FileUtil.getAbsolutePath(file); - - // 数据源参数 - final String url = DbConfig.getInstance().getDbUrl(); - - ServerExtConfigBean serverExtConfigBean = ServerExtConfigBean.getInstance(); - final String user = serverExtConfigBean.getDbUserName(); - final String pass = serverExtConfigBean.getDbUserPwd(); - - // 先构造备份信息插入数据库 - BackupInfoModel backupInfoModel = new BackupInfoModel(); - backupInfoModel.setId(IdUtil.fastSimpleUUID()); - backupInfoModel.setName(fileName); - - backupInfoModel.setBackupType(backupType); - backupInfoModel.setFilePath(backupSqlPath); - insert(backupInfoModel); - - // 开启一个子线程去执行任务,任务完成之后修改对应的数据库备份信息 - ThreadUtil.execute(() -> { - // 修改用的实体类 - BackupInfoModel backupInfo = new BackupInfoModel(); - BeanUtil.copyProperties(backupInfoModel, backupInfo); - try { - DefaultSystemLog.getLog().info("start a new Thread to execute H2 Database backup...start"); - h2BackupService.backupSql(url, user, pass, backupSqlPath, tableNameList); - // 修改备份任务执行完成 - backupInfo.setFileSize(FileUtil.size(file)); - backupInfo.setSha1Sum(SecureUtil.sha1(file)); - backupInfo.setStatus(BackupStatusEnum.SUCCESS.getCode()); - update(backupInfo); - DefaultSystemLog.getLog().info("start a new Thread to execute H2 Database backup...success"); - } catch (SQLException e) { - // 记录错误日志信息,修改备份任务执行失败 - DefaultSystemLog.getLog().error("start a new Thread to execute H2 Database backup...catch exception...message: {}, cause: {}", - e.getMessage(), e.getCause()); - backupInfo.setStatus(BackupStatusEnum.FAILED.getCode()); - update(backupInfo); - } - }); - } - - /** - * 根据 SQL 文件还原数据库 - * 还原数据库时只能同步,防止该过程中修改数据造成数据不一致 - * - * @param backupSqlPath 备份 sql 文件地址 - */ - public boolean restoreWithSql(String backupSqlPath) { - try { - long startTs = System.currentTimeMillis(); - h2BackupService.restoreBackupSql(backupSqlPath); - long endTs = System.currentTimeMillis(); - DefaultSystemLog.getLog().info("restore H2 Database backup...success...cast {} ms", - endTs - startTs); - return true; - } catch (Exception e) { - // 记录错误日志信息,返回数据库备份还原执行失败 - DefaultSystemLog.getLog().error("restore H2 Database backup...catch exception...message: {}, cause: {}", - e.getMessage(), e.getCause()); - return false; - } - } - - /** - * load table name list from h2 database - * - * @return list - */ - public List h2TableNameList() { - String sql = "show tables;"; - List list = super.query(sql); - // 筛选字段 - return list.stream() - .filter(entity -> StringUtils.hasLength(String.valueOf(entity.get(Const.TABLE_NAME)))) - .flatMap(entity -> Stream.of(String.valueOf(entity.get(Const.TABLE_NAME)))) - .distinct() - .collect(Collectors.toList()); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/BuildInfoService.java b/modules/server/src/main/java/io/jpom/service/dblog/BuildInfoService.java deleted file mode 100644 index d644d90e3b..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/BuildInfoService.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.build.BuildInfoManage; -import io.jpom.model.BaseEnum; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.enums.BuildReleaseMethod; -import io.jpom.model.enums.BuildStatus; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; -import org.springframework.util.Assert; - -import java.util.Objects; - -/** - * 构建 service 新版本,数据从数据库里面加载 - * - * @author Hotstrip - * @date 2021-08-10 - **/ -@Service -public class BuildInfoService extends BaseWorkspaceService { - - private final RepositoryService repositoryService; - - public BuildInfoService(RepositoryService repositoryService) { - this.repositoryService = repositoryService; - } - -// /** -// * load date group by group name -// * -// * @return list -// */ -// public List listGroup() { -// String sql = "select `GROUP` from " + getTableName() + " where 1=1 group by `GROUP`"; -// List list = super.query(sql); -// // 筛选字段 -// return list.stream() -// .filter(entity -> StringUtils.hasLength(String.valueOf(entity.get(Const.GROUP_STR)))) -// .flatMap(entity -> Stream.of(String.valueOf(entity.get(Const.GROUP_STR)))) -// .distinct() -// .collect(Collectors.toList()); -// } - - /** - * start build - * - * @param buildInfoModel 构建信息 - * @param userModel 用户信息 - * @param delay 延迟的时间 - * @return json - */ - public String start(final BuildInfoModel buildInfoModel, final UserModel userModel, Integer delay) { - // load repository - RepositoryModel repositoryModel = repositoryService.getByKey(buildInfoModel.getRepositoryId(), false); - Assert.notNull(repositoryModel, "仓库信息不存在"); - BuildInfoManage.create(buildInfoModel, repositoryModel, userModel, delay); - String msg = (delay == null || delay <= 0) ? "开始构建中" : "延迟" + delay + "秒后开始构建"; - return JsonMessage.getString(200, msg, buildInfoModel.getBuildId()); - } - - /** - * check status - * - * @param status 状态吗 - * @return 错误消息 - */ - public String checkStatus(Integer status) { - if (status == null) { - return null; - } - BuildStatus nowStatus = BaseEnum.getEnum(BuildStatus.class, status); - Objects.requireNonNull(nowStatus); - if (BuildStatus.Ing == nowStatus || - BuildStatus.PubIng == nowStatus) { - return "当前还在:" + nowStatus.getDesc(); - } - return null; - } - - /** - * 判断是否存在 节点关联 - * - * @param nodeId 节点ID - * @return true 关联 - */ - public boolean checkNode(String nodeId) { - Entity entity = new Entity(); - entity.set("releaseMethod", BuildReleaseMethod.Project.getCode()); - entity.set("releaseMethodDataId", StrUtil.format(" like '{}:%'", nodeId)); - return super.exists(entity); - } - - /** - * 判断是否存在 发布关联 - * - * @param dataId 数据ID - * @return true 关联 - */ - public boolean checkReleaseMethod(String dataId, BuildReleaseMethod releaseMethod) { - BuildInfoModel buildInfoModel = new BuildInfoModel(); - buildInfoModel.setReleaseMethodDataId(dataId); - buildInfoModel.setReleaseMethod(releaseMethod.getCode()); - return super.exists(buildInfoModel); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/DbBuildHistoryLogService.java b/modules/server/src/main/java/io/jpom/service/dblog/DbBuildHistoryLogService.java deleted file mode 100644 index 3631660f04..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/DbBuildHistoryLogService.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.bean.copier.CopyOptions; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Db; -import cn.hutool.db.Entity; -import cn.hutool.db.Page; -import cn.hutool.db.PageResult; -import cn.hutool.db.sql.Direction; -import cn.hutool.db.sql.Order; -import cn.hutool.http.HttpStatus; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import io.jpom.build.BuildUtil; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.enums.BuildStatus; -import io.jpom.model.log.BuildHistoryLog; -import io.jpom.service.h2db.BaseWorkspaceService; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.system.db.DbConfig; -import org.springframework.stereotype.Service; - -import java.io.File; -import java.sql.SQLException; - -/** - * 构建历史db - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@Service -public class DbBuildHistoryLogService extends BaseWorkspaceService { - - private final BuildInfoService buildService; - - public DbBuildHistoryLogService(BuildInfoService buildService) { - this.buildService = buildService; - } - - /** - * 根据 构建ID 删除构建历史 - * - * @param buildDataId 构建ID - */ - public void delByBuildId(String buildDataId) { - Entity where = new Entity(getTableName()); - where.set("buildDataId", buildDataId); - del(where); - } - - /** - * 更新状态 - * - * @param logId 记录id - * @param status 状态 - */ - public void updateLog(String logId, BuildStatus status) { - if (logId == null) { - return; - } - BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); - buildHistoryLog.setId(logId); - buildHistoryLog.setStatus(status.getCode()); - if (status != BuildStatus.PubIng) { - // 结束 - buildHistoryLog.setEndTime(SystemClock.now()); - } - this.update(buildHistoryLog); - } - - /** - * 更新状态 - * - * @param logId 记录id - * @param resultDirFile 构建产物目录 - */ - public void updateResultDirFile(String logId, String resultDirFile) { - if (logId == null || StrUtil.isEmpty(resultDirFile)) { - return; - } - - BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); - buildHistoryLog.setId(logId); - buildHistoryLog.setResultDirFile(resultDirFile); - this.update(buildHistoryLog); - } - - /** - * 清理文件并删除记录 - * - * @param logId 记录id - * @return json - */ - public JsonMessage deleteLogAndFile(String logId) { - BuildHistoryLog buildHistoryLog = getByKey(logId); - return this.deleteLogAndFile(buildHistoryLog); - } - - /** - * 清理文件并删除记录 - * - * @param buildHistoryLog 构建记录 - * @return json - */ - public JsonMessage deleteLogAndFile(BuildHistoryLog buildHistoryLog) { - if (buildHistoryLog == null) { - return new JsonMessage<>(405, "没有对应构建记录"); - } - BuildInfoModel item = buildService.getByKey(buildHistoryLog.getBuildDataId()); - if (item != null) { - File logFile = BuildUtil.getLogFile(item.getId(), buildHistoryLog.getBuildNumberId()); - if (logFile != null) { - File dataFile = logFile.getParentFile(); - if (dataFile.exists()) { - boolean s = FileUtil.del(dataFile); - if (!s) { - return new JsonMessage<>(500, "清理文件失败"); - } - } - } - } - int count = delByKey(buildHistoryLog.getId()); - return new JsonMessage<>(200, "删除成功", count + ""); - } - - @Override - public void insert(BuildHistoryLog buildHistoryLog) { - super.insert(buildHistoryLog); - // 清理总数据 - int buildMaxHistoryCount = ServerExtConfigBean.getInstance().getBuildMaxHistoryCount(); - DbConfig.autoClear(getTableName(), "startTime", buildMaxHistoryCount, - aLong -> doClearPage(1, aLong, null)); - // 清理单个 - int buildItemMaxHistoryCount = ServerExtConfigBean.getInstance().getBuildItemMaxHistoryCount(); - DbConfig.autoClear(getTableName(), "startTime", buildItemMaxHistoryCount, - entity -> entity.set("buildDataId", buildHistoryLog.getBuildDataId()), - aLong -> doClearPage(1, aLong, buildHistoryLog.getBuildDataId())); - } - - private void doClearPage(int pageNo, long time, String buildDataId) { - Entity entity = Entity.create(getTableName()); - entity.set("startTime", "< " + time); - if (buildDataId != null) { - entity.set("buildDataId", buildDataId); - } - Page page = new Page(pageNo, 10); - page.addOrder(new Order("startTime", Direction.DESC)); - PageResult pageResult; - try { - pageResult = Db.use().setWrapper((Character) null).page(entity, page); - if (pageResult.isEmpty()) { - return; - } - pageResult.forEach(entity1 -> { - CopyOptions copyOptions = new CopyOptions(); - copyOptions.setIgnoreError(true); - copyOptions.setIgnoreCase(true); - BuildHistoryLog v1 = BeanUtil.mapToBean(entity1, BuildHistoryLog.class, copyOptions); - String id = v1.getId(); - JsonMessage jsonMessage = deleteLogAndFile(id); - if (jsonMessage.getCode() != HttpStatus.HTTP_OK) { - DefaultSystemLog.getLog().info(jsonMessage.toString()); - } - }); - if (pageResult.getTotalPage() > pageResult.getPage()) { - doClearPage(pageNo + 1, time, buildDataId); - } - } catch (SQLException e) { - DefaultSystemLog.getLog().error("数据库查询异常", e); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/DbMonitorNotifyLogService.java b/modules/server/src/main/java/io/jpom/service/dblog/DbMonitorNotifyLogService.java deleted file mode 100644 index 44f3b5e5b6..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/DbMonitorNotifyLogService.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.UserModel; -import io.jpom.model.log.MonitorNotifyLog; -import io.jpom.service.h2db.BaseWorkspaceService; -import io.jpom.system.db.DbConfig; -import org.springframework.stereotype.Service; - -/** - * 监控消息 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@Service -public class DbMonitorNotifyLogService extends BaseWorkspaceService { - - - @Override - public void insert(MonitorNotifyLog monitorNotifyLog) { - try { - BaseServerController.resetInfo(UserModel.EMPTY); - if (MonitorNotifyLog.HAS_LOG_ID) { - // 兼容历史字段 - monitorNotifyLog.setLogId(StrUtil.emptyToDefault(monitorNotifyLog.getId(), IdUtil.fastSimpleUUID())); - } - // - monitorNotifyLog.setCreateTime(ObjectUtil.defaultIfNull(monitorNotifyLog.getCreateTime(), SystemClock.now())); - super.insert(monitorNotifyLog); - // - DbConfig.autoClear(getTableName(), "createTime"); - DbConfig.autoClear(getTableName(), "createTimeMillis"); - } finally { - BaseServerController.remove(); - } - } - - - /** - * 修改执行结果 - * - * @param logId 通知id - * @param status 状态 - * @param errorMsg 错误消息 - */ - public void updateStatus(String logId, boolean status, String errorMsg) { - MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); - monitorNotifyLog.setId(logId); - monitorNotifyLog.setNotifyStatus(status); - monitorNotifyLog.setNotifyError(errorMsg); - super.update(monitorNotifyLog); -// Entity entity = new Entity(); -// entity.set("notifyStatus", status); -// if (errorMsg != null) { -// entity.set("notifyError", errorMsg); -// } -// // -// Entity where = new Entity(); -// where.set("logId", logId); -// super.update(entity, where); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/DbOutGivingLogService.java b/modules/server/src/main/java/io/jpom/service/dblog/DbOutGivingLogService.java deleted file mode 100644 index 777cecfbed..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/DbOutGivingLogService.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.model.log.OutGivingLog; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -/** - * 分发日志 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@Service -public class DbOutGivingLogService extends BaseWorkspaceService { - - - @Override - public void insert(OutGivingLog outGivingLog) { - outGivingLog.setStartTime(System.currentTimeMillis()); - if (outGivingLog.getStatus() == OutGivingNodeProject.Status.Cancel.getCode()) { - outGivingLog.setEndTime(System.currentTimeMillis()); - } - super.insert(outGivingLog); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/DbSystemMonitorLogService.java b/modules/server/src/main/java/io/jpom/service/dblog/DbSystemMonitorLogService.java deleted file mode 100644 index 9a3d7b881a..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/DbSystemMonitorLogService.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import io.jpom.model.log.SystemMonitorLog; -import io.jpom.service.h2db.BaseDbService; -import org.springframework.stereotype.Service; - -/** - * @author Arno - * @date 2019/9/13 - */ -@Service -public class DbSystemMonitorLogService extends BaseDbService { - - -// -// public PageResultDto getMonitorData(long startTime, long endTime) { -// Entity entity = new Entity(SystemMonitorLog.TABLE_NAME); -// entity.set(" MONITORTIME", ">= " + startTime); -// entity.set("MONITORTIME", "<= " + endTime); -// return listPage(entity, null); -// } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/DbUserOperateLogService.java b/modules/server/src/main/java/io/jpom/service/dblog/DbUserOperateLogService.java deleted file mode 100644 index 56f63f86fd..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/DbUserOperateLogService.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.data.MonitorModel; -import io.jpom.model.data.MonitorUserOptModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.model.log.UserOperateLogV1; -import io.jpom.monitor.NotifyUtil; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.h2db.BaseDbCommonService; -import io.jpom.service.h2db.BaseWorkspaceService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.monitor.MonitorUserOptService; -import io.jpom.service.node.NodeService; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.service.node.ssh.SshService; -import io.jpom.service.system.WorkspaceService; -import io.jpom.service.user.UserService; -import io.jpom.system.db.DbConfig; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 操作日志 - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -@Service -public class DbUserOperateLogService extends BaseWorkspaceService { - - private static final Map>> CLASS_FEATURE_SERVICE = new HashMap<>(); - - static { - CLASS_FEATURE_SERVICE.put(ClassFeature.NODE, NodeService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.SSH, SshService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.OUTGIVING, SshService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.MONITOR, MonitorService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.OPT_MONITOR, MonitorUserOptService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.PROJECT, ProjectInfoCacheService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.BUILD_REPOSITORY, RepositoryService.class); - CLASS_FEATURE_SERVICE.put(ClassFeature.BUILD, BuildInfoService.class); - } - - private final MonitorUserOptService monitorUserOptService; - private final UserService userService; - private final WorkspaceService workspaceService; - - public DbUserOperateLogService(MonitorUserOptService monitorUserOptService, - UserService userService, - WorkspaceService workspaceService) { - this.monitorUserOptService = monitorUserOptService; - this.userService = userService; - this.workspaceService = workspaceService; - } - - private void checkMonitor(UserOperateLogV1 userOperateLogV1) { - ClassFeature classFeature = EnumUtil.fromString(ClassFeature.class, userOperateLogV1.getClassFeature(), null); - MethodFeature methodFeature = EnumUtil.fromString(MethodFeature.class, userOperateLogV1.getMethodFeature(), null); - UserModel optUserItem = userService.getByKey(userOperateLogV1.getUserId()); - if (classFeature == null || methodFeature == null || optUserItem == null) { - return; - } - String otherMsg = ""; - Class> aClass = CLASS_FEATURE_SERVICE.get(classFeature); - if (aClass != null) { - BaseDbCommonService baseDbCommonService = SpringUtil.getBean(aClass); - Object data = baseDbCommonService.getByKey(userOperateLogV1.getNodeId()); - if (data != null) { - Object name = BeanUtil.getProperty(data, "name"); - otherMsg = name == null ? StrUtil.EMPTY : StrUtil.format("操作的数据名称:{}\n", name); - } - } - WorkspaceModel workspaceModel = workspaceService.getByKey(userOperateLogV1.getWorkspaceId()); - - String optTypeMsg = StrUtil.format(" 【{}】->【{}】", classFeature.getName(), methodFeature.getName()); - List monitorUserOptModels = monitorUserOptService.listByType(userOperateLogV1.getWorkspaceId(), - classFeature, - methodFeature); - if (CollUtil.isEmpty(monitorUserOptModels)) { - return; - } - for (MonitorUserOptModel monitorUserOptModel : monitorUserOptModels) { - List notifyUser = monitorUserOptModel.notifyUser(); - if (CollUtil.isEmpty(notifyUser)) { - continue; - } - for (String userId : notifyUser) { - UserModel item = userService.getByKey(userId); - if (item == null) { - continue; - } - // - String context = StrUtil.format("操作用户:{}\n操作状态:{}\n操作类型:{}\n所属工作空间:{}\n操作节点:{}\n 操作数据id: {}\n操作IP: {}\n{}", - optUserItem.getName(), - userOperateLogV1.getOptStatusMsg(), - optTypeMsg, - workspaceModel.getName(), - userOperateLogV1.getNodeId(), userOperateLogV1.getDataId(), userOperateLogV1.getIp(), otherMsg); - // 邮箱 - String email = item.getEmail(); - if (StrUtil.isNotEmpty(email)) { - MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.mail, email); - ThreadUtil.execute(() -> NotifyUtil.send(notify1, "用户操作报警", context)); - - } - // dingding - String dingDing = item.getDingDing(); - if (StrUtil.isNotEmpty(dingDing)) { - MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.dingding, dingDing); - ThreadUtil.execute(() -> NotifyUtil.send(notify1, "用户操作报警", context)); - } - // 企业微信 - String workWx = item.getWorkWx(); - if (StrUtil.isNotEmpty(workWx)) { - MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.workWx, workWx); - ThreadUtil.execute(() -> NotifyUtil.send(notify1, "用户操作报警", context)); - } - } - } - } - - @Override - public void insert(UserOperateLogV1 userOperateLogV1) { - super.insert(userOperateLogV1); - DbConfig.autoClear(getTableName(), "optTime"); - DbConfig.autoClear(getTableName(), "createTimeMillis"); - ThreadUtil.execute(() -> { - try { - this.checkMonitor(userOperateLogV1); - } catch (Exception e) { - DefaultSystemLog.getLog().error("执行操作监控错误", e); - } - }); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/RepositoryService.java b/modules/server/src/main/java/io/jpom/service/dblog/RepositoryService.java deleted file mode 100644 index 16e891241c..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/RepositoryService.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import io.jpom.model.data.RepositoryModel; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -/** - * @author Hotstrip - * Repository service - */ -@Service -public class RepositoryService extends BaseWorkspaceService { - - @Override - protected void fillSelectResult(RepositoryModel repositoryModel) { - if (repositoryModel == null) { - return; - } - // 隐藏密码字段 - repositoryModel.setPassword(null); - repositoryModel.setRsaPrv(null); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/dblog/SshTerminalExecuteLogService.java b/modules/server/src/main/java/io/jpom/service/dblog/SshTerminalExecuteLogService.java deleted file mode 100644 index 7ab6431228..0000000000 --- a/modules/server/src/main/java/io/jpom/service/dblog/SshTerminalExecuteLogService.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.dblog; - -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.util.StrUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.SshModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.log.SshTerminalExecuteLog; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - - -/** - * ssh 终端执行日志 - * - * @author jiangzeyin - * @date 2021/08/04 - */ -@Service -public class SshTerminalExecuteLogService extends BaseWorkspaceService { - - /** - * 批量记录日志 - * - * @param userInfo 操作的用户 - * @param sshItem ssh 对象 - * @param ip 操作人的ip - * @param userAgent 浏览器标识 - * @param commands 命令行 - * @param refuse 是否拒绝执行 - */ - public void batch(UserModel userInfo, SshModel sshItem, String ip, String userAgent, boolean refuse, List commands) { - long optTime = SystemClock.now(); - try { - BaseServerController.resetInfo(UserModel.EMPTY); - List executeLogs = commands.stream().filter(StrUtil::isNotEmpty).map(s -> { - SshTerminalExecuteLog sshTerminalExecuteLog = new SshTerminalExecuteLog(); - //sshTerminalExecuteLog.setId(IdUtil.fastSimpleUUID()); - if (sshItem != null) { - sshTerminalExecuteLog.setSshId(sshItem.getId()); - sshTerminalExecuteLog.setSshName(sshItem.getName()); - sshTerminalExecuteLog.setWorkspaceId(sshItem.getWorkspaceId()); - } - sshTerminalExecuteLog.setCommands(s); - sshTerminalExecuteLog.setRefuse(refuse); - sshTerminalExecuteLog.setCreateTimeMillis(optTime); - sshTerminalExecuteLog.setIp(ip); - sshTerminalExecuteLog.setUserAgent(userAgent); - sshTerminalExecuteLog.setUserId(UserModel.getOptUserName(userInfo)); - return sshTerminalExecuteLog; - }).collect(Collectors.toList()); - super.insert(executeLogs); - } finally { - BaseServerController.remove(); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java b/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java deleted file mode 100644 index 144fe79e30..0000000000 --- a/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java +++ /dev/null @@ -1,620 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.bean.copier.CopyOptions; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.PageUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.TypeUtil; -import cn.hutool.db.Db; -import cn.hutool.db.Entity; -import cn.hutool.db.Page; -import cn.hutool.db.PageResult; -import cn.hutool.db.sql.Order; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.model.PageResultDto; -import io.jpom.system.JpomRuntimeException; -import io.jpom.system.db.DbConfig; -import org.springframework.util.Assert; - -import java.sql.SQLException; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -/** - * 数据库基础操作 通用 service - * - * @author bwcx_jzy - * @date 2019/7/20 - */ -public abstract class BaseDbCommonService { - - static { - // 配置页码是从 1 开始 - PageUtil.setFirstPageNo(1); - } - - /** - * 表名 - */ - protected final String tableName; - protected final Class tClass; - /** - * 主键 - */ - protected final String key; - - public BaseDbCommonService(String tableName, String key, Class tClass) { - this.tableName = this.covetTableName(tableName, tClass); - this.tClass = tClass; - this.key = key; - } - - @SuppressWarnings("unchecked") - public BaseDbCommonService(String tableName, String key) { - this.tClass = (Class) TypeUtil.getTypeArgument(this.getClass()); - this.tableName = this.covetTableName(tableName, this.tClass); - this.key = key; - } - - /** - * 转换表面 - * - * @param tableName 表面 - * @param tClass 类 - * @return 转换后的表名 - */ - protected String covetTableName(String tableName, Class tClass) { - return tableName; - } - - public String getTableName() { - return tableName; - } - - protected String getKey() { - return key; - } - - /** - * 插入数据 - * - * @param t 数据 - */ - public void insert(T t) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return; - } - Db db = Db.use(); - db.setWrapper((Character) null); - try { - Entity entity = this.dataBeanToEntity(t); - db.insert(entity); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 插入数据 - * - * @param t 数据 - */ - public void insert(Collection t) { - if (CollUtil.isEmpty(t)) { - return; - } - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return; - } - Db db = Db.use(); - db.setWrapper((Character) null); - try { - List entities = t.stream().map(this::dataBeanToEntity).collect(Collectors.toList()); - db.insert(entities); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 实体转 entity - * - * @param data 实体对象 - * @return entity - */ - public Entity dataBeanToEntity(T data) { - Entity entity = new Entity(tableName); - // 转换为 map - Map beanToMap = BeanUtil.beanToMap(data, new LinkedHashMap<>(), true, s -> StrUtil.format("`{}`", s)); - entity.putAll(beanToMap); - return entity; - } - - /** - * 插入数据 - * - * @param entity 要修改的数据 - * @return 影响行数 - */ - public int insert(Entity entity) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return 0; - } - Db db = Db.use(); - db.setWrapper((Character) null); - entity.setTableName(tableName); - try { - return db.insert(entity); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 修改数据,需要自行实现 - * - * @param t 数据 - * @return 影响行数 - */ - public int update(T t) { - return 0; - } - - /** - * 修改数据 - * - * @param entity 要修改的数据 - * @param where 条件 - * @return 影响行数 - */ - public int update(Entity entity, Entity where) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return 0; - } - Db db = Db.use(); - db.setWrapper((Character) null); - if (where.isEmpty()) { - throw new JpomRuntimeException("没有更新条件"); - } - entity.setTableName(tableName); - where.setTableName(tableName); - try { - return db.update(entity, where); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 根据主键查询实体 - * - * @param keyValue 主键值 - * @return 数据 - */ - public T getByKey(String keyValue) { - return this.getByKey(keyValue, true); - } - - /** - * 根据主键查询实体 - * - * @param keyValue 主键值 - * @return 数据 - */ - public T getByKey(String keyValue, boolean fill) { - return this.getByKey(keyValue, fill, null); - } - - /** - * 根据主键查询实体 - * - * @param keyValue 主键值 - * @return 数据 - */ - public T getByKey(String keyValue, boolean fill, Consumer consumer) { - if (StrUtil.isEmpty(keyValue)) { - return null; - } - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return null; - } - Entity where = new Entity(tableName); - where.set(key, keyValue); - Db db = Db.use(); - db.setWrapper((Character) null); - if (consumer != null) { - consumer.accept(where); - } - Entity entity; - try { - entity = db.get(where); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - T entityToBean = this.entityToBean(entity, this.tClass); - if (fill) { - this.fillSelectResult(entityToBean); - } - return entityToBean; - } - - /** - * entity 转 实体 - * - * @param entity Entity - * @param rClass 实体类 - * @param 乏型 - * @return data - */ - private R entityToBean(Entity entity, Class rClass) { - if (entity == null) { - return null; - } - CopyOptions copyOptions = new CopyOptions(); - copyOptions.setIgnoreError(true); - copyOptions.setIgnoreCase(true); - return BeanUtil.toBean(entity, rClass, copyOptions); - } - - /** - * entity 转 实体 - * - * @param entity Entity - * @return data - */ - public T entityToBean(Entity entity) { - if (entity == null) { - return null; - } - CopyOptions copyOptions = new CopyOptions(); - copyOptions.setIgnoreError(true); - copyOptions.setIgnoreCase(true); - T toBean = BeanUtil.toBean(entity, this.tClass, copyOptions); - this.fillSelectResult(toBean); - return toBean; - } - - /** - * 根据主键生成 - * - * @param keyValue 主键值 - * @return 影响行数 - */ - public int delByKey(String keyValue) { - return this.delByKey(keyValue, null); - } - - /** - * 根据主键生成 - * - * @param keyValue 主键值 - * @param consumer 回调 - * @return 影响行数 - */ - public int delByKey(Object keyValue, Consumer consumer) { - if (ObjectUtil.isEmpty(keyValue)) { - return 0; - } - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return 0; - } - Entity where = new Entity(tableName); - where.set(key, keyValue); - if (consumer != null) { - consumer.accept(where); - } - return del(where); - } - - /** - * 根据条件删除 - * - * @param where 条件 - * @return 影响行数 - */ - public int del(Entity where) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return 0; - } - where.setTableName(tableName); - if (where.isEmpty()) { - throw new JpomRuntimeException("没有删除条件"); - } - Db db = Db.use(); - db.setWrapper((Character) null); - try { - return db.del(where); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 判断是否存在 - * - * @param data 实体 - * @return true 存在 - */ - public boolean exists(T data) { - Entity entity = this.dataBeanToEntity(data); - return this.exists(entity); - } - - /** - * 判断是否存在 - * - * @param where 条件 - * @return true 存在 - */ - public boolean exists(Entity where) { - long count = this.count(where); - return count > 0; - } - - /** - * 查询记录条数 - * - * @param where 条件 - * @return count - */ - public long count(Entity where) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return 0; - } - where.setTableName(getTableName()); - Db db = Db.use(); - db.setWrapper((Character) null); - try { - return db.count(where); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 查询一个 - * - * @param where 条件 - * @return Entity - */ - public Entity query(Entity where) { - List entities = this.queryList(where); - return CollUtil.getFirst(entities); - } - - /** - * 查询列表 - * - * @param where 条件 - * @return List - */ - public List queryList(Entity where) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return null; - } - where.setTableName(getTableName()); - Db db = Db.use(); - db.setWrapper((Character) null); - try { - return db.find(where); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * 查询列表 - * - * @param data 数据 - * @param count 查询数量 - * @param orders 排序 - * @return List - */ - public List queryList(T data, int count, Order... orders) { - Entity where = this.dataBeanToEntity(data); - Page page = new Page(1, count); - page.addOrder(orders); - PageResultDto tPageResultDto = this.listPage(where, page); - return tPageResultDto.getResult(); - } - - /** - * 分页查询 - * - * @param where 条件 - * @param page 分页 - * @return 结果 - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public PageResultDto listPage(Entity where, Page page) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return PageResultDto.EMPTY; - } - where.setTableName(getTableName()); - PageResult pageResult; - Db db = Db.use(); - db.setWrapper((Character) null); - try { - pageResult = db.page(where, page); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常:" + e.getMessage(), e); - } - // - List list = pageResult.stream().map(entity -> { - T entityToBean = this.entityToBean(entity, this.tClass); - this.fillSelectResult(entityToBean); - return entityToBean; - }).collect(Collectors.toList()); - PageResultDto pageResultDto = new PageResultDto(pageResult); - pageResultDto.setResult(list); - if (pageResultDto.isEmpty() && pageResultDto.getPage() > 1) { - Assert.state(pageResultDto.getTotal() <= 0, "筛选的分页有问题,当前页码查询不到任何数据"); - } - return pageResultDto; - } - - /** - * 分页查询 - * - * @param where 条件 - * @param page 分页 - * @return 结果 - */ - public List listPageOnlyResult(Entity where, Page page) { - PageResultDto pageResultDto = this.listPage(where, page); - return pageResultDto.getResult(); - } - - /** - * sql 查询 - * - * @param sql sql 语句 - * @param params 参数 - * @return list - */ - public List query(String sql, Object... params) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return null; - } - try { - return Db.use().query(sql, params); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - /** - * sql 执行 - * - * @param sql sql 语句 - * @param params 参数 - * @return list - */ - public int execute(String sql, Object... params) { - if (!DbConfig.getInstance().isInit()) { - // ignore - DefaultSystemLog.getLog().error("The database is not initialized, this execution will be ignored"); - return 0; - } - try { - return Db.use().execute(sql, params); - } catch (SQLException e) { - throw new JpomRuntimeException("数据库异常", e); - } - } - - - /** - * sql 查询 list - * - * @param sql sql 语句 - * @param params 参数 - * @return list - */ - public List queryList(String sql, Object... params) { - List query = this.query(sql, params); - if (query != null) { - return query.stream().map((entity -> { - T entityToBean = this.entityToBean(entity, this.tClass); - this.fillSelectResult(entityToBean); - return entityToBean; - })).collect(Collectors.toList()); - } - return null; - } - - /** - * 查询实体对象 - * - * @param data 实体 - * @return data - */ - public List listByBean(T data) { - Entity where = this.dataBeanToEntity(data); - List entitys = this.queryList(where); - return this.entityToBeanList(entitys); - } - - public List entityToBeanList(List entitys) { - if (entitys == null) { - return null; - } - return entitys.stream().map((entity -> { - T entityToBean = this.entityToBean(entity, this.tClass); - this.fillSelectResult(entityToBean); - return entityToBean; - })).collect(Collectors.toList()); - } - - /** - * 查询实体对象 - * - * @param data 实体 - * @return data - */ - public T queryByBean(T data) { - Entity where = this.dataBeanToEntity(data); - Entity entity = this.query(where); - T entityToBean = this.entityToBean(entity, this.tClass); - this.fillSelectResult(entityToBean); - return entityToBean; - } - - /** - * 查询结果 填充 - * - * @param data 数据 - */ - protected void fillSelectResult(T data) { - } -} diff --git a/modules/server/src/main/java/io/jpom/service/h2db/BaseDbService.java b/modules/server/src/main/java/io/jpom/service/h2db/BaseDbService.java deleted file mode 100644 index 0091ae2ead..0000000000 --- a/modules/server/src/main/java/io/jpom/service/h2db/BaseDbService.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.*; -import cn.hutool.db.Entity; -import cn.hutool.db.Page; -import cn.hutool.db.sql.Direction; -import cn.hutool.db.sql.Order; -import cn.hutool.extra.servlet.ServletUtil; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.model.BaseDbModel; -import io.jpom.model.BaseUserModifyDbModel; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.UserModel; -import org.springframework.util.Assert; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -/** - * 数据库操作 通用 serve - * - * @author bwcx_jzy - * @since 2021/8/13 - */ -public abstract class BaseDbService extends BaseDbCommonService { - - public BaseDbService() { - super(null, Const.ID_STR); - } - - @Override - protected String covetTableName(String tableName, Class tClass) { - TableName annotation = tClass.getAnnotation(TableName.class); - Assert.notNull(annotation, "请配置 table Name"); - return annotation.value(); - } - - @Override - public void insert(T t) { - this.fillInsert(t); - super.insert(t); - } - - /** - * 先尝试 更新,更新失败插入 - * - * @param t 数据 - */ - public void upsert(T t) { - int update = this.update(t); - if (update <= 0) { - this.insert(t); - } - } - - /** - * 不填充 插入 - * - * @param t 数据 - */ - public void insertNotFill(T t) { - // def create time - t.setCreateTimeMillis(ObjectUtil.defaultIfNull(t.getCreateTimeMillis(), SystemClock.now())); - t.setId(ObjectUtil.defaultIfNull(t.getId(), IdUtil.fastSimpleUUID())); - super.insert(t); - } - - @Override - public void insert(Collection t) { - // def create time - t.forEach(this::fillInsert); - super.insert(t); - } - - /** - * 插入数据填充 - * - * @param t 数据 - */ - protected void fillInsert(T t) { - // def create time - t.setCreateTimeMillis(ObjectUtil.defaultIfNull(t.getCreateTimeMillis(), SystemClock.now())); - t.setId(ObjectUtil.defaultIfNull(t.getId(), IdUtil.fastSimpleUUID())); - if (t instanceof BaseUserModifyDbModel) { - // 获取数据修改人 - BaseUserModifyDbModel modifyDbModel = (BaseUserModifyDbModel) t; - UserModel userModel = BaseServerController.getUserModel(); - if (userModel != null) { - modifyDbModel.setModifyUser(ObjectUtil.defaultIfNull(modifyDbModel.getModifyUser(), userModel.getId())); - } - } - } - - /** - * update by id with data - * - * @param info data - * @return 影响的行数 - */ - public int updateById(T info) { - // check id - String id = info.getId(); - Assert.hasText(id, "不能执行:error"); - // def modify time - info.setModifyTimeMillis(ObjectUtil.defaultIfNull(info.getModifyTimeMillis(), SystemClock.now())); - // remove create time - info.setCreateTimeMillis(null); - // fill modify user - if (info instanceof BaseUserModifyDbModel) { - BaseUserModifyDbModel modifyDbModel = (BaseUserModifyDbModel) info; - UserModel userModel = BaseServerController.getUserModel(); - if (userModel != null) { - modifyDbModel.setModifyUser(ObjectUtil.defaultIfNull(modifyDbModel.getModifyUser(), userModel.getId())); - } - } - // - Entity entity = this.dataBeanToEntity(info); - // - entity.remove(StrUtil.format("`{}`", Const.ID_STR)); - // - Entity where = new Entity(); - where.set(Const.ID_STR, id); - return super.update(entity, where); - } - - @Override - public int update(T t) { - return this.updateById(t); - } - - - public List list() { - return super.listByBean(ReflectUtil.newInstance(this.tClass)); - } - - public long count() { - return super.count(Entity.create()); - } - - - /** - * 通用的分页查询, 使用该方法查询,数据库表字段不能保护 "page", "limit", "order_field", "order", "total" - *

- * page=1&limit=10&order=ascend&order_field=name - * - * @param request 请求对象 - * @return page - */ - public PageResultDto listPage(HttpServletRequest request) { - Map paramMap = ServletUtil.getParamMap(request); - return this.listPage(paramMap); - } - - /** - * 通用的分页查询, 使用该方法查询,数据库表字段不能包含 "page", "limit", "order_field", "order", "total" - *

- * page=1&limit=10&order=ascend&order_field=name - * - * @param paramMap 请求参数 - * @return page - */ - public PageResultDto listPage(Map paramMap) { - int page = Convert.toInt(paramMap.get("page"), 1); - int limit = Convert.toInt(paramMap.get("limit"), 10); - Assert.state(page > 0, "page value error"); - Assert.state(limit > 0 && limit < 200, "limit value error"); - String orderField = paramMap.get("order_field"); - String order = paramMap.get("order"); - // 移除 默认字段 - MapUtil.removeAny(paramMap, "page", "limit", "order_field", "order", "total"); - // - Page pageReq = new Page(page, limit); - Entity where = Entity.create(); - // 查询条件 - for (Map.Entry stringStringEntry : paramMap.entrySet()) { - String key = stringStringEntry.getKey(); - String value = stringStringEntry.getValue(); - if (StrUtil.isEmpty(value)) { - continue; - } - key = StrUtil.removeAll(key, "%"); - if (StrUtil.startWith(stringStringEntry.getKey(), "%") && StrUtil.endWith(stringStringEntry.getKey(), "%")) { - where.set(StrUtil.format("`{}`", key), StrUtil.format(" like '%{}%'", value)); - } else if (StrUtil.endWith(stringStringEntry.getKey(), "%")) { - where.set(StrUtil.format("`{}`", key), StrUtil.format(" like '{}%'", value)); - } else if (StrUtil.startWith(stringStringEntry.getKey(), "%")) { - where.set(StrUtil.format("`{}`", key), StrUtil.format(" like '%{}'", value)); - } else if (StrUtil.containsIgnoreCase(key, "time") && StrUtil.contains(value, "~")) { - String[] val = StrUtil.splitToArray(value, "~"); - if (val.length == 2) { - DateTime startDateTime = DateUtil.parse(val[0], DatePattern.NORM_DATETIME_FORMAT); - where.set(key, ">= " + startDateTime.getTime()); - - DateTime endDateTime = DateUtil.parse(val[1], DatePattern.NORM_DATETIME_FORMAT); - if (startDateTime.equals(endDateTime)) { - endDateTime = DateUtil.endOfDay(endDateTime); - } - // 防止字段重复 - where.set(key + " ", "<= " + endDateTime.getTime()); - } - } else { - where.set(StrUtil.format("`{}`", key), value); - } - } - // 排序 - if (StrUtil.isNotEmpty(orderField)) { - orderField = StrUtil.removeAll(orderField, "%"); - pageReq.addOrder(new Order(orderField, StrUtil.equalsIgnoreCase(order, "ascend") ? Direction.ASC : Direction.DESC)); - } - return this.listPage(where, pageReq); - } - - @Override - public PageResultDto listPage(Entity where, Page page) { - if (ArrayUtil.isEmpty(page.getOrders())) { - page.addOrder(new Order("createTimeMillis", Direction.DESC)); - page.addOrder(new Order("modifyTimeMillis", Direction.DESC)); - } - return super.listPage(where, page); - } - - /** - * 多个 id 查询数据 - * - * @param ids ids - * @return list - */ - public List listById(Collection ids) { - return this.listById(ids, null); - } - - /** - * 多个 id 查询数据 - * - * @param ids ids - * @return list - */ - public List listById(Collection ids, Consumer consumer) { - if (CollUtil.isEmpty(ids)) { - return null; - } - Entity entity = Entity.create(); - entity.set(Const.ID_STR, ids); - if (consumer != null) { - consumer.accept(entity); - } - List entities = super.queryList(entity); - return this.entityToBeanList(entities); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/h2db/BaseNodeService.java b/modules/server/src/main/java/io/jpom/service/h2db/BaseNodeService.java deleted file mode 100644 index addb58096c..0000000000 --- a/modules/server/src/main/java/io/jpom/service/h2db/BaseNodeService.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.BaseNodeModel; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.NodeModel; -import io.jpom.service.node.NodeService; -import org.springframework.util.Assert; - -import javax.servlet.http.HttpServletRequest; -import java.util.Map; - -/** - * @author bwcx_jzy - * @since 2021/12/5 - */ -public abstract class BaseNodeService extends BaseWorkspaceService { - - public PageResultDto listPageNode(HttpServletRequest request) { - // 验证工作空间权限 - Map paramMap = ServletUtil.getParamMap(request); - String workspaceId = this.getCheckUserWorkspace(request); - paramMap.put("workspaceId", workspaceId); - // 验证节点 - 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, "不存在对应的节点"); - paramMap.put("nodeId", nodeId); - return super.listPage(paramMap); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/h2db/BaseWorkspaceService.java b/modules/server/src/main/java/io/jpom/service/h2db/BaseWorkspaceService.java deleted file mode 100644 index 42a7f5e16f..0000000000 --- a/modules/server/src/main/java/io/jpom/service/h2db/BaseWorkspaceService.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.PageResultDto; -import io.jpom.model.data.UserBindWorkspaceModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.service.user.UserBindWorkspaceService; -import org.springframework.util.Assert; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * 工作空间 通用 service - * - * @author bwcx_jzy - * @since 2021/8/13 - */ -public abstract class BaseWorkspaceService extends BaseDbService { - - /** - * 根据工作空间查询 - * - * @param request 请求 - * @return list - */ - public List listByWorkspace(HttpServletRequest request) { - String workspaceId = this.getCheckUserWorkspace(request); - Entity entity = Entity.create(); - entity.set("workspaceId", workspaceId); - List entities = super.queryList(entity); - return super.entityToBeanList(entities); - } - - /** - * 根据主键ID + 请信息查询 - * - * @param keyValue ID - * @param request 请求 - * @return data - */ - public T getByKey(String keyValue, HttpServletRequest request) { - String workspace = this.getCheckUserWorkspace(request); - return super.getByKey(keyValue, true, entity -> entity.set("workspaceId", workspace)); - } - - /** - * 根据主键ID + 用户ID - * - * @param keyValue ID - * @param userModel 用户 - * @return data - */ - public T getByKey(String keyValue, UserModel userModel) { - T byKey = super.getByKey(keyValue, false); - this.checkUserWorkspace(byKey.getWorkspaceId(), userModel); - return byKey; - } - - @Override - protected void fillInsert(T t) { - super.fillInsert(t); - if (StrUtil.isEmpty(t.getWorkspaceId())) { - // 自动绑定 工作空间ID - ServletRequestAttributes servletRequestAttributes = BaseServerController.tryGetRequestAttributes(); - if (servletRequestAttributes != null) { - HttpServletRequest request = servletRequestAttributes.getRequest(); - String workspaceId = getCheckUserWorkspace(request); - t.setWorkspaceId(workspaceId); - } else { - t.setWorkspaceId(WorkspaceModel.DEFAULT_ID); - } - } else { - // 检查权限 - this.checkUserWorkspace(t.getWorkspaceId()); - } - } - - @Override - public PageResultDto listPage(HttpServletRequest request) { - // 验证工作空间权限 - Map paramMap = ServletUtil.getParamMap(request); - String workspaceId = this.getCheckUserWorkspace(request); - paramMap.put("workspaceId", workspaceId); - return super.listPage(paramMap); - } - - /** - * 删除 - * - * @param keyValue 主键 - * @param request 请求信息 - * @return 影响行数 - */ - public int delByKey(String keyValue, HttpServletRequest request) { - String workspace = this.getCheckUserWorkspace(request); - return super.delByKey(keyValue, entity -> entity.set("workspaceId", workspace)); - } - - /** - * 获取 工作空间ID 并判断是否有权限 - * - * @param request 请求对象 - * @return 工作空间ID - */ - public String getCheckUserWorkspace(HttpServletRequest request) { - String workspaceId = ServletUtil.getHeader(request, Const.WORKSPACEID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); - Assert.hasText(workspaceId, "请选择工作空间"); - // - this.checkUserWorkspace(workspaceId); - return workspaceId; - } - - /** - * 判断用户是否有对应工作空间权限 - * - * @param workspaceId 工作空间ID - */ - private void checkUserWorkspace(String workspaceId) { - UserModel userModel = BaseServerController.getUserByThreadLocal(); - this.checkUserWorkspace(workspaceId, userModel); - } - - /** - * 判断用户是否有对应工作空间权限 - * - * @param workspaceId 工作空间ID - */ - private void checkUserWorkspace(String workspaceId, UserModel userModel) { - Assert.notNull(userModel, "没有对应的用户"); - if (StrUtil.equals(userModel.getId(), UserModel.SYSTEM_ADMIN)) { - // 系统执行,发行检查 - return; - } - if (userModel.isSuperSystemUser()) { - // 超级管理员 - return; - } - // 查询绑定的权限 - UserBindWorkspaceModel workspaceModel = new UserBindWorkspaceModel(); - workspaceModel.setId(UserBindWorkspaceModel.getId(userModel.getId(), workspaceId)); - UserBindWorkspaceService userBindWorkspaceService = SpringUtil.getBean(UserBindWorkspaceService.class); - boolean exists = userBindWorkspaceService.exists(workspaceModel); - Assert.state(exists, "没有对应的工作空间权限"); - } - - - public List listById(Collection ids, HttpServletRequest request) { - String checkUserWorkspace = this.getCheckUserWorkspace(request); - return super.listById(ids, entity -> entity.set("workspaceId", checkUserWorkspace)); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/h2db/H2BackupService.java b/modules/server/src/main/java/io/jpom/service/h2db/H2BackupService.java deleted file mode 100644 index 0ccc8aecb3..0000000000 --- a/modules/server/src/main/java/io/jpom/service/h2db/H2BackupService.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.ds.DSFactory; -import cn.jiangzeyin.common.DefaultSystemLog; -import org.h2.tools.RunScript; -import org.h2.tools.Shell; -import org.springframework.stereotype.Service; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -import javax.sql.DataSource; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.List; - -/** - * H2 数据库备份与还原 service - * @author Hotstrip - * @since 2021-11-17 - */ -@Service -public class H2BackupService { - - /** - * 备份 SQL - * @param url jdbc url - * @param user user - * @param password password - * @param backupSqlPath backup SQL file path, absolute path - * @param tableNameList backup table name list, if need backup all table, use null - */ - public void backupSql(String url, String user, String password, - String backupSqlPath, List tableNameList) throws SQLException { - // 备份 SQL - String sql = StrUtil.format("SCRIPT DROP to '{}'", backupSqlPath); - // 判断是否部分部分表 - if (!CollectionUtils.isEmpty(tableNameList)) { - String tableNames = StrUtil.join(",", tableNameList.toArray()); - sql = StrUtil.format("{} TABLE {}", sql, tableNames); - } - - DefaultSystemLog.getLog().info("backup SQL is: {}", sql); - - // 执行 SQL 备份脚本 - Shell shell = new Shell(); - - /** - * url 表示 h2 数据库的 jdbc url - * user 表示登录的用户名 - * password 表示登录密码 - * driver 是 jdbc 驱动 - * sql 是备份的 sql 语句 - * - 案例:script drop to ${fileName1} table ${tableName1},${tableName2}... - * - script drop to 表示备份数据库,drop 表示建表之前会先删除表 - * - ${fileName1} 表示备份之后的文件名 - * - table 表示需要备份的表名称,后面跟多个表名,用英文逗号分割 - */ - String[] params = new String[] { - "-url", url, - "-user", user, - "-password", password, - "-driver", "org.h2.Driver", - "-sql", sql - }; - shell.runTool(params); - } - - /** - * 还原备份 SQL - * @param backupSqlPath backup SQL file path, absolute path - * @throws SQLException SQLException - * @throws FileNotFoundException FileNotFoundException - */ - public void restoreBackupSql(String backupSqlPath) throws SQLException, IOException { - // 加载数据源 - DataSource dataSource = DSFactory.get(); - Assert.notNull(dataSource, "Restore Backup sql error...H2 DataSource not null"); - Connection connection = dataSource.getConnection(); - - // 读取数据库备份文件,执行还原 - try (FileReader fileReader = new FileReader(backupSqlPath)) { - RunScript.execute(connection, fileReader); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/service/h2db/TableName.java b/modules/server/src/main/java/io/jpom/service/h2db/TableName.java deleted file mode 100644 index 267599a168..0000000000 --- a/modules/server/src/main/java/io/jpom/service/h2db/TableName.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import java.lang.annotation.*; - -/** - * 数据库表名 - * - * @author bwcx_jzy - * @since 2021/8/13 - */ -@Documented -@Target({ElementType.TYPE}) -@Inherited -@Retention(RetentionPolicy.RUNTIME) -public @interface TableName { - - /** - * 表名 - * - * @return tableName - */ - String value(); - - /** - * 表描述 - * - * @return 描述 - */ - String name(); -} diff --git a/modules/server/src/main/java/io/jpom/service/monitor/MonitorService.java b/modules/server/src/main/java/io/jpom/service/monitor/MonitorService.java deleted file mode 100644 index cd041f97ae..0000000000 --- a/modules/server/src/main/java/io/jpom/service/monitor/MonitorService.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.monitor; - -import cn.hutool.core.util.ObjectUtil; -import io.jpom.model.Cycle; -import io.jpom.model.data.MonitorModel; -import io.jpom.monitor.Monitor; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -import java.util.Collections; -import java.util.List; - -/** - * 监控管理Service - * - * @author Arno - */ -@Service -public class MonitorService extends BaseWorkspaceService { - - @Override - public void insert(MonitorModel monitorModel) { - super.insert(monitorModel); - if (monitorModel.status()) { - Monitor.start(); - } - } - - @Override - public int delByKey(String keyValue) { - int byKey = super.delByKey(keyValue); - this.checkCronStatus(); - return byKey; - } - - public boolean checkCronStatus() { - // 关闭监听 - MonitorModel monitorModel = new MonitorModel(); - monitorModel.setStatus(true); - long count = super.count(super.dataBeanToEntity(monitorModel)); - if (count > 0) { - Monitor.start(); - } else { - Monitor.stop(); - } - return count > 0; - } - - @Override - public int updateById(MonitorModel info) { - int updateById = super.updateById(info); - this.checkCronStatus(); - return updateById; - } - - - /** - * 根据周期获取list - * - * @param cycle 周期 - * @return list - */ - public List listRunByCycle(Cycle cycle) { - MonitorModel monitorModel = new MonitorModel(); - monitorModel.setCycle(cycle.getCode()); - monitorModel.setStatus(true); - List monitorModels = this.listByBean(monitorModel); - return ObjectUtil.defaultIfNull(monitorModels, Collections.EMPTY_LIST); - } - - /** - * 设置报警状态 - * - * @param id 监控id - * @param alarm 状态 - */ - public synchronized void setAlarm(String id, boolean alarm) { - MonitorModel monitorModel = new MonitorModel(); - monitorModel.setId(id); - monitorModel.setAlarm(alarm); - super.update(monitorModel); - } - - /** - * 判断是否存在对应节点数据 - * - * @param nodeId 节点id - * @return true 存在 - */ - public boolean checkNode(String nodeId) { - List list = list(); - if (list == null || list.isEmpty()) { - return false; - } - for (MonitorModel monitorModel : list) { - List projects = monitorModel.projects(); - if (projects != null) { - for (MonitorModel.NodeProject project : projects) { - if (nodeId.equals(project.getNode())) { - return true; - } - } - } - } - return false; - } - - - public boolean checkProject(String nodeId, String projectId) { - List list = list(); - if (list == null || list.isEmpty()) { - return false; - } - for (MonitorModel monitorModel : list) { - List projects = monitorModel.projects(); - if (projects != null) { - for (MonitorModel.NodeProject project : projects) { - if (project.getNode().equals(nodeId)) { - List projects1 = project.getProjects(); - if (projects1 != null && projects1.contains(projectId)) { - return true; - } - } - } - } - } - return false; - } -} diff --git a/modules/server/src/main/java/io/jpom/service/monitor/MonitorUserOptService.java b/modules/server/src/main/java/io/jpom/service/monitor/MonitorUserOptService.java deleted file mode 100644 index e71f4ff647..0000000000 --- a/modules/server/src/main/java/io/jpom/service/monitor/MonitorUserOptService.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.monitor; - -import cn.hutool.core.collection.CollUtil; -import io.jpom.model.data.MonitorUserOptModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 监控用户操作Service - * - * @author bwcx_jzy - */ -@Service -public class MonitorUserOptService extends BaseWorkspaceService { - - public List listByType(String workspaceId, ClassFeature classFeature, MethodFeature methodFeature) { - MonitorUserOptModel where = new MonitorUserOptModel(); - where.setWorkspaceId(workspaceId); - where.setStatus(true); - List list = super.listByBean(where); - if (CollUtil.isEmpty(list)) { - return null; - } - return list.stream().filter(monitorUserOptModel -> { - List classFeatures = monitorUserOptModel.monitorFeature(); - List methodFeatures = monitorUserOptModel.monitorOpt(); - return CollUtil.contains(classFeatures, classFeature) && CollUtil.contains(methodFeatures, methodFeature); - }).collect(Collectors.toList()); - } - -// public List listByType(UserOperateLogV1.OptType optType, String userId) { -// List userOptModels = this.listByType(optType); -// if (CollUtil.isEmpty(userOptModels)) { -// return null; -// } -// return userOptModels.stream().filter(monitorUserOptModel -> { -// List monitorUser = monitorUserOptModel.getMonitorUser(); -// return CollUtil.contains(monitorUser, userId); -// }).collect(Collectors.toList()); -// } -} diff --git a/modules/server/src/main/java/io/jpom/service/node/NodeService.java b/modules/server/src/main/java/io/jpom/service/node/NodeService.java deleted file mode 100644 index 7fad7a8452..0000000000 --- a/modules/server/src/main/java/io/jpom/service/node/NodeService.java +++ /dev/null @@ -1,188 +0,0 @@ -package io.jpom.service.node; - -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.JpomManifest; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.Cycle; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.SshModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.monitor.NodeMonitor; -import io.jpom.service.h2db.BaseWorkspaceService; -import io.jpom.service.node.ssh.SshService; -import org.springframework.stereotype.Service; -import org.springframework.util.Assert; - -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -/** - * @author bwcx_jzy - * @since 2021/12/4 - */ -@Service -public class NodeService extends BaseWorkspaceService { - - private final SshService sshService; - - public NodeService(SshService sshService) { - this.sshService = sshService; - } - - @Override - protected void fillSelectResult(NodeModel data) { - if (data != null) { - data.setLoginPwd(null); - } - } - - /** - * 修改 节点 - * - * @param request 请求对象 - */ - public void update(HttpServletRequest request) { - String type = request.getParameter("type"); - boolean create = "add".equalsIgnoreCase(type); - // 创建对象 - NodeModel nodeModel = ServletUtil.toBean(request, NodeModel.class, true); - String id = nodeModel.getId(); - if (StrUtil.isNotEmpty(id)) { - //boolean general = StringUtil.isGeneral(id, 2, 20); - //Assert.state(general, "节点id不能为空并且2-20(英文字母 、数字和下划线)"); - } - Assert.hasText(nodeModel.getName(), "节点名称 不能为空"); - // 节点地址 重复 - String workspaceId = this.getCheckUserWorkspace(request); - // Entity entity = Entity.create(); - // entity.set("url", nodeModel.getUrl()); - // entity.set("workspaceId", workspaceId); - // if (StrUtil.isNotEmpty(id)) { - // entity.set("id", StrUtil.format(" <> {}", id)); - // } - // boolean exists = super.exists(entity); - // Assert.state(!exists, "对应的节点已经存在啦"); - { - NodeModel nodeModel1 = new NodeModel(); - nodeModel1.setUrl(nodeModel.getUrl()); - nodeModel1.setWorkspaceId(workspaceId); - List nodeModels = ObjectUtil.defaultIfNull(super.listByBean(nodeModel1), Collections.EMPTY_LIST); - Optional any = nodeModels.stream().filter(nodeModel2 -> !StrUtil.equals(id, nodeModel2.getId())).findAny(); - Assert.state(!any.isPresent(), "对应的节点已经存在啦"); - } - // 判断 ssh - String sshId = nodeModel.getSshId(); - if (StrUtil.isNotEmpty(sshId)) { - SshModel byKey = sshService.getByKey(sshId, request); - Assert.notNull(byKey, "对应的 SSH 不存在"); - List nodeBySshId = this.getNodeBySshId(sshId); - nodeBySshId = ObjectUtil.defaultIfNull(nodeBySshId, Collections.EMPTY_LIST); - Optional any = nodeBySshId.stream().filter(nodeModel2 -> !StrUtil.equals(id, nodeModel2.getId())).findAny(); - Assert.state(!any.isPresent(), "对应的SSH已经被其他节点绑定啦"); - } - if (nodeModel.isOpenStatus()) { - int timeOut = nodeModel.getTimeOut(); - // 检查是否可用默认为5秒,避免太长时间无法连接一直等待 - nodeModel.setTimeOut(5); - JpomManifest jpomManifest = NodeForward.requestData(nodeModel, NodeUrl.Info, request, JpomManifest.class); - Assert.notNull(jpomManifest, "节点连接失败,请检查节点是否在线"); - nodeModel.setTimeOut(timeOut); - } - if (create) { - this.insert(nodeModel); - // 同步项目 - ProjectInfoCacheService projectInfoCacheService = SpringUtil.getBean(ProjectInfoCacheService.class); - projectInfoCacheService.syncNode(nodeModel); - } else { - this.update(nodeModel); - } - } - - @Override - public void insert(NodeModel nodeModel) { - super.insert(nodeModel); - Integer cycle = nodeModel.getCycle(); - if (nodeModel.isOpenStatus() && cycle != null && cycle != Cycle.none.getCode()) { - NodeMonitor.start(); - } - } - - @Override - public void insertNotFill(NodeModel nodeModel) { - nodeModel.setWorkspaceId(WorkspaceModel.DEFAULT_ID); - super.insertNotFill(nodeModel); - Integer cycle = nodeModel.getCycle(); - if (nodeModel.isOpenStatus() && cycle != null && cycle != Cycle.none.getCode()) { - NodeMonitor.start(); - } - } - - @Override - public int delByKey(String keyValue) { - return super.delByKey(keyValue); - } - - @Override - public int del(Entity where) { - return super.del(where); - } - - @Override - public int update(NodeModel nodeModel) { - int update = super.update(nodeModel); - this.checkCronStatus(); - return update; - } - - @Override - public int updateById(NodeModel info) { - int updateById = super.updateById(info); - this.checkCronStatus(); - return updateById; - } - - public boolean checkCronStatus() { - // 关闭监听 - Entity entity = Entity.create(); - entity.set("openStatus", 1); - entity.set("cycle", StrUtil.format(" <> {}", Cycle.none.getCode())); - long count = super.count(entity); - if (count <= 0) { - NodeMonitor.stop(); - return false; - } - NodeMonitor.start(); - return true; - } - - /** - * 根据周期获取list - * - * @param cycle 周期 - * @return list - */ - public List listByCycle(Cycle cycle) { - NodeModel nodeModel = new NodeModel(); - nodeModel.setCycle(cycle.getCode()); - nodeModel.setOpenStatus(1); - List list = this.listByBean(nodeModel); - if (list == null) { - return new ArrayList<>(); - } - return list; - } - - public List getNodeBySshId(String sshId) { - NodeModel nodeModel = new NodeModel(); - nodeModel.setSshId(sshId); - return super.listByBean(nodeModel); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/node/OutGivingServer.java b/modules/server/src/main/java/io/jpom/service/node/OutGivingServer.java deleted file mode 100644 index 86e6049cbe..0000000000 --- a/modules/server/src/main/java/io/jpom/service/node/OutGivingServer.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.jpom.service.node; - -import io.jpom.model.data.OutGivingModel; -import io.jpom.model.data.OutGivingNodeProject; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -import javax.servlet.http.HttpServletRequest; -import java.util.List; - -/** - * 分发管理 - * - * @author jiangzeyin - * @date 2019/4/21 - */ -@Service -public class OutGivingServer extends BaseWorkspaceService { - - - public boolean checkNode(String nodeId, HttpServletRequest request) { - List list = super.listByWorkspace(request); - if (list == null || list.isEmpty()) { - return false; - } - for (OutGivingModel outGivingModel : list) { - List outGivingNodeProjectList = outGivingModel.outGivingNodeProjectList(); - if (outGivingNodeProjectList != null) { - for (OutGivingNodeProject outGivingNodeProject : outGivingNodeProjectList) { - if (outGivingNodeProject.getNodeId().equals(nodeId)) { - return true; - } - } - } - } - return false; - } -} diff --git a/modules/server/src/main/java/io/jpom/service/node/ProjectInfoCacheService.java b/modules/server/src/main/java/io/jpom/service/node/ProjectInfoCacheService.java deleted file mode 100644 index 99cdcfd35b..0000000000 --- a/modules/server/src/main/java/io/jpom/service/node/ProjectInfoCacheService.java +++ /dev/null @@ -1,226 +0,0 @@ -package io.jpom.service.node; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.ProjectInfoModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.service.h2db.BaseNodeService; -import io.jpom.service.system.WorkspaceService; -import org.springframework.stereotype.Service; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * @author bwcx_jzy - * @since 2021/12/5 - */ -@Service -public class ProjectInfoCacheService extends BaseNodeService { - - private final NodeService nodeService; - private final WorkspaceService workspaceService; - - public ProjectInfoCacheService(NodeService nodeService, - WorkspaceService workspaceService) { - this.nodeService = nodeService; - this.workspaceService = workspaceService; - } - - /** - * 查询远端项目 - * - * @param nodeModel 节点ID - * @param id 项目ID - * @return json - */ - public JSONObject getItem(NodeModel nodeModel, String id) { - return NodeForward.requestData(nodeModel, NodeUrl.Manage_GetProjectItem, JSONObject.class, "id", id); - } - - public boolean exists(String workspaceId, String nodeId, String id) { - ProjectInfoModel projectInfoModel = new ProjectInfoModel(); - projectInfoModel.setWorkspaceId(workspaceId); - projectInfoModel.setNodeId(nodeId); - projectInfoModel.setProjectId(id); - return super.exists(projectInfoModel); - } - - public boolean exists(String nodeId, String id) { - NodeModel nodeModel = nodeService.getByKey(nodeId); - if (nodeModel == null) { - return false; - } - return this.exists(nodeModel.getWorkspaceId(), nodeId, id); - } - - public JSONObject getLogSize(NodeModel nodeModel, String id, String copyId) { - return NodeForward.requestData(nodeModel, NodeUrl.Manage_Log_LogSize, JSONObject.class, "id", id, "copyId", copyId); - } - - /** - * 同步所有节点的项目 - */ - public void syncAllNode() { - ThreadUtil.execute(() -> { - List list = nodeService.list(); - if (CollUtil.isEmpty(list)) { - DefaultSystemLog.getLog().debug("没有任何节点"); - return; - } - // 排序 避免项目被个节点绑定 - list.sort((o1, o2) -> { - if (StrUtil.equals(o1.getWorkspaceId(), WorkspaceModel.DEFAULT_ID)) { - return 1; - } - if (StrUtil.equals(o2.getWorkspaceId(), WorkspaceModel.DEFAULT_ID)) { - return 1; - } - return 0; - }); - for (NodeModel nodeModel : list) { - this.syncNode(nodeModel); - } - }); - } - - /** - * 同步节点的项目 - * - * @param nodeModel 节点 - */ - public void syncNode(final NodeModel nodeModel) { - ThreadUtil.execute(() -> this.syncExecuteNode(nodeModel)); - } - - /** - * 同步执行 同步节点项目信息 - * - * @param nodeModel 节点信息 - * @return json - */ - public String syncExecuteNode(NodeModel nodeModel) { - String nodeModelName = nodeModel.getName(); - if (!nodeModel.isOpenStatus()) { - DefaultSystemLog.getLog().debug("{} 节点未启用", nodeModelName); - return "节点未启用"; - } - try { - JSONArray jsonArray = NodeForward.requestData(nodeModel, NodeUrl.Manage_GetProjectInfo, JSONArray.class, "notStatus", "true"); - if (CollUtil.isEmpty(jsonArray)) { - DefaultSystemLog.getLog().debug("{} 节点没有拉取到任何项目项目", nodeModelName); - return "节点没有拉取到任何项目项目"; - } - // 查询现在存在的项目 - ProjectInfoModel where = new ProjectInfoModel(); - where.setWorkspaceId(nodeModel.getWorkspaceId()); - where.setNodeId(nodeModel.getId()); - List projectInfoModelsCacheAll = super.listByBean(where); - projectInfoModelsCacheAll = ObjectUtil.defaultIfNull(projectInfoModelsCacheAll, Collections.EMPTY_LIST); - Set cacheIds = projectInfoModelsCacheAll.stream() - .map(ProjectInfoModel::getProjectId) - .collect(Collectors.toSet()); - // - List projectInfoModels = jsonArray.toJavaList(ProjectInfoModel.class); - List models = projectInfoModels.stream().peek(projectInfoModel -> { - projectInfoModel.setProjectId(projectInfoModel.getId()); - projectInfoModel.setId(null); - projectInfoModel.setNodeId(nodeModel.getId()); - if (StrUtil.isEmpty(projectInfoModel.getWorkspaceId())) { - projectInfoModel.setWorkspaceId(WorkspaceModel.DEFAULT_ID); - } - projectInfoModel.setId(projectInfoModel.fullId()); - }).filter(projectInfoModel -> { - // 检查对应的工作空间 是否存在 - return workspaceService.exists(new WorkspaceModel(projectInfoModel.getWorkspaceId())); - }).filter(projectInfoModel -> { - // 避免重复同步 - return StrUtil.equals(nodeModel.getWorkspaceId(), projectInfoModel.getWorkspaceId()); - }).peek(projectInfoModel -> cacheIds.remove(projectInfoModel.getProjectId())).collect(Collectors.toList()); - // 设置 临时缓存,便于放行检查 - BaseServerController.resetInfo(UserModel.EMPTY); - // - models.forEach(ProjectInfoCacheService.super::upsert); - // 删除项目 - Set strings = cacheIds.stream() - .map(s -> ProjectInfoModel.fullId(nodeModel.getWorkspaceId(), nodeModel.getId(), s)) - .collect(Collectors.toSet()); - if (CollUtil.isNotEmpty(strings)) { - super.delByKey(strings, null); - } - String format = StrUtil.format("{} 节点拉取到 {} 个项目,已经缓存 {} 个项目,更新 {} 个项目,删除 {} 个缓存", - nodeModelName, CollUtil.size(jsonArray), CollUtil.size(projectInfoModelsCacheAll), CollUtil.size(models), CollUtil.size(strings)); - DefaultSystemLog.getLog().debug(format); - return format; - } catch (Exception e) { - DefaultSystemLog.getLog().error("同步节点项目失败:" + nodeModelName, e); - return "同步节点项目失败" + e.getMessage(); - } finally { - BaseServerController.remove(); - } - } - - /** - * 同步节点的项目 - * - * @param nodeModel 节点 - */ - public void syncNode(final NodeModel nodeModel, String id) { - String nodeModelName = nodeModel.getName(); - if (!nodeModel.isOpenStatus()) { - DefaultSystemLog.getLog().debug("{} 节点未启用", nodeModelName); - return; - } - ThreadUtil.execute(() -> { - try { - JSONObject data = this.getItem(nodeModel, id); - if (data == null) { - return; - } - ProjectInfoModel projectInfoModel = data.toJavaObject(ProjectInfoModel.class); - this.fullData(projectInfoModel, nodeModel); - // 设置 临时缓存,便于放行检查 - BaseServerController.resetInfo(UserModel.EMPTY); - // - super.upsert(projectInfoModel); - } catch (Exception e) { - DefaultSystemLog.getLog().error("同步节点项目失败:" + nodeModel.getId(), e); - } finally { - BaseServerController.remove(); - } - }); - } - - public int delProjectCache(String nodeId, HttpServletRequest request) { - String checkUserWorkspace = this.getCheckUserWorkspace(request); - Entity entity = Entity.create(); - entity.set("nodeId", nodeId); - entity.set("workspaceId", checkUserWorkspace); - return super.del(entity); - } - - - private void fullData(ProjectInfoModel projectInfoModel, NodeModel nodeModel) { - projectInfoModel.setProjectId(projectInfoModel.getId()); - projectInfoModel.setId(null); - projectInfoModel.setNodeId(nodeModel.getId()); - if (StrUtil.isEmpty(projectInfoModel.getWorkspaceId())) { - projectInfoModel.setWorkspaceId(nodeModel.getWorkspaceId()); - } - projectInfoModel.setId(projectInfoModel.fullId()); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/node/script/ScriptServer.java b/modules/server/src/main/java/io/jpom/service/node/script/ScriptServer.java deleted file mode 100644 index 774d1e7479..0000000000 --- a/modules/server/src/main/java/io/jpom/service/node/script/ScriptServer.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.jpom.service.node.script; - -import com.alibaba.fastjson.JSONArray; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.service.node.NodeService; -import org.springframework.stereotype.Service; - -/** - * @author bwcx_jzy - * @date 2019/8/16 - */ -@Service -public class ScriptServer { - - - private final NodeService nodeService; - - public ScriptServer(NodeService nodeService) { - this.nodeService = nodeService; - } - - - public JSONArray listToArray(NodeModel nodeModel) { - return NodeForward.requestData(nodeModel, NodeUrl.Script_List, null, JSONArray.class); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/node/ssh/SshService.java b/modules/server/src/main/java/io/jpom/service/node/ssh/SshService.java deleted file mode 100644 index 68717149cf..0000000000 --- a/modules/server/src/main/java/io/jpom/service/node/ssh/SshService.java +++ /dev/null @@ -1,272 +0,0 @@ -package io.jpom.service.node.ssh; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.ssh.ChannelType; -import cn.hutool.extra.ssh.JschUtil; -import cn.hutool.extra.ssh.Sftp; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.jcraft.jsch.*; -import io.jpom.model.data.SshModel; -import io.jpom.service.h2db.BaseWorkspaceService; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerExtConfigBean; -import org.springframework.stereotype.Service; - -import java.io.*; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * @author bwcx_jzy - * @since 2021/12/4 - */ -@Service -public class SshService extends BaseWorkspaceService { - - @Override - protected void fillSelectResult(SshModel data) { - if (data == null) { - return; - } - data.setPassword(null); - data.setPrivateKey(null); - } - - /** - * 获取 ssh 回话 - * - * @param sshId id - * @return session - */ - public static Session getSession(String sshId) { - SshModel sshModel = SpringUtil.getBean(SshService.class).getByKey(sshId, false); - return getSessionByModel(sshModel); - } - - /** - * 获取 ssh 回话 - * - * @param sshModel sshModel - * @return session - */ - public static Session getSessionByModel(SshModel sshModel) { - Session session; - SshModel.ConnectType connectType = sshModel.connectType(); - if (connectType == SshModel.ConnectType.PASS) { - session = JschUtil.openSession(sshModel.getHost(), sshModel.getPort(), sshModel.getUser(), sshModel.getPassword()); - - } else if (connectType == SshModel.ConnectType.PUBKEY) { - File tempPath = ConfigBean.getInstance().getTempPath(); - String sshFile = StrUtil.emptyToDefault(sshModel.getId(), IdUtil.fastSimpleUUID()); - File ssh = FileUtil.file(tempPath, "ssh", sshFile); - FileUtil.writeString(sshModel.getPrivateKey(), ssh, CharsetUtil.UTF_8); - byte[] pas = null; - if (StrUtil.isNotEmpty(sshModel.getPassword())) { - pas = sshModel.getPassword().getBytes(); - } - session = JschUtil.openSession(sshModel.getHost(), sshModel.getPort(), sshModel.getUser(), FileUtil.getAbsolutePath(ssh), pas); - } else { - throw new IllegalArgumentException("不支持的模式"); - } - try { - session.setServerAliveInterval((int) TimeUnit.SECONDS.toMillis(5)); - session.setServerAliveCountMax(5); - } catch (JSchException ignored) { - } - return session; - } - - /** - * 检查是否存在正在运行的进程 - * - * @param sshModel ssh - * @param tag 标识 - * @return true 存在运行中的 - * @throws IOException IO - * @throws JSchException jsch - */ - public boolean checkSshRun(SshModel sshModel, String tag) throws IOException, JSchException { - return this.checkSshRunPid(sshModel, tag) != null; - } - - /** - * 检查是否存在正在运行的进程 - * - * @param sshModel ssh - * @param tag 标识 - * @return true 存在运行中的 - * @throws IOException IO - * @throws JSchException jsch - */ - public Integer checkSshRunPid(SshModel sshModel, String tag) throws IOException, JSchException { - String ps = StrUtil.format("ps -ef | grep -v 'grep' | egrep {}", tag); - Session session = null; - ChannelExec channel = null; - try { - session = getSessionByModel(sshModel); - channel = (ChannelExec) JschUtil.createChannel(session, ChannelType.EXEC); - channel.setCommand(ps); - InputStream inputStream = channel.getInputStream(); - InputStream errStream = channel.getErrStream(); - channel.connect(); - Charset charset = sshModel.getCharsetT(); - // 运行中 - List result = new ArrayList<>(); - IoUtil.readLines(inputStream, charset, (LineHandler) result::add); - IoUtil.readLines(errStream, charset, (LineHandler) result::add); - return result.stream().map(s -> { - List split = StrUtil.splitTrim(s, StrUtil.SPACE); - return Convert.toInt(CollUtil.get(split, 1)); - }).filter(Objects::nonNull).findAny().orElse(null); - } finally { - JschUtil.close(channel); - JschUtil.close(session); - } - } - - public String exec(SshModel sshModel, String... command) throws IOException { - if (ArrayUtil.isEmpty(command)) { - return "没有任何命令"; - } - Session session = null; - InputStream sshExecTemplateInputStream = null; - Sftp sftp = null; - try { - File buildSsh = FileUtil.file(ConfigBean.getInstance().getTempPath(), "build_ssh", sshModel.getId() + ".sh"); - sshExecTemplateInputStream = ResourceUtil.getStream("classpath:/bin/execTemplate.sh"); - String sshExecTemplate = IoUtil.readUtf8(sshExecTemplateInputStream); - StringBuilder stringBuilder = new StringBuilder(sshExecTemplate); - for (String s : command) { - stringBuilder.append(s).append(StrUtil.LF); - } - Charset charset = sshModel.getCharsetT(); - FileUtil.writeString(stringBuilder.toString(), buildSsh, charset); - // - session = getSessionByModel(sshModel); - // 上传文件 - sftp = new Sftp(session); - String home = sftp.home(); - String path = home + "/.jpom/"; - String destFile = path + IdUtil.fastSimpleUUID() + ".sh"; - sftp.mkDirs(path); - sftp.upload(destFile, buildSsh); - - // 执行命令 - String exec, error; - try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { - exec = JschUtil.exec(session, "sh " + destFile, charset, stream); - error = new String(stream.toByteArray(), charset); - if (StrUtil.isNotEmpty(error)) { - error = " 错误:" + error; - } - } finally { - try { - sftp.delFile(destFile); - } catch (Exception ignored) { - } - } - return exec + error; - } finally { - IoUtil.close(sftp); - IoUtil.close(sshExecTemplateInputStream); - JschUtil.close(session); - } - } - - /** - * 执行命令 - * - * @param sshModel ssh - * @param command 命令 - * @return 结果 - * @throws IOException io - * @throws JSchException jsch - */ - public String exec(SshModel sshModel, String command) throws IOException, JSchException { - Session session = null; - try { - session = getSessionByModel(sshModel); - return exec(session, sshModel.getCharsetT(), command); - } finally { - JschUtil.close(session); - } - } - - private String exec(Session session, Charset charset, String command) throws IOException, JSchException { - ChannelExec channel = null; - try { - channel = (ChannelExec) JschUtil.createChannel(session, ChannelType.EXEC); - // 添加环境变量 - channel.setCommand(ServerExtConfigBean.getInstance().getSshInitEnv() + " && " + command); - InputStream inputStream = channel.getInputStream(); - InputStream errStream = channel.getErrStream(); - channel.connect(); - // 读取结果 - String result = IoUtil.read(inputStream, charset); - // - String error = IoUtil.read(errStream, charset); - return result + error; - } finally { - JschUtil.close(channel); - } - } - - /** - * 上传文件 - * - * @param sshModel ssh - * @param remotePath 远程路径 - * @param desc 文件夹或者文件 - */ - public void uploadDir(SshModel sshModel, String remotePath, File desc) { - Session session = null; - ChannelSftp channel = null; - try { - session = getSessionByModel(sshModel); - channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); - Sftp sftp = new Sftp(channel, sshModel.getCharsetT()); - sftp.syncUpload(desc, remotePath); - //uploadDir(channel, remotePath, desc, sshModel.getCharsetT()); - } finally { - JschUtil.close(channel); - JschUtil.close(session); - } - } - - /** - * 下载文件 - * - * @param sshModel 实体 - * @param remoteFile 远程文件 - * @param save 文件对象 - * @throws FileNotFoundException io - * @throws SftpException sftp - */ - public void download(SshModel sshModel, String remoteFile, File save) throws FileNotFoundException, SftpException { - Session session = null; - ChannelSftp channel = null; - OutputStream output = null; - try { - session = getSessionByModel(sshModel); - channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); - output = new FileOutputStream(save); - channel.get(remoteFile, output); - } finally { - IoUtil.close(output); - JschUtil.close(channel); - JschUtil.close(session); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/service/node/tomcat/TomcatService.java b/modules/server/src/main/java/io/jpom/service/node/tomcat/TomcatService.java deleted file mode 100644 index 87edbd2e10..0000000000 --- a/modules/server/src/main/java/io/jpom/service/node/tomcat/TomcatService.java +++ /dev/null @@ -1,201 +0,0 @@ -package io.jpom.service.node.tomcat; - -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.service.node.NodeService; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartHttpServletRequest; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * tomcat - * - * @author lf - */ -@Service -public class TomcatService { - - - private final NodeService nodeService; - - public TomcatService(NodeService nodeService) { - this.nodeService = nodeService; - } - - /** - * 查询tomcat列表 - * - * @param nodeModel 节点信息 - * @return tomcat的信息 - */ - public JSONArray getTomcatList(NodeModel nodeModel) { - if (!nodeModel.isOpenStatus()) { - return null; - } - return NodeForward.requestData(nodeModel, NodeUrl.Tomcat_List, JSONArray.class, null, null); - } - - /** - * 查询tomcat信息 - * - * @param nodeModel 节点信息 - * @param id tomcat的id - * @return tomcat的信息 - */ - public JSONObject getTomcatInfo(NodeModel nodeModel, String id) { - return NodeForward.requestData(nodeModel, NodeUrl.Tomcat_GetItem, JSONObject.class, "id", id); - } - - - public JSONArray getTomcatProjectList(NodeModel nodeModel, String id) { - return NodeForward.requestData(nodeModel, NodeUrl.Tomcat_GetTomcatProjectList, JSONArray.class, "id", id); - } - - /** - * tomcat项目管理 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String tomcatProjectManage(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_TomcatProjectManage).toString(); - } - - /** - * 新增Tomcat - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String addTomcat(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Add).toString(); - } - - /** - * 更新Tomcat信息 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String updateTomcat(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Update).toString(); - } - - /** - * 查询tomcat运行状态 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String getTomcatStatus(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_GetTomcatStatus).toString(); - } - - /** - * 启动tomcat - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String start(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Start).toString(); - } - - /** - * 停止tomcat - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String stop(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Stop).toString(); - } - - /** - * 重启tomcat - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String restart(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Restart).toString(); - } - - /** - * 删除tomcat - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String delete(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Delete).toString(); - } - - /** - * 获取文件列表 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String getFileList(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_File_GetFileList).toString(); - } - - /** - * 上传文件 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String upload(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_File_Upload).toString(); - } - - /** - * 下载文件 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @param response 响应信息 - */ - public void download(NodeModel nodeModel, HttpServletRequest request, HttpServletResponse response) { - NodeForward.requestDownload(nodeModel, request, response, NodeUrl.Tomcat_File_Download); - } - - /** - * 删除文件 - * - * @param nodeModel 节点信息 - * @param request 请求信息 - * @return 操作结果 - */ - public String deleteFile(NodeModel nodeModel, HttpServletRequest request) { - return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_File_DeleteFile).toString(); - } - - /** - * 上传War包 - * - * @param node 节点信息 - * @param multiRequest 请求信息 - * @return 操作结果 - */ - public String uploadWar(NodeModel node, MultipartHttpServletRequest multiRequest) { - return NodeForward.requestMultipart(node, multiRequest, NodeUrl.Tomcat_File_UploadWar).toString(); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/package-info.java b/modules/server/src/main/java/io/jpom/service/package-info.java deleted file mode 100644 index 45ed34bc76..0000000000 --- a/modules/server/src/main/java/io/jpom/service/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/service/system/SystemParametersServer.java b/modules/server/src/main/java/io/jpom/service/system/SystemParametersServer.java deleted file mode 100644 index 8d7711feca..0000000000 --- a/modules/server/src/main/java/io/jpom/service/system/SystemParametersServer.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; - -import cn.hutool.core.util.ReflectUtil; -import io.jpom.model.BaseJsonModel; -import io.jpom.model.data.SystemParametersModel; -import io.jpom.service.h2db.BaseDbService; -import org.springframework.stereotype.Service; - -/** - * @author bwcx_jzy - * @since 2021/12/2 - */ -@Service -public class SystemParametersServer extends BaseDbService { - - - /** - * 先尝试更新,更新失败尝试插入 - * - * @param name 参数名称 - * @param jsonModel 参数值 - * @param desc 描述 - */ - public void upsert(String name, BaseJsonModel jsonModel, String desc) { - SystemParametersModel systemParametersModel = new SystemParametersModel(); - systemParametersModel.setId(name); - systemParametersModel.setValue(jsonModel.toString()); - systemParametersModel.setDescription(desc); - super.upsert(systemParametersModel); - } - - /** - * 查询 系统参数 值 - * - * @param name 参数名称 - * @param cls 类 - * @param 泛型 - * @return data - */ - public T getConfig(String name, Class cls) { - SystemParametersModel parametersModel = super.getByKey(name); - if (parametersModel == null) { - return null; - } - return parametersModel.jsonToBean(cls); - } - - /** - * 查询系统参数值,没有数据创建一个空对象 - * - * @param name 参数名称 - * @param cls 类 - * @param 泛型 - * @return data - */ - public T getConfigDefNewInstance(String name, Class cls) { - T config = this.getConfig(name, cls); - return config == null ? ReflectUtil.newInstance(cls) : config; - } -} diff --git a/modules/server/src/main/java/io/jpom/service/system/WhitelistDirectoryService.java b/modules/server/src/main/java/io/jpom/service/system/WhitelistDirectoryService.java deleted file mode 100644 index c3363a9f78..0000000000 --- a/modules/server/src/main/java/io/jpom/service/system/WhitelistDirectoryService.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; - -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.AgentWhitelist; -import io.jpom.model.data.NodeModel; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * 白名单 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -@Service -public class WhitelistDirectoryService { - - public AgentWhitelist getData(NodeModel model) { - return NodeForward.requestData(model, NodeUrl.WhitelistDirectory_data, null, AgentWhitelist.class); - } - - /** - * 获取项目路径白名单 - * - * @param model 实体 - * @return project - */ - public List getProjectDirectory(NodeModel model) { - AgentWhitelist agentWhitelist = getData(model); - if (agentWhitelist == null) { - return null; - } - return agentWhitelist.getProject(); - } - - public List getNgxDirectory(NodeModel model) { - AgentWhitelist agentWhitelist = getData(model); - if (agentWhitelist == null) { - return null; - } - return agentWhitelist.getNginx(); - } - - public List getCertificateDirectory(NodeModel model) { - AgentWhitelist agentWhitelist = getData(model); - if (agentWhitelist == null) { - return null; - } - return agentWhitelist.getCertificate(); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/service/system/WorkspaceEnvVarService.java b/modules/server/src/main/java/io/jpom/service/system/WorkspaceEnvVarService.java deleted file mode 100644 index de9d460f09..0000000000 --- a/modules/server/src/main/java/io/jpom/service/system/WorkspaceEnvVarService.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; - -import io.jpom.model.data.WorkspaceEnvVarModel; -import io.jpom.service.h2db.BaseWorkspaceService; -import org.springframework.stereotype.Service; - -/** - * @author bwcx_jzy - * @since 2021/12/10 - */ -@Service -public class WorkspaceEnvVarService extends BaseWorkspaceService { -} diff --git a/modules/server/src/main/java/io/jpom/service/system/WorkspaceService.java b/modules/server/src/main/java/io/jpom/service/system/WorkspaceService.java deleted file mode 100644 index 61d703d003..0000000000 --- a/modules/server/src/main/java/io/jpom/service/system/WorkspaceService.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; - -import cn.hutool.core.util.ClassUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.model.BaseWorkspaceModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.service.h2db.BaseDbService; -import io.jpom.service.h2db.TableName; -import io.jpom.service.node.NodeService; -import org.springframework.stereotype.Service; - -import java.util.Set; - -/** - * @author bwcx_jzy - * @since 2021/12/3 - */ -@Service -public class WorkspaceService extends BaseDbService { - - /** - * 检查初始化 默认的工作空间 - */ - public void checkInitDefault() { - WorkspaceModel workspaceModel = super.getByKey(WorkspaceModel.DEFAULT_ID); - if (workspaceModel != null) { - return; - } - WorkspaceModel defaultWorkspace = new WorkspaceModel(); - defaultWorkspace.setId(WorkspaceModel.DEFAULT_ID); - defaultWorkspace.setName("默认"); - defaultWorkspace.setDescription("系统默认的工作空间,不能删除"); - super.insert(defaultWorkspace); - DefaultSystemLog.getLog().info("init created default workspace"); - } - - - /** - * 将没有工作空间ID 的数据添加默认值 - */ - public void convertNullWorkspaceId() { - Set> classes = ClassUtil.scanPackage("io.jpom.model", BaseWorkspaceModel.class::isAssignableFrom); - for (Class aClass : classes) { - TableName tableName = aClass.getAnnotation(TableName.class); - if (tableName == null) { - continue; - } - String sql = "update " + tableName.value() + " set workspaceId=? where workspaceId is null"; - NodeService nodeService = SpringUtil.getBean(NodeService.class); - int execute = nodeService.execute(sql, WorkspaceModel.DEFAULT_ID); - if (execute > 0) { - DefaultSystemLog.getLog().info("convertNullWorkspaceId {} {}", tableName.value(), execute); - } - } - - } -} diff --git a/modules/server/src/main/java/io/jpom/service/system/package-info.java b/modules/server/src/main/java/io/jpom/service/system/package-info.java deleted file mode 100644 index 306b3f4613..0000000000 --- a/modules/server/src/main/java/io/jpom/service/system/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.system; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/service/user/UserBindWorkspaceService.java b/modules/server/src/main/java/io/jpom/service/user/UserBindWorkspaceService.java deleted file mode 100644 index d307fdc1db..0000000000 --- a/modules/server/src/main/java/io/jpom/service/user/UserBindWorkspaceService.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.user; - -import cn.hutool.db.Entity; -import io.jpom.model.data.UserBindWorkspaceModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.data.WorkspaceModel; -import io.jpom.service.h2db.BaseDbService; -import io.jpom.service.system.WorkspaceService; -import org.springframework.stereotype.Service; -import org.springframework.util.Assert; - -import java.util.HashSet; -import java.util.List; -import java.util.stream.Collectors; - -/** - * @author bwcx_jzy - * @since 2021/12/4 - */ -@Service -public class UserBindWorkspaceService extends BaseDbService { - - private final WorkspaceService workspaceService; - - public UserBindWorkspaceService(WorkspaceService workspaceService) { - this.workspaceService = workspaceService; - } - - /** - * 更新用户的工作空间信息 - * - * @param userId 用户ID - * @param workspace 工作空间信息 - */ - public void updateUserWorkspace(String userId, List workspace) { - Assert.notEmpty(workspace, "没有任何工作空间信息"); - List list = new HashSet<>(workspace).stream() - // 过滤 - .filter(s -> workspaceService.exists(new WorkspaceModel(s))) - .map(s -> { - UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); - userBindWorkspaceModel.setWorkspaceId(s); - userBindWorkspaceModel.setUserId(userId); - userBindWorkspaceModel.setId(UserBindWorkspaceModel.getId(userId, s)); - return userBindWorkspaceModel; - }) - .collect(Collectors.toList()); - // 删除之前的数据 - UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); - userBindWorkspaceModel.setUserId(userId); - super.del(super.dataBeanToEntity(userBindWorkspaceModel)); - // 重新入库 - super.insert(list); - } - - /** - * 查询用户绑定的工作空间 - * - * @param userId 用户ID - * @return list - */ - public List listUserWorkspace(String userId) { - UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); - userBindWorkspaceModel.setUserId(userId); - return super.listByBean(userBindWorkspaceModel); - } - - /** - * 判断对应的工作空间是否被用户绑定 - * - * @param workspaceId 工作空间ID - * @return true 有用户绑定 - */ - public boolean existsWorkspace(String workspaceId) { - UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); - userBindWorkspaceModel.setWorkspaceId(workspaceId); - return super.exists(userBindWorkspaceModel); - } - - /** - * 查询用户绑定的工作空间 - * - * @param userModel 用户 - * @return list - */ - public List listUserWorkspaceInfo(UserModel userModel) { - if (userModel.isSuperSystemUser()) { - // 超级管理员有所有工作空间权限 - return workspaceService.list(); - } - UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); - userBindWorkspaceModel.setUserId(userModel.getId()); - List userBindWorkspaceModels = super.listByBean(userBindWorkspaceModel); - Assert.notEmpty(userBindWorkspaceModels, "没有任何工作空间信息"); - List collect = userBindWorkspaceModels.stream().map(UserBindWorkspaceModel::getWorkspaceId).collect(Collectors.toList()); - return workspaceService.listById(collect); - } - - /** - * 删除 - * - * @param userId 用户ID - */ - public void deleteByUserId(String userId) { - UserBindWorkspaceModel bindWorkspaceModel = new UserBindWorkspaceModel(); - bindWorkspaceModel.setUserId(userId); - Entity where = super.dataBeanToEntity(bindWorkspaceModel); - super.del(where); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/user/UserService.java b/modules/server/src/main/java/io/jpom/service/user/UserService.java deleted file mode 100644 index e6e5dafe6a..0000000000 --- a/modules/server/src/main/java/io/jpom/service/user/UserService.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.user; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.db.Entity; -import io.jpom.model.data.UserModel; -import io.jpom.service.h2db.BaseDbService; -import org.springframework.stereotype.Service; -import org.springframework.util.Assert; - -import java.util.List; - -/** - * @author bwcx_jzy - * @since 2021/12/3 - */ -@Service -public class UserService extends BaseDbService { - - /** - * 是否需要初始化 - * - * @return true 有系统管理员账号,系统可以正常使用 - */ - public boolean canUse() { - UserModel userModel = new UserModel(); - userModel.setSystemUser(1); - return super.exists(userModel); - } - - @Override - protected void fillSelectResult(UserModel data) { - if (data == null) { - return; - } - data.setSalt(null); - data.setPassword(null); - } - - /** - * 生成 随机盐值 - * - * @return 随机盐值 - */ - public synchronized String generateSalt() { - while (true) { - String salt = RandomUtil.randomString(UserModel.SALT_LEN); - UserModel userModel = new UserModel(); - userModel.setSalt(salt); - boolean exists = super.exists(userModel); - if (exists) { - continue; - } - return salt; - } - } - - /** - * 验证用户md5 - * - * @param userMd5 用户md5 - * @return userModel 用户对象 - */ - public UserModel checkUser(String userMd5) { - UserModel userModel = new UserModel(); - userModel.setPassword(userMd5); - return super.queryByBean(userModel); - } - - /** - * 查询用户 jwt id - * - * @param id 用户id - * @return jwt id - */ - public String getUserJwtId(String id) { - String sql = "select password from USER_INFO where id=?"; - List query = super.query(sql, id); - Entity first = CollUtil.getFirst(query); - Assert.notEmpty(first, "没有对应的用户信息"); - String password = (String) first.get("password"); - Assert.hasText(password, "没有对应的用户信息"); - return password; - } - - /** - * 是否返回系统管理员信息 - * - * @param hiddenSystem 隐藏系统管理员 - * @return list - */ - public List list(boolean hiddenSystem) { - UserModel userModel = new UserModel(); - userModel.setSystemUser(hiddenSystem ? 0 : 1); - return super.listByBean(userModel); - } - - /** - * 当前系统中的系统管理员的数量 - * - * @return int - */ - public long systemUserCount() { - UserModel userModel = new UserModel(); - userModel.setSystemUser(1); - return super.count(super.dataBeanToEntity(userModel)); - } - - /** - * 修改密码 - * - * @param id 账号ID - * @param newPwd 新密码 - */ - public void updatePwd(String id, String newPwd) { - String salt = this.generateSalt(); - UserModel userModel = new UserModel(); - userModel.setId(id); - userModel.setSalt(salt); - userModel.setPassword(SecureUtil.sha1(newPwd + salt)); - super.update(userModel); - } - - /** - * 用户登录 - * - * @param name 用户名 - * @param pwd 密码 - * @return 登录 - */ - public UserModel simpleLogin(String name, String pwd) { - UserModel userModel = super.getByKey(name, false); - if (userModel == null) { - return null; - } - String obj = SecureUtil.sha1(pwd + userModel.getSalt()); - if (StrUtil.equals(obj, userModel.getPassword())) { - super.fillSelectResult(userModel); - return userModel; - } - return null; - } - - /** - * 重置超级管理账号密码 - * - * @return 新密码 - */ - public String restSuperUserPwd() { - UserModel userModel = new UserModel(); - userModel.setParent(UserModel.SYSTEM_ADMIN); - UserModel queryByBean = super.queryByBean(userModel); - if (queryByBean == null) { - return null; - } - String newPwd = RandomUtil.randomString(UserModel.SALT_LEN); - this.updatePwd(queryByBean.getId(), SecureUtil.sha1(newPwd)); - return StrUtil.format("重置超级管理员账号密码成功,登录账号为:{} 新密码为:{}", queryByBean.getId(), newPwd); - } -} diff --git a/modules/server/src/main/java/io/jpom/service/user/package-info.java b/modules/server/src/main/java/io/jpom/service/user/package-info.java deleted file mode 100644 index 2d48d4243a..0000000000 --- a/modules/server/src/main/java/io/jpom/service/user/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.user; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/socket/BaseHandler.java b/modules/server/src/main/java/io/jpom/socket/BaseHandler.java deleted file mode 100644 index 70a29496c4..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/BaseHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.jiangzeyin.common.DefaultSystemLog; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.TextWebSocketHandler; - -/** - * @author bwcx_jzy - * @date 2019/8/9 - */ -public abstract class BaseHandler extends TextWebSocketHandler { - - @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { - DefaultSystemLog.getLog().error(session.getId() + "socket 异常", exception); - destroy(session); - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { - destroy(session); - } - - /** - * 关闭连接 - * - * @param session session - */ - public abstract void destroy(WebSocketSession session); -} diff --git a/modules/server/src/main/java/io/jpom/socket/BaseProxyHandler.java b/modules/server/src/main/java/io/jpom/socket/BaseProxyHandler.java deleted file mode 100644 index ec212e5043..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/BaseProxyHandler.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.system.init.OperateLogController; -import org.springframework.http.HttpHeaders; -import org.springframework.util.Assert; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.io.IOException; -import java.util.Map; - -/** - * 服务端socket 基本类 - * - * @author jiangzeyin - * @date 2019/4/25 - */ -public abstract class BaseProxyHandler extends BaseHandler { - - private final NodeUrl nodeUrl; - - public BaseProxyHandler(NodeUrl nodeUrl) { - this.nodeUrl = nodeUrl; - } - - /** - * 连接参数 - * - * @param attributes 属性 - * @return key, value, key, value..... - */ - protected abstract Object[] getParameters(Map attributes); - - /** - * 是否输出连接成功 消息 - * - * @return true 输出 - */ - protected boolean showHelloMsg() { - return true; - } - - @Override - public void afterConnectionEstablished(WebSocketSession session) throws Exception { - Map attributes = session.getAttributes(); - NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo"); - UserModel userInfo = (UserModel) attributes.get("userInfo"); - - - if (nodeModel != null) { - Object[] parameters = this.getParameters(attributes); - String url = NodeForward.getSocketUrl(nodeModel, nodeUrl, userInfo, parameters); - // 连接节点 - ProxySession proxySession = new ProxySession(url, session); - session.getAttributes().put("proxySession", proxySession); - } - if (this.showHelloMsg()) { - session.sendMessage(new TextMessage(StrUtil.format("欢迎加入:{} 会话id:{} ", userInfo.getName(), session.getId()))); - } - } - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException { - String msg = message.getPayload(); - Map attributes = session.getAttributes(); - ProxySession proxySession = (ProxySession) attributes.get("proxySession"); - JSONObject json = JSONObject.parseObject(msg); - String op = json.getString("op"); - ConsoleCommandOp consoleCommandOp = StrUtil.isNotEmpty(op) ? ConsoleCommandOp.valueOf(op) : null; - if (proxySession != null) { - this.handleTextMessage(attributes, proxySession, json, consoleCommandOp); - } else { - this.handleTextMessage(attributes, session, json, consoleCommandOp); - } - } - - /** - * 消息处理方法 - * - * @param attributes 属性 - * @param session 当前回话 - * @param json 数据 - * @param consoleCommandOp 操作类型 - */ - protected void handleTextMessage(Map attributes, - WebSocketSession session, - JSONObject json, - ConsoleCommandOp consoleCommandOp) throws IOException { - } - - /** - * 消息处理方法 - * - * @param attributes 属性 - * @param proxySession 代理回话 - * @param json 数据 - * @param consoleCommandOp 操作类型 - */ - protected void handleTextMessage(Map attributes, - ProxySession proxySession, - JSONObject json, - ConsoleCommandOp consoleCommandOp) { - } - - public static void logOpt(Class cls, Map attributes, - Object reqData) { - String ip = (String) attributes.get("ip"); - NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo"); - OperateLogController.CacheInfo cacheInfo = new OperateLogController.CacheInfo(); - cacheInfo.setIp(ip); - Feature feature = cls.getAnnotation(Feature.class); - Assert.state(feature != null && feature.cls() != ClassFeature.NULL, "权限功能没有配置正确"); - cacheInfo.setClassFeature(feature.cls()); - cacheInfo.setMethodFeature(MethodFeature.EXECUTE); - cacheInfo.setNodeModel(nodeModel); - cacheInfo.setDataId(null); - String userAgent = (String) attributes.get(HttpHeaders.USER_AGENT); - cacheInfo.setUserAgent(userAgent); - cacheInfo.setReqData(JSONObject.toJSONString(reqData)); - - // 记录操作日志 - UserModel userInfo = (UserModel) attributes.get("userInfo"); - cacheInfo.setMethodFeature(MethodFeature.EXECUTE); - Object proxySession = attributes.get("proxySession"); - try { - attributes.remove("proxySession"); - attributes.put("use_type", "WebSocket"); - attributes.put("class_type", cls.getName()); - OperateLogController operateLogController = SpringUtil.getBean(OperateLogController.class); - operateLogController.log(userInfo, JSONObject.toJSONString(attributes), cacheInfo); - } catch (Exception e) { - DefaultSystemLog.getLog().error("记录操作日志异常", e); - } finally { - if (proxySession != null) { - attributes.put("proxySession", proxySession); - } - } - } - - protected void logOpt(Map attributes, - JSONObject json) { - logOpt(this.getClass(), attributes, json); - } - - @Override - public void destroy(WebSocketSession session) { - try { - if (session.isOpen()) { - session.close(); - } - } catch (IOException ignored) { - } - Map attributes = session.getAttributes(); - ProxySession proxySession = (ProxySession) attributes.get("proxySession"); - if (proxySession != null) { - proxySession.close(); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/HandlerType.java b/modules/server/src/main/java/io/jpom/socket/HandlerType.java deleted file mode 100644 index 49ce7dd8a0..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/HandlerType.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import io.jpom.socket.handler.*; - -/** - * @author bwcx_jzy - * @date 2019/8/9 - */ -public enum HandlerType { - /** - * 脚本模板 - */ - script(ScriptHandler.class), - /** - * tomcat - */ - tomcat(TomcatHandler.class), - /** - * 项目控制台和首页监控 - */ - console(ConsoleHandler.class), - /** - * ssh - */ - ssh(SshHandler.class), - /** - * 节点升级 - */ - nodeUpdate(NodeUpdateHandler.class), - ; - final Class handlerClass; - - HandlerType(Class handlerClass) { - this.handlerClass = handlerClass; - } - - public Class getHandlerClass() { - return handlerClass; - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/ProxySession.java b/modules/server/src/main/java/io/jpom/socket/ProxySession.java deleted file mode 100644 index c3b5749b57..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/ProxySession.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.system.init.OperateLogController; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.handshake.ServerHandshake; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Objects; - -/** - * 代理socket 会话 - * - * @author jiangzeyin - * @date 2019/4/16 - */ -public class ProxySession extends WebSocketClient { - private final WebSocketSession session; - private final OperateLogController logController; - - private ProxySession(URI uri, WebSocketSession session) { - super(uri); - Objects.requireNonNull(session); - this.session = session; - this.connect(); - this.loopOpen(); - logController = SpringUtil.getBean(OperateLogController.class); - } - - /** - * 等待连接成功 - */ - private void loopOpen() { - int count = 0; - while (!this.isOpen() && count < 20) { - ThreadUtil.sleep(500); - count++; - } - } - - public ProxySession(String uri, WebSocketSession session) throws URISyntaxException { - this(new URI(uri), session); - } - - @Override - public void onOpen(ServerHandshake serverHandshake) { - - } - - @Override - public void onMessage(String message) { - try { - session.sendMessage(new TextMessage(message)); - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - } - try { - JSONObject jsonObject = JSONObject.parseObject(message); - String reqId = jsonObject.getString("reqId"); - if (StrUtil.isNotEmpty(reqId)) { - logController.updateLog(reqId, message); - } - } catch (Exception ignored) { - } - } - - @Override - public void onClose(int code, String reason, boolean remote) { - try { - session.close(); - } catch (IOException e) { - DefaultSystemLog.getLog().error("关闭错误", e); - } - } - - @Override - public void onError(Exception ex) { - try { - session.sendMessage(new TextMessage("agent服务端发生异常" + ExceptionUtil.stacktraceToString(ex))); -// SocketSessionUtil.send(session, ); - } catch (IOException ignored) { - } - DefaultSystemLog.getLog().error("发生错误", ex); - } - - @Override - public void send(String text) { - try { - super.send(text); - } catch (Exception e) { - DefaultSystemLog.getLog().error("转发消息失败", e); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/ServerWebSocketConfig.java b/modules/server/src/main/java/io/jpom/socket/ServerWebSocketConfig.java deleted file mode 100644 index 244aa92b7e..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/ServerWebSocketConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import io.jpom.socket.handler.*; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; - -/** - * socket 配置 - * - * @author jiangzeyin - */ -@Configuration -@EnableWebSocket -public class ServerWebSocketConfig implements WebSocketConfigurer { - private final ServerWebSocketInterceptor serverWebSocketInterceptor = new ServerWebSocketInterceptor(); -// private final ServerNodeUpdateWebSocketInterceptor serverNodeUpdateWebSocketInterceptor = new ServerNodeUpdateWebSocketInterceptor(); - - @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - // 控制台 - registry.addHandler(new ConsoleHandler(), "/console") - .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); - // 脚本模板 - registry.addHandler(new ScriptHandler(), "/script_run") - .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); - // tomcat - registry.addHandler(new TomcatHandler(), "/tomcat_log") - .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); - // ssh - registry.addHandler(new SshHandler(), "/ssh") - .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); - // 节点升级 - registry.addHandler(new NodeUpdateHandler(), "/node_update") - .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/ServerWebSocketInterceptor.java b/modules/server/src/main/java/io/jpom/socket/ServerWebSocketInterceptor.java deleted file mode 100644 index cda6aacb04..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/ServerWebSocketInterceptor.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.JpomApplication; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.SshModel; -import io.jpom.model.data.UserModel; -import io.jpom.service.node.NodeService; -import io.jpom.service.node.ssh.SshService; -import io.jpom.service.user.UserService; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.HandshakeInterceptor; - -import javax.servlet.http.HttpServletRequest; -import java.util.Map; - -/** - * socket 拦截器、鉴权 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -public class ServerWebSocketInterceptor implements HandshakeInterceptor { - - @Override - public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { - if (request instanceof ServletServerHttpRequest) { - ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; - HttpServletRequest httpServletRequest = serverHttpRequest.getServletRequest(); - // 判断用户 - String userId = httpServletRequest.getParameter("userId"); - UserService userService = SpringUtil.getBean(UserService.class); - UserModel userModel = userService.checkUser(userId); - if (userModel == null) { - return false; - } - // 验证 node 权限 - String nodeId = httpServletRequest.getParameter("nodeId"); - if (!JpomApplication.SYSTEM_ID.equals(nodeId)) { - NodeService nodeService = SpringUtil.getBean(NodeService.class); - NodeModel nodeModel = nodeService.getByKey(nodeId, userModel); - if (nodeModel == null) { - return false; - } - // - attributes.put("nodeInfo", nodeModel); - } - // 判断拦截类型 - String type = httpServletRequest.getParameter("type"); - HandlerType handlerType; - try { - handlerType = HandlerType.valueOf(type); - } catch (Exception e) { - //throw new JpomRuntimeException("type 错误:" + type); - DefaultSystemLog.getLog().warn("传入的类型错误:{}", type); - return false; - } - switch (handlerType) { - case console: - //控制台 - String projectId = httpServletRequest.getParameter("projectId"); - // 判断权限 -// if (roleService.errorDynamicPermission(userModel, ClassFeature.PROJECT, projectId)) { -// return false; -// } - attributes.put("copyId", httpServletRequest.getParameter("copyId")); - attributes.put("projectId", projectId); - break; - case script: - // 脚本模板 - String scriptId = httpServletRequest.getParameter("scriptId"); -// if (roleService.errorDynamicPermission(userModel, ClassFeature.SCRIPT, scriptId)) { -// return false; -// } - attributes.put("scriptId", scriptId); - break; - case tomcat: - String tomcatId = httpServletRequest.getParameter("tomcatId"); -// if (roleService.errorDynamicPermission(userModel, ClassFeature.TOMCAT, tomcatId)) { -// return false; -// } - attributes.put("tomcatId", tomcatId); - break; - case ssh: - String sshId = httpServletRequest.getParameter("sshId"); - - SshService bean = SpringUtil.getBean(SshService.class); - SshModel sshModel = bean.getByKey(sshId, userModel); - if (sshModel == null) { - return false; - } - Map parameterMap = httpServletRequest.getParameterMap(); - attributes.put("parameterMap", parameterMap); - attributes.put("sshItem", sshModel); - break; - case nodeUpdate: - if (!userModel.isSuperSystemUser()) { - return false; - } - break; - default: - return false; - } - // - String ip = ServletUtil.getClientIP(httpServletRequest); - attributes.put("ip", ip); - // - String userAgent = ServletUtil.getHeaderIgnoreCase(httpServletRequest, HttpHeaders.USER_AGENT); - attributes.put(HttpHeaders.USER_AGENT, userAgent); - attributes.put("userInfo", userModel); - return true; - } - return false; - } - - @Override - public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { - if (exception != null) { - DefaultSystemLog.getLog().error("afterHandshake", exception); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java b/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java deleted file mode 100644 index 0524a4c485..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; - -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.util.BaseFileTailWatcher; -import org.springframework.web.socket.WebSocketSession; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 文件跟随器 - * - * @author jiangzeyin - * @date 2019/07/21 - */ -public class ServiceFileTailWatcher extends BaseFileTailWatcher { - private static final ConcurrentHashMap> CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); - - private ServiceFileTailWatcher(File logFile) throws IOException { - super(logFile); - } - - public static int getOneLineCount() { - return CONCURRENT_HASH_MAP.size(); - } - - /** - * 添加文件监听 - * - * @param file 文件 - * @param session 会话 - * @throws IOException 异常 - */ - public static void addWatcher(File file, WebSocketSession session) throws IOException { - if (!file.exists() || file.isDirectory()) { - throw new IOException("文件不存在或者是目录:" + file.getPath()); - } - ServiceFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.computeIfAbsent(file, s -> { - try { - return new ServiceFileTailWatcher<>(file); - } catch (Exception e) { - DefaultSystemLog.getLog().error("创建文件监听失败", e); - return null; - } - }); - if (agentFileTailWatcher == null) { - throw new IOException("加载文件失败:" + file.getPath()); - } - agentFileTailWatcher.add(session, FileUtil.getName(file)); - agentFileTailWatcher.tailWatcherRun.start(); - } - - /** - * 有客户端离线 - * - * @param session 会话 - */ - public static void offline(WebSocketSession session) { - Collection> collection = CONCURRENT_HASH_MAP.values(); - for (ServiceFileTailWatcher agentFileTailWatcher : collection) { - agentFileTailWatcher.socketSessions.removeIf(session::equals); - if (agentFileTailWatcher.socketSessions.isEmpty()) { - agentFileTailWatcher.close(); - } - } - } - - /** - * 关闭文件 - * - * @param fileName 文件 - */ - public static void offlineFile(File fileName) { - ServiceFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); - if (null == agentFileTailWatcher) { - return; - } - Set socketSessions = agentFileTailWatcher.socketSessions; - for (WebSocketSession socketSession : socketSessions) { - offline(socketSession); - } - agentFileTailWatcher.close(); - } - - /** - * 关闭文件读取流 - * - * @param fileName 文件名 - * @param session 回话 - */ - public static void offlineFile(File fileName, WebSocketSession session) { - ServiceFileTailWatcher serviceFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); - if (null == serviceFileTailWatcher) { - return; - } - Set socketSessions = serviceFileTailWatcher.socketSessions; - for (WebSocketSession socketSession : socketSessions) { - if (socketSession.equals(session)) { - offline(socketSession); - break; - } - } - if (serviceFileTailWatcher.socketSessions.isEmpty()) { - serviceFileTailWatcher.close(); - } - } - - /** - * 关闭 - */ - @Override - protected void close() { - super.close(); - // 清理线程记录 - CONCURRENT_HASH_MAP.remove(this.logFile); - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/client/NodeClient.java b/modules/server/src/main/java/io/jpom/socket/client/NodeClient.java deleted file mode 100644 index 1d8ee4f336..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/client/NodeClient.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.client; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.WebSocketMessageModel; -import io.jpom.model.data.NodeModel; -import io.jpom.system.init.OperateLogController; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.handshake.ServerHandshake; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.ByteBuffer; - -/** - * 节点Client - * - * @author lf - */ -public class NodeClient extends WebSocketClient { - private final WebSocketSession session; - private final OperateLogController logController; - private final NodeModel nodeModel; - - - public NodeClient(String uri, NodeModel nodeModel, WebSocketSession session) throws URISyntaxException { - super(new URI(uri)); - this.session = session; - this.nodeModel = nodeModel; - this.connect(); - this.loopOpen(); - logController = SpringUtil.getBean(OperateLogController.class); - } - - /** - * 等待连接成功 - */ - private void loopOpen() { - int count = 0; - while (!this.isOpen() && count < 20) { - ThreadUtil.sleep(500); - count++; - } - } - - @Override - public void onOpen(ServerHandshake serverHandshake) { - // 连接成功后获取版本信息 - WebSocketMessageModel command = new WebSocketMessageModel("getVersion", this.nodeModel.getId()); - send(command.toString()); - } - - @Override - public void onMessage(String message) { - try { - // 不能并发向同一个客户端发送消息 @author jzy 2021-08-03 - synchronized (session.getId()) { - session.sendMessage(new TextMessage(message)); - } - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - } - try { - JSONObject jsonObject = JSONObject.parseObject(message); - String reqId = jsonObject.getString("reqId"); - if (StrUtil.isNotEmpty(reqId)) { - logController.updateLog(reqId, message); - } - } catch (Exception ignored) { - } - } - - @Override - public void onClose(int code, String reason, boolean remote) { - - } - - @Override - public void send(String text) { - super.send(text); - } - - @Override - public void send(ByteBuffer bytes) { - super.send(bytes); - } - - @Override - public void onError(Exception e) { - DefaultSystemLog.getLog().error("发生异常", e); - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/handler/ConsoleHandler.java b/modules/server/src/main/java/io/jpom/socket/handler/ConsoleHandler.java deleted file mode 100644 index 165d77eb9c..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/handler/ConsoleHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.handler; - -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeUrl; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.socket.BaseProxyHandler; -import io.jpom.socket.ConsoleCommandOp; -import io.jpom.socket.ProxySession; - -import java.util.Map; - -/** - * 控制台消息处理器 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -@Feature(cls = ClassFeature.PROJECT_CONSOLE) -public class ConsoleHandler extends BaseProxyHandler { - - public ConsoleHandler() { - super(NodeUrl.TopSocket); - } - - @Override - protected Object[] getParameters(Map attributes) { - return new Object[]{"projectId", attributes.get("projectId"), "copyId", attributes.get("copyId")}; - } - - @Override - protected void handleTextMessage(Map attributes, - ProxySession proxySession, - JSONObject json, - ConsoleCommandOp consoleCommandOp) { - if(consoleCommandOp!=ConsoleCommandOp.heart) { - super.logOpt(attributes, json); - } - proxySession.send(json.toString()); - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/handler/NodeUpdateHandler.java b/modules/server/src/main/java/io/jpom/socket/handler/NodeUpdateHandler.java deleted file mode 100644 index 96e3a09015..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/handler/NodeUpdateHandler.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.handler; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeForward; -import io.jpom.common.forward.NodeUrl; -import io.jpom.model.AgentFileModel; -import io.jpom.model.WebSocketMessageModel; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.service.node.NodeService; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.socket.BaseProxyHandler; -import io.jpom.socket.ConsoleCommandOp; -import io.jpom.socket.client.NodeClient; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * 节点管理控制器 - * - * @author lf - */ -@SystemPermission -@Feature(cls = ClassFeature.UPGRADE_NODE_LIST) -public class NodeUpdateHandler extends BaseProxyHandler { - - private final ConcurrentMap clientMap = new ConcurrentHashMap<>(); - - private SystemParametersServer systemParametersServer; - private NodeService nodeService; - private UserModel userInfo; - - public NodeUpdateHandler() { - super(null); - } - - private void init(Map attributes) { - systemParametersServer = SpringUtil.getBean(SystemParametersServer.class); - nodeService = SpringUtil.getBean(NodeService.class); - userInfo = (UserModel) attributes.get("userInfo"); - } - - @Override - protected boolean showHelloMsg() { - return false; - } - - @Override - protected Object[] getParameters(Map attributes) { - return new Object[]{}; - } - - private void pullNodeList(WebSocketSession session, String ids) { - List split = StrUtil.split(ids, StrUtil.COMMA); - List nodeModelList = nodeService.listById(split); - if (nodeModelList == null) { - this.onError(session, "没有查询到节点信息:" + ids); - return; - } - for (NodeModel model : nodeModelList) { - if (clientMap.containsKey(model.getId())) { - continue; - } - Map attributes = session.getAttributes(); - String url = NodeForward.getSocketUrl(model, NodeUrl.NodeUpdate, (UserModel) attributes.get("userInfo")); - // 连接节点 - ThreadUtil.execute(() -> { - try { - NodeClient client = new NodeClient(url, model, session); - clientMap.put(model.getId(), client); - } catch (Exception e) { - DefaultSystemLog.getLog().error("创建插件端连接失败", e); - } - }); - } - } - - @Override - public void destroy(WebSocketSession session) { - for (String key : clientMap.keySet()) { - NodeClient client = clientMap.get(key); - if (client.isOpen()) { - client.close(); - } - } - clientMap.clear(); - // - super.destroy(session); - } - - @Override - protected void handleTextMessage(Map attributes, WebSocketSession session, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { - WebSocketMessageModel model = WebSocketMessageModel.getInstance(json.toString()); - this.init(attributes); - String ids = null; - String command = model.getCommand(); - switch (command) { - case "getAgentVersion": - model.setData(getAgentVersion()); - break; - case "updateNode": - super.logOpt(attributes, json); - updateNode(model, session); - break; - default: { - if (StrUtil.startWith(command, "getNodeList:")) { - ids = StrUtil.removePrefix(command, "getNodeList:"); - } - } - break; - } - - if (model.getData() != null) { - this.sendMsg(model, session); - } - if (StrUtil.isNotEmpty(ids)) { - pullNodeList(session, ids); - } - } - - private void onError(WebSocketSession session, String msg) { - WebSocketMessageModel error = new WebSocketMessageModel("onError", ""); - error.setData(msg); - this.sendMsg(error, session); - } - - /** - * 更新节点 - * - * @param model 参数 - */ - private void updateNode(WebSocketMessageModel model, WebSocketSession session) { - JSONObject params = (JSONObject) model.getParams(); - JSONArray ids = params.getJSONArray("ids"); - if (CollUtil.isEmpty(ids)) { - return; - } - try { - AgentFileModel agentFileModel = systemParametersServer.getConfig(AgentFileModel.ID, AgentFileModel.class); - // - if (agentFileModel == null || !FileUtil.exist(agentFileModel.getSavePath())) { - this.onError(session, "Agent JAR包不存在"); - return; - } - for (int i = 0; i < ids.size(); i++) { - int finalI = i; - ThreadUtil.execute(() -> this.updateNodeItem(ids.getString(finalI), session, agentFileModel)); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("升级失败", e); - } - } - - private void updateNodeItem(String id, WebSocketSession session, AgentFileModel agentFileModel) { - try { - NodeModel node = nodeService.getByKey(id); - if (node == null) { - this.onError(session, "没有对应的节点:" + id); - return; - } - NodeClient client = clientMap.get(node.getId()); - if (client == null) { - this.onError(session, "对应的插件端还没有被初始化:" + id); - return; - } - if (client.isOpen()) { - // 发送文件信息 - WebSocketMessageModel webSocketMessageModel = new WebSocketMessageModel("upload", id); - webSocketMessageModel.setNodeId(id); - webSocketMessageModel.setParams(agentFileModel); - client.send(webSocketMessageModel.toString()); - // - try (FileInputStream fis = new FileInputStream(agentFileModel.getSavePath())) { - // 发送文件内容 - int len; - byte[] buffer = new byte[1024 * 1024]; - while ((len = fis.read(buffer)) > 0) { - client.send(ByteBuffer.wrap(buffer, 0, len)); - } - } - WebSocketMessageModel restartMessage = new WebSocketMessageModel("restart", id); - client.send(restartMessage.toString()); - // 重启后尝试访问插件端,能够连接说明重启完毕 - ThreadUtil.execute(() -> { - WebSocketMessageModel callbackRestartMessage = new WebSocketMessageModel("restart", id); - int retryCount = 0; - try { - // 先等待一会,太快可能还没重启 - ThreadUtil.sleep(10000L); - while (retryCount <= 30) { - ++retryCount; - try { - ThreadUtil.sleep(1000L); - if (client.reconnectBlocking()) { - this.sendMsg(callbackRestartMessage.setData("重启完成"), session); - return; - } - } catch (Exception ignored) { - } - } - this.sendMsg(callbackRestartMessage.setData("重连失败"), session); - } catch (Exception e) { - DefaultSystemLog.getLog().error("升级后重连插件端失败:" + id, e); - this.sendMsg(callbackRestartMessage.setData("重连插件端失败"), session); - } - }); - } else { - this.onError(session, "节点连接丢失"); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("升级失败:" + id, e); - this.onError(session, "节点升级失败:" + e.getMessage()); - } - } - - private void sendMsg(WebSocketMessageModel model, WebSocketSession session) { - try { - synchronized (session.getId()) { - session.sendMessage(new TextMessage(model.toString())); - } - } catch (Exception e) { - DefaultSystemLog.getLog().error("发送消息失败", e); - } - } - - /** - * 获取当前系统缓存的Agent - * - * @return json - */ - private String getAgentVersion() { - AgentFileModel agentFileModel = systemParametersServer.getConfig(AgentFileModel.ID, AgentFileModel.class); - if (agentFileModel == null) { - return null; - } - return JSONObject.toJSONString(agentFileModel); - } - -// /** -// * 获取节点列表 -// * -// * @return 节点列表 -// */ -// private List getNodeList() { -// NodeOld1Service nodeService = SpringUtil.getBean(NodeOld1Service.class); -// List nodeModels = nodeService.list(); -// List result = new ArrayList<>(); -// for (NodeModel node : nodeModels) { -// NodeVersionModel model = new NodeVersionModel(); -// model.setId(node.getId()); -// model.setName(node.getName()); -//// model.setGroup(node.getGroup()); -// result.add(model); -// } -// return result; -// } -} diff --git a/modules/server/src/main/java/io/jpom/socket/handler/ScriptHandler.java b/modules/server/src/main/java/io/jpom/socket/handler/ScriptHandler.java deleted file mode 100644 index e5616c2c9c..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/handler/ScriptHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.handler; - -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.forward.NodeUrl; -import io.jpom.permission.SystemPermission; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.socket.BaseProxyHandler; -import io.jpom.socket.ConsoleCommandOp; -import io.jpom.socket.ProxySession; - -import java.util.Map; - -/** - * 脚本模板消息控制器 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -@SystemPermission -@Feature(cls = ClassFeature.UPGRADE_NODE_LIST) -public class ScriptHandler extends BaseProxyHandler { - - public ScriptHandler() { - super(NodeUrl.Script_Run); - } - - @Override - protected Object[] getParameters(Map attributes) { - return new Object[]{"id", attributes.get("scriptId")}; - } - - @Override - protected void handleTextMessage(Map attributes, ProxySession proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) { - if (consoleCommandOp != ConsoleCommandOp.heart) { - super.logOpt(attributes, json); - } - proxySession.send(json.toString()); - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/handler/SshHandler.java b/modules/server/src/main/java/io/jpom/socket/handler/SshHandler.java deleted file mode 100644 index aeafe29e72..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/handler/SshHandler.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.handler; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.ssh.ChannelType; -import cn.hutool.extra.ssh.JschUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.JSONValidator; -import com.jcraft.jsch.ChannelShell; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import io.jpom.model.data.SshModel; -import io.jpom.model.data.UserModel; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.service.dblog.SshTerminalExecuteLogService; -import io.jpom.service.node.ssh.SshService; -import io.jpom.socket.BaseHandler; -import io.jpom.socket.BaseProxyHandler; -import org.springframework.http.HttpHeaders; -import org.springframework.web.socket.BinaryMessage; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * ssh 处理2 - * - * @author bwcx_jzy - * @date 2019/8/9 - */ -@Feature(cls = ClassFeature.SSH_TERMINAL) -public class SshHandler extends BaseHandler { - - private static final ConcurrentHashMap HANDLER_ITEM_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); - private SshTerminalExecuteLogService sshTerminalExecuteLogService; - - @Override - public void afterConnectionEstablished(WebSocketSession session) throws Exception { - Map attributes = session.getAttributes(); - SshModel sshItem = (SshModel) attributes.get("sshItem"); - BaseProxyHandler.logOpt(this.getClass(), attributes, attributes); - // - HandlerItem handlerItem; - try { - handlerItem = new HandlerItem(session, sshItem); - handlerItem.startRead(); - } catch (Exception e) { - // 输出超时日志 @author jzy - DefaultSystemLog.getLog().error("ssh 控制台连接超时", e); - sendBinary(session, "ssh 控制台连接超时"); - this.destroy(session); - return; - } - HANDLER_ITEM_CONCURRENT_HASH_MAP.put(session.getId(), handlerItem); - // - Thread.sleep(1000); - } - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { - HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); - if (handlerItem == null) { - sendBinary(session, "已经离线啦"); - IoUtil.close(session); - return; - } - String payload = message.getPayload(); - if (JSONValidator.from(payload).getType() == JSONValidator.Type.Object) { - JSONObject jsonObject = JSONObject.parseObject(payload); - String data = jsonObject.getString("data"); - if (StrUtil.equals(data, "jpom-heart")) { - // 心跳消息不转发 - return; - } - if (StrUtil.equals(data, "resize")) { - // 缓存区大小 - handlerItem.resize(jsonObject); - return; - } - } - try { - this.sendCommand(handlerItem, payload); - } catch (Exception e) { - sendBinary(session, "Failure:" + e.getMessage()); - DefaultSystemLog.getLog().error("执行命令异常", e); - } - } - - private void sendCommand(HandlerItem handlerItem, String data) throws Exception { - if (handlerItem.checkInput(data)) { - handlerItem.outputStream.write(data.getBytes()); - } else { - handlerItem.outputStream.write("没有执行相关命令权限".getBytes()); - handlerItem.outputStream.flush(); - handlerItem.outputStream.write(new byte[]{3}); - } - handlerItem.outputStream.flush(); - } - - /** - * 记录终端执行记录 - * - * @param session 回话 - * @param command 命令行 - * @param refuse 是否拒绝 - */ - private void logCommands(WebSocketSession session, String command, boolean refuse) { - if (sshTerminalExecuteLogService == null) { - sshTerminalExecuteLogService = SpringUtil.getBean(SshTerminalExecuteLogService.class); - } - List split = StrUtil.split(command, StrUtil.CR); - // 最后一个是否为回车 - boolean all = StrUtil.endWith(command, StrUtil.CR); - int size = split.size(); - split = CollUtil.sub(split, 0, all ? size : size - 1); - if (CollUtil.isEmpty(split)) { - return; - } - // 获取基础信息 - Map attributes = session.getAttributes(); - UserModel userInfo = (UserModel) attributes.get("userInfo"); - String ip = (String) attributes.get("ip"); - String userAgent = (String) attributes.get(HttpHeaders.USER_AGENT); - SshModel sshItem = (SshModel) attributes.get("sshItem"); - // - sshTerminalExecuteLogService.batch(userInfo, sshItem, ip, userAgent, refuse, split); - } - - private class HandlerItem implements Runnable { - private final WebSocketSession session; - private final InputStream inputStream; - private final OutputStream outputStream; - private final Session openSession; - private final ChannelShell channel; - private final SshModel sshItem; - private final StringBuilder nowLineInput = new StringBuilder(); - - HandlerItem(WebSocketSession session, SshModel sshItem) throws IOException { - this.session = session; - this.sshItem = sshItem; - this.openSession = SshService.getSessionByModel(sshItem); - this.channel = (ChannelShell) JschUtil.createChannel(openSession, ChannelType.SHELL); - this.inputStream = channel.getInputStream(); - this.outputStream = channel.getOutputStream(); - } - - void startRead() throws JSchException { - this.channel.connect(); - ThreadUtil.execute(this); - } - - /** - * 调整 缓存区大小 - * - * @param jsonObject 参数 - */ - private void resize(JSONObject jsonObject) { - Integer rows = Convert.toInt(jsonObject.getString("rows"), 10); - Integer cols = Convert.toInt(jsonObject.getString("cols"), 10); - Integer wp = Convert.toInt(jsonObject.getString("wp"), 10); - Integer hp = Convert.toInt(jsonObject.getString("hp"), 10); - this.channel.setPtySize(cols, rows, wp, hp); - } - - /** - * 添加到命令队列 - * - * @param msg 输入 - * @return 当前待确认待所有命令 - */ - private String append(String msg) { - char[] x = msg.toCharArray(); - if (x.length == 1 && x[0] == 127) { - // 退格键 - int length = nowLineInput.length(); - if (length > 0) { - nowLineInput.delete(length - 1, length); - } - } else { - nowLineInput.append(msg); - } - return nowLineInput.toString(); - } - - public boolean checkInput(String msg) { - String allCommand = this.append(msg); - boolean refuse; - if (StrUtil.equalsAny(msg, StrUtil.CR, StrUtil.TAB)) { - String join = nowLineInput.toString(); - if (StrUtil.equals(msg, StrUtil.CR)) { - nowLineInput.setLength(0); - } - refuse = SshModel.checkInputItem(sshItem, join); - } else { - // 复制输出 - refuse = SshModel.checkInputItem(sshItem, msg); - } - // 执行命令行记录 - logCommands(session, allCommand, refuse); - return refuse; - } - - - @Override - public void run() { - try { - byte[] buffer = new byte[1024]; - int i; - //如果没有数据来,线程会一直阻塞在这个地方等待数据。 - while ((i = inputStream.read(buffer)) != -1) { - sendBinary(session, new String(Arrays.copyOfRange(buffer, 0, i), sshItem.getCharsetT())); - } - } catch (Exception e) { - if (!this.openSession.isConnected()) { - return; - } - DefaultSystemLog.getLog().error("读取错误", e); - SshHandler.this.destroy(this.session); - } - } - } - - @Override - public void destroy(WebSocketSession session) { - HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); - if (handlerItem != null) { - IoUtil.close(handlerItem.inputStream); - IoUtil.close(handlerItem.outputStream); - JschUtil.close(handlerItem.channel); - JschUtil.close(handlerItem.openSession); - } - IoUtil.close(session); - HANDLER_ITEM_CONCURRENT_HASH_MAP.remove(session.getId()); - } - - private static void sendBinary(WebSocketSession session, String msg) { - if (!session.isOpen()) { - // 会话关闭不能发送消息 @author jzy 21-08-04 - DefaultSystemLog.getLog().warn("回话已经关闭啦,不能发送消息:{}", msg); - return; - } - synchronized (session.getId()) { - BinaryMessage byteBuffer = new BinaryMessage(msg.getBytes()); - try { - session.sendMessage(byteBuffer); - } catch (IOException e) { - DefaultSystemLog.getLog().error("发送消息失败:" + msg, e); - } - } - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/handler/TomcatHandler.java b/modules/server/src/main/java/io/jpom/socket/handler/TomcatHandler.java deleted file mode 100644 index 26a289383f..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/handler/TomcatHandler.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket.handler; - -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.JpomApplication; -import io.jpom.common.forward.NodeUrl; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.socket.BaseProxyHandler; -import io.jpom.socket.ConsoleCommandOp; -import io.jpom.socket.ProxySession; -import io.jpom.socket.ServiceFileTailWatcher; -import io.jpom.system.WebAopLog; -import io.jpom.util.SocketSessionUtil; -import org.springframework.web.socket.WebSocketSession; - -import java.io.File; -import java.io.IOException; -import java.util.Map; - -/** - * 脚本模板消息控制器 - * - * @author jiangzeyin - * @date 2019/4/24 - */ -@Feature(cls = ClassFeature.TOMCAT) -public class TomcatHandler extends BaseProxyHandler { - - public TomcatHandler() { - super(NodeUrl.Tomcat_Socket); - } - - @Override - protected Object[] getParameters(Map attributes) { - return new Object[]{"tomcatId", attributes.get("tomcatId")}; - } - - @Override - protected void handleTextMessage(Map attributes, WebSocketSession session, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { - String tomcatId = (String) attributes.get("tomcatId"); - String fileName = json.getString("fileName"); - if (!JpomApplication.SYSTEM_ID.equals(tomcatId) && consoleCommandOp == ConsoleCommandOp.heart) { - // 服务端心跳 - return; - } - super.logOpt(attributes, json); - // - if (consoleCommandOp == ConsoleCommandOp.showlog && JpomApplication.SYSTEM_ID.equals(tomcatId)) { - WebAopLog webAopLog = SpringUtil.getBean(WebAopLog.class); - // 进入管理页面后需要实时加载日志 - File file = FileUtil.file(webAopLog.getPropertyValue(), fileName); - // - File nowFile = (File) attributes.get("nowFile"); - if (nowFile != null && !nowFile.equals(file)) { - // 离线上一个日志 - ServiceFileTailWatcher.offlineFile(file, session); - } - try { - ServiceFileTailWatcher.addWatcher(file, session); - attributes.put("nowFile", file); - } catch (Exception io) { - DefaultSystemLog.getLog().error("监听日志变化", io); - SocketSessionUtil.send(session, io.getMessage()); - } - } - } - - @Override - protected void handleTextMessage(Map attributes, ProxySession proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) { - proxySession.send(json.toString()); - } - - @Override - public void destroy(WebSocketSession session) { - super.destroy(session); - ServiceFileTailWatcher.offline(session); - } -} diff --git a/modules/server/src/main/java/io/jpom/socket/package-info.java b/modules/server/src/main/java/io/jpom/socket/package-info.java deleted file mode 100644 index 20fb51c33b..0000000000 --- a/modules/server/src/main/java/io/jpom/socket/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.socket; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/system/AgentException.java b/modules/server/src/main/java/io/jpom/system/AgentException.java deleted file mode 100644 index 96b3aeca59..0000000000 --- a/modules/server/src/main/java/io/jpom/system/AgentException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -/** - * agent 插件端异常 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -public class AgentException extends RuntimeException { - - public AgentException(String message) { - super(message); - } - - public AgentException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/modules/server/src/main/java/io/jpom/system/AuthorizeException.java b/modules/server/src/main/java/io/jpom/system/AuthorizeException.java deleted file mode 100644 index c4de71262a..0000000000 --- a/modules/server/src/main/java/io/jpom/system/AuthorizeException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.jiangzeyin.common.JsonMessage; - -/** - * 授权错误 - * - * @author jiangzeyin - * @date 2019/4/17 - */ -public class AuthorizeException extends RuntimeException { - private final JsonMessage jsonMessage; - - public AuthorizeException(JsonMessage jsonMessage, String msg) { - super(msg); - this.jsonMessage = jsonMessage; - } - - public JsonMessage getJsonMessage() { - return jsonMessage; - } -} diff --git a/modules/server/src/main/java/io/jpom/system/ServerConfigBean.java b/modules/server/src/main/java/io/jpom/system/ServerConfigBean.java deleted file mode 100644 index f1abc12e03..0000000000 --- a/modules/server/src/main/java/io/jpom/system/ServerConfigBean.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.io.FileUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.UserModel; -import org.springframework.context.annotation.Configuration; - -import java.io.File; - -/** - * 配置信息静态变量类 - * - * @author jiangzeyin - * @date 2019/1/16 - */ -@Configuration -public class ServerConfigBean { - /** - * 用户数据文件 - */ - @Deprecated - public static final String USER = "user.json"; - - /** - * 节点数据文件 - */ - @Deprecated - public static final String NODE = "node.json"; - - /** - * Agent信息文件 - */ - @Deprecated - public static final String AGENT_FILE = "agent_file.json"; - - /** - * 分发数据文件 - */ - @Deprecated - public static final String OUTGIVING = "outgiving.json"; - - /** - * 白名单数据 - */ - @Deprecated - public static final String OUTGIVING_WHITELIST = "outgiving_whitelist.json"; - - /** - * 分发包存储路径 - */ - public static final String OUTGIVING_FILE = "outgiving"; - - /** - * 项目监控文件 - */ - @Deprecated - public static final String MONITOR_FILE = "monitor.json"; - - /** - * 监控用户操作文件 - */ - public static final String MONITOR_USER_OPT_FILE = "monitor_user_opt.json"; - - /** - * 邮箱配置 - */ - @Deprecated - public static final String MAIL_CONFIG = "mail_config.json"; - - /** - * 构建数据 - */ - @Deprecated - public static final String BUILD = "build.json"; - - /** - * 第一次服务端安装信息 - */ - public static final String INSTALL = "INSTALL.json"; - - /** - * ssh信息 - */ - @Deprecated - public static final String SSH_LIST = "ssh_list.json"; - - /** - * 用户角色信息 - */ - public static final String ROLE = "user_role.json"; - - /** - * ip配置 - */ - @Deprecated - public static final String IP_CONFIG = "ip_config.json"; - - /** - * token自动续签状态码 - */ - public static final int RENEWAL_AUTHORIZE_CODE = 801; - - /** - * token 失效 - */ - public static final int AUTHORIZE_TIME_OUT_CODE = 800; - - private static ServerConfigBean serverConfigBean; - - /** - * 单利模式 - * - * @return config - */ - public static ServerConfigBean getInstance() { - if (serverConfigBean == null) { - serverConfigBean = SpringUtil.getBean(ServerConfigBean.class); - } - return serverConfigBean; - } - - /** - * 获取当前登录用户的临时文件存储路径,如果没有登录则抛出异常 - * - * @return file - */ - public File getUserTempPath() { - File file = ConfigBean.getInstance().getTempPath(); - UserModel userModel = BaseServerController.getUserModel(); - if (userModel == null) { - throw new JpomRuntimeException("没有登录"); - } - file = FileUtil.file(file, userModel.getId()); - FileUtil.mkdir(file); - return file; - } - - - /** - * 获取保存 agent jar 包目录文件夹 - * - * @return 数据目录下的 agent 目录 - */ - public File getAgentPath() { - File file = new File(ConfigBean.getInstance().getDataPath()); - file = new File(file.getPath() + "/agent/"); - FileUtil.mkdir(file); - return file; - } -} diff --git a/modules/server/src/main/java/io/jpom/system/ServerExtConfigBean.java b/modules/server/src/main/java/io/jpom/system/ServerExtConfigBean.java deleted file mode 100644 index dc122e2219..0000000000 --- a/modules/server/src/main/java/io/jpom/system/ServerExtConfigBean.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.StrUtil; -import cn.hutool.script.ScriptUtil; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.system.db.DbConfig; -import org.eclipse.jgit.api.Git; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.unit.DataSize; - -import java.util.concurrent.TimeUnit; - -/** - * 外部配置文件 - * - * @author jiangzeyin - * @date 2019/3/04 - */ -@Configuration -public class ServerExtConfigBean implements DisposableBean { - - /** - * 系统最多能创建多少用户 - */ - @Value("${user.maxCount:10}") - public int userMaxCount; - /** - * 用户连续登录失败次数,超过此数将自动不再被允许登录,零是不限制 - */ - @Value("${user.alwaysLoginError:5}") - public int userAlwaysLoginError; - - /** - * 当ip连续登录失败,锁定对应IP时长,单位毫秒 - */ - @Value("${user.ipErrorLockTime:60*60*5*1000}") - private String ipErrorLockTime; - private long ipErrorLockTimeValue = -1; - /** - * 日志记录最大条数 - */ - @Value("${db.logStorageCount:100000}") - private int h2DbLogStorageCount; - - /** - * 数据库账号、默认为 jpom - */ - @Value("${db.userName:}") - private String dbUserName; - - /** - * 数据库密码、默认为 jpom - */ - @Value("${db.userPwd:}") - private String dbUserPwd; - - /** - * 缓存大小 - *

- * http://www.h2database.com/html/features.html#cache_settings - */ - @Value("${db.cacheSize:}") - private DataSize cacheSize; - - /** - * 自动全量备份数据库间隔天数 小于等于 0,不自动备份 - */ - @Value("${db.autoBackupIntervalDay:1}") - private Integer autoBackupIntervalDay; - - /** - * author Hotstrip - * 是否开启 web 访问数据库 - * - * @see http://${ip}:${port}/h2-console - */ - @Value("${spring.h2.console.enabled:false}") - private boolean h2ConsoleEnabled; - - /** - * 服务端api token,长度要求大于等于6位,字母数字符号组合 - */ - @Value("${jpom.authorize.token:}") - private String authorizeToken; - - /** - * 登录token失效时间(单位:小时),默认为24 - */ - @Value("${jpom.authorize.expired:24}") - private int authorizeExpired; - - /** - * 登录token失效后自动续签时间(单位:分钟),默认为60, - */ - @Value("${jpom.authorize.renewal:60}") - private int authorizeRenewal; - - /** - * 登录token 加密的key 长度建议控制到 16位 - */ - @Value("${jpom.authorize.key:}") - private String authorizeKey; - - /** - * 构建最多保存多少份历史记录 - */ - @Value("${build.maxHistoryCount:1000}") - private int buildMaxHistoryCount; - - /** - * 每一项构建最多保存的历史份数 - */ - @Value("${build.itemMaxHistoryCount:50}") - private int buildItemMaxHistoryCount; - - @Value("${build.checkDeleteCommand:true}") - private Boolean buildCheckDeleteCommand; - - /** - * ssh 中执行命令 初始化的环境变量 - */ - @Value("${ssh.initEnv:}") - private String sshInitEnv; - - /** - * 上传文件的超时时间 单位秒,最短5秒中 - */ - @Value("${node.uploadFileTimeOut:300}") - private int uploadFileTimeOut; - - /** - * 前端接口 超时时间 单位秒 - */ - @Value("${jpom.webApiTimeout:20}") - private int webApiTimeout; - - /** - * 系统名称 - */ - @Value("${jpom.name:}") - private String name; - - /** - * 系统副名称(标题) 建议4个汉字以内 - */ - @Value("${jpom.subTitle:}") - private String subTitle; - - /** - * 登录页标题 - */ - @Value("${jpom.loginTitle:}") - private String loginTitle; - - /** - * logo 文件路径 - */ - @Value("${jpom.logoFile:}") - private String logoFile; - - /** - * 获取上传文件超时时间 - * - * @return 返回毫秒 - */ - public int getUploadFileTimeOut() { - return Math.max(this.uploadFileTimeOut, 5) * 1000; - } - - public String getSshInitEnv() { - return StrUtil.emptyToDefault(this.sshInitEnv, "source /etc/profile && source ~/.bash_profile && source ~/.bashrc"); - } - - public String getAuthorizeToken() { - return authorizeToken; - } - - public long getIpErrorLockTime() { - if (this.ipErrorLockTimeValue == -1) { - String str = StrUtil.emptyToDefault(this.ipErrorLockTime, "60*60*5*1000"); - this.ipErrorLockTimeValue = Convert.toLong(ScriptUtil.eval(str), TimeUnit.HOURS.toMillis(5)); - } - return this.ipErrorLockTimeValue; - } - - public int getH2DbLogStorageCount() { - return h2DbLogStorageCount; - } - - public int getBuildMaxHistoryCount() { - return buildMaxHistoryCount; - } - - public int getBuildItemMaxHistoryCount() { - return buildItemMaxHistoryCount; - } - - public int getAuthorizeExpired() { - return authorizeExpired; - } - - public int getAuthorizeRenewal() { - return authorizeRenewal; - } - - public String getDbUserName() { - return StrUtil.emptyToDefault(this.dbUserName, DbConfig.DEFAULT_USER_OR_AUTHORIZATION); - } - - public String getDbUserPwd() { - return StrUtil.emptyToDefault(this.dbUserPwd, DbConfig.DEFAULT_USER_OR_AUTHORIZATION); - } - - public boolean isH2ConsoleEnabled() { - return h2ConsoleEnabled; - } - - public byte[] getAuthorizeKey() { - return StrUtil.emptyToDefault(this.authorizeKey, "KZQfFBJTW2v6obS1").getBytes(); - } - - /** - * 数据缓存大小,默认10m, - *

- * SELECT * FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME = 'info.CACHE_MAX_SIZE' - * - * @return dataSize - */ - public DataSize getCacheSize() { - if (cacheSize == null) { - cacheSize = DataSize.ofMegabytes(10); - } - return cacheSize; - } - - public boolean getBuildCheckDeleteCommand() { - return buildCheckDeleteCommand != null && buildCheckDeleteCommand; - } - - /** - * 最小值 10秒 - * - * @return 超时时间(单位秒) - */ - public int getWebApiTimeout() { - return Math.max(this.webApiTimeout, 10); - } - - public String getName() { - return StrUtil.emptyToDefault(name, "Jpom项目管理系统"); - } - - public String getSubTitle() { - return StrUtil.emptyToDefault(subTitle, "项目管理"); - } - - public String getLoginTitle() { - return StrUtil.emptyToDefault(loginTitle, "登录JPOM"); - } - - public String getLogoFile() { - return logoFile; - } - - public Integer getAutoBackupIntervalDay() { - return autoBackupIntervalDay; - } - - /** - * 单例 - * - * @return this - */ - public static ServerExtConfigBean getInstance() { - return SpringUtil.getBean(ServerExtConfigBean.class); - } - - @Override - public void destroy() throws Exception { - try { - Git.shutdown(); - } catch (Exception e) { - Console.error(e.getMessage()); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java b/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java new file mode 100644 index 0000000000..d9ee6e7e19 --- /dev/null +++ b/modules/server/src/main/java/io/jpom/system/ServerLogbackConfig.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package io.jpom.system; + +/** + * @author bwcx_jzy + * @since 2023/3/31 + */ +@Deprecated +public class ServerLogbackConfig extends org.dromara.jpom.system.ServerLogbackConfig { +} diff --git a/modules/server/src/main/java/io/jpom/system/db/DbConfig.java b/modules/server/src/main/java/io/jpom/system/db/DbConfig.java deleted file mode 100644 index 3ff5f65fbb..0000000000 --- a/modules/server/src/main/java/io/jpom/system/db/DbConfig.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.db; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Db; -import cn.hutool.db.Entity; -import cn.hutool.db.Page; -import cn.hutool.db.PageResult; -import cn.hutool.db.sql.Direction; -import cn.hutool.db.sql.Order; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.JpomApplication; -import io.jpom.system.ExtConfigBean; -import io.jpom.system.ServerExtConfigBean; - -import java.io.File; -import java.sql.SQLException; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -/** - * 数据库配置 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -public class DbConfig { - - private static final String DB = "db"; - - /** - * 默认的账号或者密码 - */ - public static final String DEFAULT_USER_OR_AUTHORIZATION = "jpom"; - - private static DbConfig dbConfig; - - /** - * 是否初始化成功 - */ - private volatile boolean init; - - /** - * 单利模式 - * - * @return config - */ - public static DbConfig getInstance() { - if (dbConfig == null) { - dbConfig = new DbConfig(); - } - return dbConfig; - } - - public void initOk() { - init = true; - } - - public boolean isInit() { - return init; - } - - /** - * 获取数据库保存路径 - * - * @return 默认本地数据目录下面的 db 目录 - * @author bwcx_jzy - */ - public File dbLocalPath() { - return FileUtil.file(ExtConfigBean.getInstance().getPath(), DB); - } - - /** - * 获取数据库的jdbc 连接 - * - * @return jdbc - */ - public String getDbUrl() { - File file = FileUtil.file(DbConfig.getInstance().dbLocalPath(), JpomApplication.getAppType().name()); - String path = FileUtil.getAbsolutePath(file); - return StrUtil.format("jdbc:h2:{};CACHE_SIZE={}", path, ServerExtConfigBean.getInstance().getCacheSize().toKilobytes()); - } - - /** - * 加载 本地已经执行的记录 - * - * @return sha1 log - * @author bwcx_jzy - */ - public Set loadExecuteSqlLog() { - File localPath = this.dbLocalPath(); - File file = FileUtil.file(localPath, "execute.init.sql.log"); - if (!FileUtil.isFile(file)) { - // 不存在或者是文件夹 - FileUtil.del(file); - return new LinkedHashSet<>(); - } - List strings = FileUtil.readLines(file, CharsetUtil.CHARSET_UTF_8); - return new LinkedHashSet<>(strings); - } - - /** - * 清除执行记录 - */ - public void clearExecuteSqlLog() { - File localPath = this.dbLocalPath(); - File file = FileUtil.file(localPath, "execute.init.sql.log"); - FileUtil.del(file); - } - - /** - * 保存本地已经执行的记录 - * - * @author bwcx_jzy - */ - public void saveExecuteSqlLog(Set logs) { - File localPath = this.dbLocalPath(); - File file = FileUtil.file(localPath, "execute.init.sql.log"); - FileUtil.writeUtf8Lines(logs, file); - } - - /** - * 清除超限制数量的数据 - * - * @param tableName 表名 - * @param timeClo 时间字段名 - */ - public static void autoClear(String tableName, String timeClo) { - if (ServerExtConfigBean.getInstance().getH2DbLogStorageCount() <= 0) { - return; - } - autoClear(tableName, timeClo, ServerExtConfigBean.getInstance().getH2DbLogStorageCount(), time -> { - Entity entity = Entity.create(tableName); - entity.set(timeClo, "< " + time); - int count = 0; - try { - count = Db.use().setWrapper((Character) null).del(entity); - } catch (SQLException e) { - DefaultSystemLog.getLog().error("清理数据异常", e); - } - DefaultSystemLog.getLog().info("{} 清理了 {}条数据", tableName, count); - }); - } - - - public static void autoClear(String tableName, String timeClo, int maxCount, Consumer consumer) { - autoClear(tableName, timeClo, maxCount, null, consumer); - } - - /** - * 自动清理数据接口 - * - * @param tableName 表名 - * @param timeClo 时间字段 - * @param maxCount 最大数量 - * @param consumer 查询出超过范围的时间回调 - */ - public static void autoClear(String tableName, String timeClo, int maxCount, Consumer whereCon, Consumer consumer) { - if (maxCount <= 0) { - return; - } - ThreadUtil.execute(() -> { - Entity entity = Entity.create(tableName); - if (whereCon != null) { - // 条件 - whereCon.accept(entity); - } - Page page = new Page(maxCount, 1); - page.addOrder(new Order(timeClo, Direction.DESC)); - PageResult pageResult; - try { - pageResult = Db.use().setWrapper((Character) null).page(entity, page); - if (pageResult.isEmpty()) { - return; - } - Entity entity1 = pageResult.get(0); - long time = Convert.toLong(entity1.get(timeClo.toUpperCase()), 0L); - if (time <= 0) { - return; - } - consumer.accept(time); - } catch (SQLException e) { - DefaultSystemLog.getLog().error("数据库查询异常", e); - } - }); - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/AutoImportLocalNode.java b/modules/server/src/main/java/io/jpom/system/init/AutoImportLocalNode.java deleted file mode 100644 index 347b6f3e33..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/AutoImportLocalNode.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.date.DateTime; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; -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 cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.JpomManifest; -import io.jpom.common.RemoteVersion; -import io.jpom.common.Type; -import io.jpom.model.data.NodeModel; -import io.jpom.model.system.AgentAutoUser; -import io.jpom.service.node.NodeService; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import io.jpom.util.JsonFileUtil; -import io.jpom.util.JvmUtil; - -import java.io.File; - -/** - * 自动导入本机节点 - * - * @author jiangzeyin - * @date 2019/4/18 - */ -@PreLoadClass -public class AutoImportLocalNode { - - private static NodeService nodeService; - - @PreLoadMethod - private static void install() { - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.INSTALL); - if (file.exists()) { - return; - } - JSONObject jsonObject = new JSONObject(); - jsonObject.put("installId", IdUtil.fastSimpleUUID()); - jsonObject.put("installTime", DateTime.now().toString()); - jsonObject.put("desc", "请勿删除此文件,服务端安装id和插件端互通关联"); - JsonFileUtil.saveJson(file.getAbsolutePath(), jsonObject); - // 检查新版本 - ThreadUtil.execute(RemoteVersion::loadRemoteInfo); - } - - @PreLoadMethod - private static void loadAgent() { - nodeService = SpringUtil.getBean(NodeService.class); - long count = nodeService.count(); - if (count > 0) { - return; - } - // - try { - Integer mainClassPid = JvmUtil.findMainClassPid(Type.Agent.getApplicationClass()); - if (mainClassPid == null) { - return; - } - findPid(mainClassPid.toString()); - } catch (Exception e) { - DefaultSystemLog.getLog().error("自动添加本机节点错误", e); - } - } - - private static void findPid(String pid) { - File file = ConfigBean.getInstance().getApplicationJpomInfo(Type.Agent); - if (!file.exists() || file.isDirectory()) { - return; - } - // 比较进程id - String json = FileUtil.readString(file, CharsetUtil.CHARSET_UTF_8); - JpomManifest jpomManifest = JSONObject.parseObject(json, JpomManifest.class); - if (!pid.equals(String.valueOf(jpomManifest.getPid()))) { - return; - } - // 判断自动授权文件是否存在 - String path = ConfigBean.getInstance().getAgentAutoAuthorizeFile(jpomManifest.getDataPath()); - if (!FileUtil.exist(path)) { - return; - } - json = FileUtil.readString(path, CharsetUtil.CHARSET_UTF_8); - AgentAutoUser autoUser = JSONObject.parseObject(json, AgentAutoUser.class); - // 判断授权信息 - // - NodeModel nodeModel = new NodeModel(); - nodeModel.setUrl(StrUtil.format("127.0.0.1:{}", jpomManifest.getPort())); - nodeModel.setName("本机"); - // - nodeModel.setLoginPwd(autoUser.getAgentPwd()); - nodeModel.setLoginName(autoUser.getAgentName()); - // - nodeModel.setOpenStatus(1); - nodeService.insertNotFill(nodeModel); - Console.log("自动添加本机节点成功:" + nodeModel.getId()); - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/CheckAuthorizeToken.java b/modules/server/src/main/java/io/jpom/system/init/CheckAuthorizeToken.java deleted file mode 100644 index 2cc274037e..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/CheckAuthorizeToken.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.util.StrUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import io.jpom.system.JpomRuntimeException; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.util.CheckPassword; - -/** - * 验证token 合法性 - * - * @author bwcx_jzy - * @date 2019/8/5 - */ -@PreLoadClass -public class CheckAuthorizeToken { - - @PreLoadMethod - private static void check() { - String authorizeToken = ServerExtConfigBean.getInstance().getAuthorizeToken(); - if (StrUtil.isEmpty(authorizeToken)) { - return; - } - if (authorizeToken.length() < 6) { - DefaultSystemLog.getLog().error("", new JpomRuntimeException("配置的授权token长度小于六位不生效")); - System.exit(-1); - } - int password = CheckPassword.checkPassword(authorizeToken); - if (password != 2) { - DefaultSystemLog.getLog().error("", new JpomRuntimeException("配置的授权token 需要包含数字,字母,符号的组合")); - System.exit(-1); - } - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java b/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java deleted file mode 100644 index ab492b458f..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.lang.Console; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.cron.CronUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.build.BuildUtil; -import io.jpom.common.RemoteVersion; -import io.jpom.service.dblog.BackupInfoService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.node.NodeService; -import io.jpom.util.CronUtils; - -/** - * @author bwcx_jzy - * @date 2019/7/14 - */ -@PreLoadClass -public class CheckMonitor { - - @PreLoadMethod - private static void init() { - MonitorService monitorService = SpringUtil.getBean(MonitorService.class); - boolean status = monitorService.checkCronStatus(); - if (status) { - Console.log("已经开启监听调度:监控"); - } - // - NodeService nodeService = SpringUtil.getBean(NodeService.class); - status = nodeService.checkCronStatus(); - if (status) { - Console.log("已经开启监听调度:节点信息采集"); - } - // 缓存检测调度 - CronUtil.schedule("cache_manger_schedule", "0 0/10 * * * ?", BuildUtil::reloadCacheSize); - ThreadUtil.execute(BuildUtil::reloadCacheSize); - // 开启版本检测调度 - CronUtil.schedule("system_monitor", "0 0 0,12 * * ?", () -> { - try { - BackupInfoService backupInfoService = SpringUtil.getBean(BackupInfoService.class); - backupInfoService.checkAutoBackup(); - // - RemoteVersion.loadRemoteInfo(); - } catch (Exception e) { - DefaultSystemLog.getLog().error("系统调度执行出现错误", e); - } - }); - // 开启调度 - CronUtils.start(); - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/InitDb.java b/modules/server/src/main/java/io/jpom/system/init/InitDb.java deleted file mode 100644 index aaff25f75c..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/InitDb.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.db.Db; -import cn.hutool.db.ds.DSFactory; -import cn.hutool.db.ds.GlobalDSFactory; -import cn.hutool.db.sql.SqlLog; -import cn.hutool.setting.Setting; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import cn.jiangzeyin.common.spring.SpringUtil; -import io.jpom.common.JpomManifest; -import io.jpom.service.node.ProjectInfoCacheService; -import io.jpom.service.system.WorkspaceService; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.system.db.DbConfig; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.annotation.Configuration; - -import java.io.InputStream; -import java.util.Set; - -/** - * 初始化数据库 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -@PreLoadClass(-1) -@Configuration -public class InitDb implements DisposableBean, InitializingBean { - - @PreLoadMethod - private static void init() { - // - DbConfig instance = DbConfig.getInstance(); - ServerExtConfigBean serverExtConfigBean = ServerExtConfigBean.getInstance(); - // - Setting setting = new Setting(); - String dbUrl = instance.getDbUrl(); - setting.set("url", dbUrl); - setting.set("user", serverExtConfigBean.getDbUserName()); - setting.set("pass", serverExtConfigBean.getDbUserPwd()); - // 配置连接池大小 - setting.set("maxActive", "50"); - setting.set("initialSize", "1"); - setting.set("maxWait", "10"); - setting.set("minIdle", "1"); - // 调试模式显示sql 信息 - if (!ConfigBean.getInstance().isPro()) { - - setting.set(SqlLog.KEY_SHOW_SQL, "true"); - /* - @author Hotstrip - sql log only show when it's needed, - if you want to check init sql, - set the [sqlLevel] from [DEBUG] to [INFO] - */ - setting.set(SqlLog.KEY_SQL_LEVEL, "DEBUG"); - setting.set(SqlLog.KEY_SHOW_PARAMS, "true"); - } - Console.log("start load h2 db"); - try { - // 创建连接 - DSFactory dsFactory = DSFactory.create(setting); - /** - * @author Hotstrip - * add another sql init file, if there are more sql file, - * please add it with same way - */ - String[] files = new String[]{ - "classpath:/bin/h2-db-v1.sql", - "classpath:/bin/h2-db-v1.1.sql", - "classpath:/bin/h2-db-v2.sql", - "classpath:/bin/h2-db-v2.1.sql", - "classpath:/bin/h2-db-v3.sql", - "classpath:/bin/h2-db-v3.1.sql", - }; - // 加载 sql 变更记录,避免重复执行 - Set executeSqlLog = instance.loadExecuteSqlLog(); - for (String sqlFile : files) { - InputStream inputStream = ResourceUtil.getStream(sqlFile); - String sql = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); - String sha1 = SecureUtil.sha1(sql); - if (executeSqlLog.contains(sha1)) { - // 已经执行过啦,不再执行 - continue; - } - int rows = Db.use(dsFactory.getDataSource()).execute(sql); - DefaultSystemLog.getLog().info("exec init SQL file: {} complete, and affected rows is: {}", sqlFile, rows); - executeSqlLog.add(sha1); - } - instance.saveExecuteSqlLog(executeSqlLog); - DSFactory.setCurrentDSFactory(dsFactory); - // - } catch (Exception e) { - DefaultSystemLog.getLog().error("初始化数据库失败", e); - System.exit(0); - return; - } - instance.initOk(); - // json load to db - InitDb.loadJsonToDb(); - Console.log("h2 db Successfully loaded, url is 【{}】", dbUrl); - if (JpomManifest.getInstance().isDebug()) { - // - } else { - if (serverExtConfigBean.isH2ConsoleEnabled() - && StrUtil.equals(serverExtConfigBean.getDbUserName(), DbConfig.DEFAULT_USER_OR_AUTHORIZATION) - && StrUtil.equals(serverExtConfigBean.getDbUserPwd(), DbConfig.DEFAULT_USER_OR_AUTHORIZATION)) { - Console.error("【安全警告】数据库账号密码使用默认的情况下不建议开启 h2 数据 web 控制台"); - System.exit(-2); - } - } - } - - private static void loadJsonToDb() { - /** - * @author Hotstrip - * @date 2021-08-03 - * load build.js data to DB - */ - LoadBuildJsonToDB.getInstance().doJsonToSql(); - // @author bwcx_jzy @date 2021-12-02 - LoadJsonConfigToDb instance = LoadJsonConfigToDb.getInstance(); - instance.loadIpConfig(); - instance.loadMailConfig(); - instance.loadOutGivingWhitelistConfig(); - instance.loadUserInfo(); - // init workspace - WorkspaceService workspaceService = SpringUtil.getBean(WorkspaceService.class); - workspaceService.checkInitDefault(); - // - instance.loadNodeInfo(); - instance.loadSshInfo(); - instance.loadMonitorInfo(); - instance.loadOutgivinInfo(); - // - workspaceService.convertNullWorkspaceId(); - instance.convertMonitorLogField(); - // 同步项目 - ProjectInfoCacheService projectInfoCacheService = SpringUtil.getBean(ProjectInfoCacheService.class); - projectInfoCacheService.syncAllNode(); - } - - @Override - public void afterPropertiesSet() throws Exception { - - } - - @Override - public void destroy() throws Exception { - try { - DSFactory dsFactory = GlobalDSFactory.get(); - dsFactory.destroy(); - Console.log("h2 db destroy"); - } catch (Throwable ignored) { - } - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/LoadBuildJsonToDB.java b/modules/server/src/main/java/io/jpom/system/init/LoadBuildJsonToDB.java deleted file mode 100644 index a2db5d698c..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/LoadBuildJsonToDB.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Db; -import cn.hutool.db.ds.DSFactory; -import cn.hutool.db.ds.GlobalDSFactory; -import cn.jiangzeyin.common.DefaultSystemLog; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.jpom.model.data.BuildInfoModel; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.service.h2db.TableName; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import io.jpom.util.JsonFileUtil; -import org.springframework.util.Assert; - -import java.io.File; -import java.io.FileNotFoundException; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.sql.SQLException; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Auto import build.json data to DB - * - * @author Hotstrip - * @date 2021-08-02 - */ -public class LoadBuildJsonToDB { - private LoadBuildJsonToDB() { - - } - - /** - * 静态内部类实现单例模式 - */ - public static class LoadBuildJsonToDBHolder { - private static final LoadBuildJsonToDB INSTANCE = new LoadBuildJsonToDB(); - } - - public static LoadBuildJsonToDB getInstance() { - return LoadBuildJsonToDBHolder.INSTANCE; - } - - /** - * read build.json file to list - * and then use list transfer SQL and execute it - */ - public void doJsonToSql() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 build.json 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.BUILD); - List list = readBuildJsonFileToList(file); - // 判断 list 是否为空 - if (null == list) { - if (!FileUtil.exist(FileUtil.file(backupOldData, ServerConfigBean.BUILD))) { - DefaultSystemLog.getLog().warn("There is no any data, the build.json file maybe no content or file is not exist..."); - } - return; - } - // 转换成 SQL 执行 - initSql(list); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } - - /** - * list data to SQL - * this method is core logic - * 1. load fields will be insert into database from class with reflection - * 2. iterate list data, and transfer each element to SQL (element's properties mapping to SQL param name and value) - * 3. use param map generate SQL - * 4. exec SQL - * --------------------- - * 这个方法是核心逻辑 - * 1.通过反射加载字段,将其插入到数据库中 - * 2. 迭代列表数据,并将每个元素转移到参数集合(元素的属性映射到SQL参数名称和值) - * 3. 使用参数集合对象生成SQL - * 4. 执行SQL - * - * @param list data from build.json - */ - private void initSql(List list) { - // 加载类里面的属性,用反射获取 - final List repositoryFieldList = getClassFieldList(RepositoryModel.class); - final List buildInfoFieldList = getClassFieldList(BuildInfoModel.class); - final Map repositoryCache = new HashMap<>(list.size()); - - // 遍历对象集合 - list.forEach(buildModelVo -> { - DefaultSystemLog.getLog().debug("buildModelVo: {}", JSON.toJSONString(buildModelVo)); - - // 拿到构造 SQL 的参数 - String gitUrl = buildModelVo.getString("gitUrl"); - //buildModelVo.getGitUrl(); - String repositoryId = repositoryCache.get(gitUrl); - if (StrUtil.isEmpty(repositoryId)) { - // 先存储仓库信息 - Map repositoryParamMap = initSqlParamMap(repositoryFieldList, buildModelVo); - // add def protocol - repositoryParamMap.put("PROTOCOL", GitProtocolEnum.HTTP.getCode()); - // 构造 insert SQL 语句 - String insertRepositorySql = initInsertSql(repositoryParamMap, RepositoryModel.class); - // 插入数据库 - insertToDB(insertRepositorySql); - repositoryId = (String) repositoryParamMap.get("ID"); - // cache - repositoryCache.put(gitUrl, repositoryId); - } - - Map buildInfoParamMap = initSqlParamMap(buildInfoFieldList, buildModelVo); - // 绑定仓库ID - buildInfoParamMap.put("REPOSITORYID", repositoryId); - // 构建发布操作信息 - JSONObject jsonObject = new JSONObject(); - String releaseMethodDataId = buildModelVo.getString("releaseMethodDataId"); - jsonObject.put("releaseMethodDataId", releaseMethodDataId); - jsonObject.put("afterOpt", buildModelVo.getInteger("afterOpt")); - jsonObject.put("clearOld", buildModelVo.getBoolean("clearOld")); - jsonObject.put("releaseCommand", buildModelVo.getString("releaseCommand")); - jsonObject.put("releasePath", buildModelVo.getString("releasePath")); - // 保存信息 - buildInfoParamMap.put("EXTRADATA", jsonObject.toJSONString()); - buildInfoParamMap.put("RELEASEMETHODDATAID", releaseMethodDataId); - String insertBuildInfoSql = initInsertSql(buildInfoParamMap, BuildInfoModel.class); - - insertToDB(insertBuildInfoSql); - }); - } - - /** - * exec insert SQL to DB - * - * @param sql SQL for insert - */ - private void insertToDB(String sql) { - DSFactory dsFactory = GlobalDSFactory.get(); - int rows = 0; - try { - rows = Db.use(dsFactory.getDataSource()).execute(sql); - } catch (SQLException e) { - DefaultSystemLog.getLog().warn("exec SQL: {} failed", sql, e); - } - DefaultSystemLog.getLog().info("exec SQL: {} complete, and affected rows is: {}", sql, rows); - } - - /** - * init insert SQL with param map and table name - * - * @param paramMap - * @param clazz 实体类 - * @return - */ - private String initInsertSql(Map paramMap, Class clazz) { - TableName tableName = clazz.getAnnotation(TableName.class); - Assert.notNull(tableName, "not find table name"); - // 构造 insert SQL 语句 - StringBuffer sqlBuffer = new StringBuffer("merge into {} ( "); - StringBuilder sqlFieldNameBuffer = new StringBuilder(); - StringBuilder sqlFieldValueBuffer = new StringBuilder(); - for (int i = 0; i < paramMap.size(); i++) { - sqlFieldNameBuffer.append("`{}`,"); - sqlFieldValueBuffer.append("'{}',"); - } - sqlBuffer.append(sqlFieldNameBuffer.substring(0, sqlFieldNameBuffer.length() - 1)) - .append(" )") - .append(" values ( ") - .append(sqlFieldValueBuffer.substring(0, sqlFieldValueBuffer.length() - 1)) - .append(" )"); - - // 构造 SQL 参数 - List params = new ArrayList<>(); - params.add(tableName.value()); - params.addAll(paramMap.keySet()); - params.addAll(paramMap.values()); - return StrUtil.format(sqlBuffer, params.toArray()); - } - - /** - * init param map for create insert SQL - * - * @param fieldList 字段名 list - * @param jsonObject json - * @return map key value - */ - private Map initSqlParamMap(List fieldList, JSONObject jsonObject) { - Map map = new HashMap<>(fieldList.size()); - - fieldList.forEach(fieldName -> { - // 判断类里面是否有这个属性 - Object filedValue = jsonObject.get(fieldName); - if (filedValue == null) { - return; - } - // 添加到参数对象中 - String sqlFiledName = fieldName.toUpperCase(); - map.put(sqlFiledName, filedValue); - }); - // 同步数据创建时间 - String modifyTime = jsonObject.getString("modifyTime"); - if (StrUtil.isNotEmpty(modifyTime)) { - map.put("CREATETIMEMILLIS", DateUtil.parse(modifyTime).getTime()); - } - return map; - } - - /** - * read build.json file to list - * - * @return List - */ - private List readBuildJsonFileToList(File file) { - if (!file.exists()) { - DefaultSystemLog.getLog().debug("there is no build.json file..."); - return null; - } - try { - // 读取 build.json 文件里面的内容,转换成实体对象集合 - JSONObject jsonObject = (JSONObject) JsonFileUtil.readJson(file.getAbsolutePath()); - return jsonObject.keySet().stream() - .map(jsonObject::get) - .flatMap((Function>) o -> Stream.of((JSONObject) o)) - .collect(Collectors.toList()); - } catch (FileNotFoundException e) { - DefaultSystemLog.getLog().error("read build.json file failed...caused: {}...message: {}", e.getCause(), e.getMessage()); - } - return null; - } - - /** - * 获取 clazz 类里面的属性,转换成集合返回 - * - * @param clazz 实体类 - * @return List - */ - private List getClassFieldList(Class clazz) { - final Field[] fields = ReflectUtil.getFieldsDirectly(clazz, true); - return Arrays.stream(fields) - .filter(field -> Modifier.isPrivate(field.getModifiers())) - .flatMap(field -> Arrays.stream(new String[]{field.getName()})) - .collect(Collectors.toList()); - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/LoadJsonConfigToDb.java b/modules/server/src/main/java/io/jpom/system/init/LoadJsonConfigToDb.java deleted file mode 100644 index 3933be9c92..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/LoadJsonConfigToDb.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.db.Entity; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.model.data.*; -import io.jpom.model.log.MonitorNotifyLog; -import io.jpom.service.dblog.DbMonitorNotifyLogService; -import io.jpom.service.monitor.MonitorService; -import io.jpom.service.node.NodeService; -import io.jpom.service.node.OutGivingServer; -import io.jpom.service.node.ssh.SshService; -import io.jpom.service.system.SystemParametersServer; -import io.jpom.service.user.UserService; -import io.jpom.system.ConfigBean; -import io.jpom.system.ServerConfigBean; -import io.jpom.util.JsonFileUtil; - -import java.io.File; -import java.util.List; -import java.util.stream.Collectors; - -/** - * json 配置转存 h2 - * - * @author bwcx_jzy - * @since 2021/12/2 - */ -public class LoadJsonConfigToDb { - - private LoadJsonConfigToDb() { - } - - /** - * 静态内部类实现单例模式 - */ - private static class LoadIpConfigToDbHolder { - private static final LoadJsonConfigToDb INSTANCE = new LoadJsonConfigToDb(); - } - - public static LoadJsonConfigToDb getInstance() { - return LoadJsonConfigToDb.LoadIpConfigToDbHolder.INSTANCE; - } - - public void loadIpConfig() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 IP_CONFIG 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.IP_CONFIG); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - SystemIpConfigModel systemIpConfigModel = json.toJavaObject(SystemIpConfigModel.class); - if (systemIpConfigModel == null) { - return; - } - SystemParametersServer parametersServer = SpringUtil.getBean(SystemParametersServer.class); - parametersServer.upsert(SystemIpConfigModel.ID, systemIpConfigModel, SystemIpConfigModel.ID); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load ip config error ", e); - } - } - - public void loadMailConfig() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 MAIL_CONFIG 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.MAIL_CONFIG); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - MailAccountModel mailAccountModel = json.toJavaObject(MailAccountModel.class); - if (mailAccountModel == null) { - return; - } - SystemParametersServer parametersServer = SpringUtil.getBean(SystemParametersServer.class); - parametersServer.upsert(MailAccountModel.ID, mailAccountModel, MailAccountModel.ID); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load mail config error ", e); - } - } - - public void loadOutGivingWhitelistConfig() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 OUTGIVING_WHITELIST 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.OUTGIVING_WHITELIST); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - ServerWhitelist serverWhitelist = json.toJavaObject(ServerWhitelist.class); - if (serverWhitelist == null) { - return; - } - SystemParametersServer parametersServer = SpringUtil.getBean(SystemParametersServer.class); - parametersServer.upsert(ServerWhitelist.ID, serverWhitelist, ServerWhitelist.ID); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load mail config error ", e); - } - } - - - public void loadUserInfo() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 USER 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.USER); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - JSONArray jsonArray = JsonFileUtil.formatToArray((JSONObject) json); - List userModels = jsonArray.toJavaList(UserModel.class); - if (userModels == null) { - return; - } - UserService userService = SpringUtil.getBean(UserService.class); - userModels = userModels.stream().peek(userModel -> { - //userModel.setRoles((Set) null); - userModel.setSystemUser(UserModel.SYSTEM_ADMIN.equals(userModel.getParent()) ? 1 : 0); - // - String salt = userService.generateSalt(); - userModel.setSalt(salt); - userModel.setPassword(SecureUtil.sha1(userModel.getPassword() + salt)); - }).collect(Collectors.toList()); - - userService.insert(userModels); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load user info error ", e); - } - } - - public void loadNodeInfo() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 node 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.NODE); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - JSONArray jsonArray = JsonFileUtil.formatToArray((JSONObject) json); - List nodeModels = jsonArray.toJavaList(NodeModel.class); - if (nodeModels == null) { - return; - } - BaseServerController.resetInfo(UserModel.EMPTY); - NodeService nodeService = SpringUtil.getBean(NodeService.class); - nodeService.insert(nodeModels); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load node error ", e); - } finally { - BaseServerController.remove(); - } - } - - public void loadSshInfo() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 ssh 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.SSH_LIST); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - JSONArray jsonArray = JsonFileUtil.formatToArray((JSONObject) json); - List sshModels = jsonArray.toJavaList(SshModel.class); - if (sshModels == null) { - return; - } - BaseServerController.resetInfo(UserModel.EMPTY); - SshService sshService = SpringUtil.getBean(SshService.class); - sshService.insert(sshModels); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load ssh error ", e); - } finally { - BaseServerController.remove(); - } - } - - public void loadMonitorInfo() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 monitor 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.MONITOR_FILE); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - JSONArray jsonArray = JsonFileUtil.formatToArray((JSONObject) json); - List monitorModels = jsonArray.toJavaList(MonitorModel.class); - if (monitorModels == null) { - return; - } - BaseServerController.resetInfo(UserModel.EMPTY); - MonitorService monitorService = SpringUtil.getBean(MonitorService.class); - monitorService.insert(monitorModels); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load monitor error ", e); - } finally { - BaseServerController.remove(); - } - } - - - public void loadOutgivinInfo() { - File backupOldData = FileUtil.file(ConfigBean.getInstance().getDataPath(), "backup_old_data"); - // 读取 outgiving 文件内容 - File file = FileUtil.file(ConfigBean.getInstance().getDataPath(), ServerConfigBean.OUTGIVING); - if (!FileUtil.exist(file)) { - return; - } - try { - JSON json = JsonFileUtil.readJson(file.getAbsolutePath()); - JSONArray jsonArray = JsonFileUtil.formatToArray((JSONObject) json); - List outGivingModels = jsonArray.toJavaList(OutGivingModel.class); - if (outGivingModels == null) { - return; - } - BaseServerController.resetInfo(UserModel.EMPTY); - OutGivingServer outGivingServer = SpringUtil.getBean(OutGivingServer.class); - outGivingServer.insert(outGivingModels); - // 将 json 文件转移到备份目录 - FileUtil.move(file, FileUtil.mkdir(backupOldData), true); - DefaultSystemLog.getLog().info("{} mv to {}", FileUtil.getAbsolutePath(file), FileUtil.getAbsolutePath(backupOldData)); - } catch (Exception e) { - DefaultSystemLog.getLog().error("load OUTGIVING error ", e); - } finally { - BaseServerController.remove(); - } - } - - /** - * 将 监控报警记录 里面但 logId 字段更新为 id - */ - public void convertMonitorLogField() { - DbMonitorNotifyLogService monitorNotifyLogService = SpringUtil.getBean(DbMonitorNotifyLogService.class); - String tableName = monitorNotifyLogService.getTableName(); - List query = monitorNotifyLogService.query("select * from " + tableName + " order by createTime asc limit 1"); - Entity first = CollUtil.getFirst(query); - if (first == null) { - this.checkLogFiled(monitorNotifyLogService); - return; - } - Object logId = first.get("logId"); - if (logId == null) { - this.checkLogFiled(monitorNotifyLogService); - return; - } - String sql = "update " + tableName + " set ID = LOGID where ID = '' and LOGID is not null and LOGID <> '';"; - int execute = monitorNotifyLogService.execute(sql); - if (execute > 0) { - Console.log("convert monitor log field {}", execute); - } - // 标记包含旧字段 - MonitorNotifyLog.HAS_LOG_ID = true; - } - - private void checkLogFiled(DbMonitorNotifyLogService monitorNotifyLogService) { - //show COLUMNS from tablename - List query = monitorNotifyLogService.query("show COLUMNS from " + monitorNotifyLogService.getTableName()); - MonitorNotifyLog.HAS_LOG_ID = query.stream().anyMatch(entity -> { - Object field = entity.get("field"); - return StrUtil.equalsIgnoreCase(StrUtil.toString(field), "LOGID"); - }); - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/OperateLogController.java b/modules/server/src/main/java/io/jpom/system/init/OperateLogController.java deleted file mode 100644 index f4e4b80880..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/OperateLogController.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; - -import cn.hutool.core.date.SystemClock; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; -import cn.hutool.extra.servlet.ServletUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import cn.jiangzeyin.common.JsonMessage; -import cn.jiangzeyin.common.PreLoadClass; -import cn.jiangzeyin.common.PreLoadMethod; -import cn.jiangzeyin.common.spring.SpringUtil; -import com.alibaba.fastjson.JSONObject; -import io.jpom.common.BaseServerController; -import io.jpom.common.Const; -import io.jpom.model.data.NodeModel; -import io.jpom.model.data.UserModel; -import io.jpom.model.log.UserOperateLogV1; -import io.jpom.plugin.ClassFeature; -import io.jpom.plugin.Feature; -import io.jpom.plugin.MethodFeature; -import io.jpom.service.dblog.DbUserOperateLogService; -import io.jpom.system.AopLogInterface; -import io.jpom.system.WebAopLog; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.Signature; -import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; -import java.util.Map; -import java.util.Set; - -/** - * 操作记录控制器 - * - * @author jiangzeyin - * @date 2019/4/19 - */ -@PreLoadClass -public class OperateLogController implements AopLogInterface { - private static final ThreadLocal CACHE_INFO_THREAD_LOCAL = new ThreadLocal<>(); - - - @PreLoadMethod - private static void init() { - WebAopLog.setAopLogInterface(SpringUtil.getBean(OperateLogController.class)); - } - - private CacheInfo createCacheInfo(Method method) { - Feature methodFeature = method.getAnnotation(Feature.class); - if (methodFeature == null) { - return null; - } - Class declaringClass = method.getDeclaringClass(); - MethodFeature feature = methodFeature.method(); - if (feature == MethodFeature.NULL) { - DefaultSystemLog.getLog().error("权限分发配置错误:{} {}", declaringClass, method.getName()); - return null; - } - ClassFeature cls = methodFeature.cls(); - if (cls == null || cls == ClassFeature.NULL) { - Feature classFeature = declaringClass.getAnnotation(Feature.class); - if (classFeature == null || classFeature.cls() == ClassFeature.NULL) { - DefaultSystemLog.getLog().error("权限分发配置错误:{} {} class not find", declaringClass, method.getName()); - return null; - } - cls = classFeature.cls(); - } - CacheInfo cacheInfo = new CacheInfo(); - cacheInfo.classFeature = cls; - cacheInfo.methodFeature = feature; - cacheInfo.optTime = SystemClock.now(); - return cacheInfo; - } - - @Override - public void before(ProceedingJoinPoint joinPoint) { - Signature signature = joinPoint.getSignature(); - if (signature instanceof MethodSignature) { - MethodSignature methodSignature = (MethodSignature) signature; - Method method = methodSignature.getMethod(); - CacheInfo cacheInfo = this.createCacheInfo(method); - if (cacheInfo == null) { - return; - } - // - ServletRequestAttributes servletRequestAttributes = BaseServerController.getRequestAttributes(); - HttpServletRequest request = servletRequestAttributes.getRequest(); - // 获取ip地址 - cacheInfo.ip = ServletUtil.getClientIP(request); - // 获取节点 - cacheInfo.nodeModel = (NodeModel) request.getAttribute("node"); - // - cacheInfo.dataId = request.getParameter("id"); - // - cacheInfo.userAgent = ServletUtil.getHeaderIgnoreCase(request, HttpHeaders.USER_AGENT); - cacheInfo.workspaceId = ServletUtil.getHeaderIgnoreCase(request, Const.WORKSPACEID_REQ_HEADER); - // - Map map = ServletUtil.getParamMap(request); - // 过滤密码字段 - Set> entries = map.entrySet(); - for (Map.Entry entry : entries) { - String key = entry.getKey(); - if (StrUtil.containsAnyIgnoreCase(key, "pwd", "password")) { - entry.setValue("***"); - } - } - map.put("request_url", request.getRequestURI()); - cacheInfo.reqData = JSONObject.toJSONString(map); - CACHE_INFO_THREAD_LOCAL.set(cacheInfo); - } - } - - @Override - public void afterReturning(Object value) { - try { - CacheInfo cacheInfo = CACHE_INFO_THREAD_LOCAL.get(); - if (cacheInfo == null || cacheInfo.methodFeature == MethodFeature.LIST) { - return; - } - if (cacheInfo.classFeature == null || cacheInfo.methodFeature == null) { - new RuntimeException("权限功能没有配置正确").printStackTrace(); - return; - } - UserModel userModel = BaseServerController.getUserByThreadLocal(); - userModel = userModel == null ? BaseServerController.getUserModel() : userModel; - // 没有对应的用户 - if (userModel == null) { - return; - } - this.log(userModel, value, cacheInfo); - } finally { - CACHE_INFO_THREAD_LOCAL.remove(); - } - } - - /** - * 记录操作日志 - * - * @param userModel 用户 - * @param value 返回执行 - * @param cacheInfo 请求信息 - */ - public void log(UserModel userModel, Object value, CacheInfo cacheInfo) { - UserOperateLogV1 userOperateLogV1 = new UserOperateLogV1(); - userOperateLogV1.setWorkspaceId(cacheInfo.workspaceId); - userOperateLogV1.setClassFeature(cacheInfo.classFeature.name()); - userOperateLogV1.setMethodFeature(cacheInfo.methodFeature.name()); - userOperateLogV1.setDataId(cacheInfo.dataId); - userOperateLogV1.setUserId(userModel.getId()); - userOperateLogV1.setIp(cacheInfo.ip); - userOperateLogV1.setUserAgent(cacheInfo.userAgent); - userOperateLogV1.setReqData(cacheInfo.reqData); - userOperateLogV1.setOptTime(ObjectUtil.defaultIfNull(cacheInfo.optTime, SystemClock.now())); - if (value != null) { - // 解析结果 - if (value instanceof Throwable) { - // 发生异常 - Throwable throwable = (Throwable) value; - userOperateLogV1.setResultMsg(ExceptionUtil.stacktraceToString(throwable)); - userOperateLogV1.setOptStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - } else { - String json = value.toString(); - userOperateLogV1.setResultMsg(json); - try { - JsonMessage jsonMessage = JSONObject.parseObject(json, JsonMessage.class); - userOperateLogV1.setOptStatus(jsonMessage.getCode()); - } catch (Exception ignored) { - } - } - } - // - if (cacheInfo.nodeModel != null) { - userOperateLogV1.setNodeId(cacheInfo.nodeModel.getId()); - if (StrUtil.isEmpty(cacheInfo.workspaceId)) { - userOperateLogV1.setWorkspaceId(cacheInfo.nodeModel.getWorkspaceId()); - } - } - // - try { - BaseServerController.resetInfo(UserModel.EMPTY); - DbUserOperateLogService dbUserOperateLogService = SpringUtil.getBean(DbUserOperateLogService.class); - dbUserOperateLogService.insert(userOperateLogV1); - } finally { - BaseServerController.remove(); - } - } - - - /** - * 修改执行结果 - * - * @param reqId 请求id - * @param val 结果 - */ - public void updateLog(String reqId, String val) { - DbUserOperateLogService dbUserOperateLogService = SpringUtil.getBean(DbUserOperateLogService.class); - - Entity entity = new Entity(); - entity.set("resultMsg", val); - try { - JsonMessage jsonMessage = JSONObject.parseObject(val, JsonMessage.class); - entity.set("optStatus", jsonMessage.getCode()); - } catch (Exception ignored) { - } - // - Entity where = new Entity(); - where.set("reqId", reqId); - dbUserOperateLogService.update(entity, where); - } - - /** - * 临时缓存 - */ - public static class CacheInfo { - private Long optTime; - private String workspaceId; - private ClassFeature classFeature; - private MethodFeature methodFeature; - private String ip; - private NodeModel nodeModel; - private String dataId; - private String userAgent; - private String reqData; - - public void setOptTime(Long optTime) { - this.optTime = optTime; - } - - public void setWorkspaceId(String workspaceId) { - this.workspaceId = workspaceId; - } - - public void setClassFeature(ClassFeature classFeature) { - this.classFeature = classFeature; - } - - public void setMethodFeature(MethodFeature methodFeature) { - this.methodFeature = methodFeature; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public void setNodeModel(NodeModel nodeModel) { - this.nodeModel = nodeModel; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public void setReqData(String reqData) { - this.reqData = reqData; - } - } -} diff --git a/modules/server/src/main/java/io/jpom/system/init/package-info.java b/modules/server/src/main/java/io/jpom/system/init/package-info.java deleted file mode 100644 index f2a9396cac..0000000000 --- a/modules/server/src/main/java/io/jpom/system/init/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system.init; diff --git a/modules/server/src/main/java/io/jpom/system/package-info.java b/modules/server/src/main/java/io/jpom/system/package-info.java deleted file mode 100644 index 8756ccf78d..0000000000 --- a/modules/server/src/main/java/io/jpom/system/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.system; \ No newline at end of file diff --git a/modules/server/src/main/java/io/jpom/util/CheckPassword.java b/modules/server/src/main/java/io/jpom/util/CheckPassword.java deleted file mode 100644 index 2389b13021..0000000000 --- a/modules/server/src/main/java/io/jpom/util/CheckPassword.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -/** - * 判断密码强度 - * - * @author jiangzeyin - * @date 2019/3/18 - */ -public class CheckPassword { - - private static final String REGEX_Z = "\\d*"; - private static final String REGEX_S = "[a-zA-Z]+"; - private static final String REGEX_T = "\\W+$"; - private static final String REGEX_ZT = "\\D*"; - private static final String REGEX_ST = "[\\d\\W]*"; - private static final String REGEX_ZS = "\\w*"; - private static final String REGEX_ZST = "[\\w\\W]*"; - - /** - * 密码强度 - * Z = 字母 S = 数字 T = 特殊字符 - * - * @param passwordStr 密码字符串 - * @return 0 弱 1 中 2强 - */ - public static int checkPassword(String passwordStr) { - if (passwordStr.matches(REGEX_Z)) { - return 0; - } - if (passwordStr.matches(REGEX_S)) { - return 0; - } - if (passwordStr.matches(REGEX_T)) { - return 0; - } - if (passwordStr.matches(REGEX_ZT)) { - return 1; - } - if (passwordStr.matches(REGEX_ST)) { - return 1; - } - if (passwordStr.matches(REGEX_ZS)) { - return 1; - } - if (passwordStr.matches(REGEX_ZST)) { - return 2; - } - return -1; - } -} diff --git a/modules/server/src/main/java/io/jpom/util/GitUtil.java b/modules/server/src/main/java/io/jpom/util/GitUtil.java deleted file mode 100644 index 8d90bb1817..0000000000 --- a/modules/server/src/main/java/io/jpom/util/GitUtil.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.collection.CollStreamUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.comparator.VersionComparator; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Tuple; -import cn.hutool.core.util.StrUtil; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import io.jpom.build.BuildUtil; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.system.JpomRuntimeException; -import org.eclipse.jgit.api.*; -import org.eclipse.jgit.api.errors.CheckoutConflictException; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.NoHeadException; -import org.eclipse.jgit.api.errors.TransportException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.*; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.*; -import org.eclipse.jgit.util.FS; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.Assert; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.*; -import java.util.stream.Collectors; - -/** - * git工具 - *

- * https://developer.aliyun.com/ask/275691 - *

- * https://github.com/centic9/jgit-cookbook - * - * @author bwcx_jzy - * @author Hotstrip - * add git with ssh key to visit repository - * @date 2019/7/15 - **/ -public class GitUtil { - - private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher(); - - /** - * 检查本地的remote是否存在对应的url - * - * @param url 要检查的url - * @param file 本地仓库文件 - * @return true 存在对应url - * @throws IOException IO - * @throws GitAPIException E - */ - private static boolean checkRemoteUrl(String url, File file) throws IOException, GitAPIException { - try (Git git = Git.open(file)) { - RemoteListCommand remoteListCommand = git.remoteList(); - boolean urlTrue = false; - List list = remoteListCommand.call(); - end: - for (RemoteConfig remoteConfig : list) { - for (URIish urIish : remoteConfig.getURIs()) { - if (urIish.toString().equals(url)) { - urlTrue = true; - break end; - } - } - } - return urlTrue; - } - } - - /** - * 删除重新clone - * - * @param repositoryModel 仓库 - * @param file 文件 - * @return git - * @throws GitAPIException api - * @throws IOException 删除文件失败 - */ - private static Git reClone(RepositoryModel repositoryModel, String branchName, File file, PrintWriter printWriter) throws GitAPIException, IOException { - if (!FileUtil.clean(file)) { - FileUtil.del(file.toPath()); - //throw new IOException("del error:" + file.getPath()); - } - CloneCommand cloneCommand = Git.cloneRepository(); - if (printWriter != null) { - cloneCommand.setProgressMonitor(new TextProgressMonitor(printWriter)); - } - if (branchName != null) { - cloneCommand.setBranch(Constants.R_HEADS + branchName); - cloneCommand.setBranchesToClone(Collections.singletonList(Constants.R_HEADS + branchName)); - } - CloneCommand command = cloneCommand.setURI(repositoryModel.getGitUrl()) - .setDirectory(file); - // 设置凭证 - setCredentials(command, repositoryModel); - return command.call(); - } - - /** - * 设置仓库凭证 - * - * @param transportCommand git 相关操作 - * @param repositoryModel 仓库实体 - */ - private static void setCredentials(TransportCommand transportCommand, RepositoryModel repositoryModel) { - Integer protocol = repositoryModel.getProtocol(); - if (protocol == GitProtocolEnum.HTTP.getCode()) { - // http - UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(repositoryModel.getUserName(), repositoryModel.getPassword()); - transportCommand.setCredentialsProvider(credentialsProvider); - } else if (protocol == GitProtocolEnum.SSH.getCode()) { - // ssh - File rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModel); - transportCommand.setTransportConfigCallback(transport -> { - SshTransport sshTransport = (SshTransport) transport; - sshTransport.setSshSessionFactory(new JschConfigSessionFactory() { - @Override - protected void configure(OpenSshConfig.Host hc, Session session) { - session.setConfig("StrictHostKeyChecking", "no"); - } - - @Override - protected JSch createDefaultJSch(FS fs) throws JSchException { - JSch jSch = super.createDefaultJSch(fs); - if (rsaFile == null) { - return jSch; - } - // 添加私钥文件 - String rsaPass = repositoryModel.getPassword(); - if (StrUtil.isEmpty(rsaPass)) { - jSch.addIdentity(rsaFile.getPath()); - } else { - jSch.addIdentity(rsaFile.getPath(), rsaPass); - } - return jSch; - } - }); - }); - } else { - throw new IllegalStateException("不支持到协议类型"); - } - } - - private static Git initGit(RepositoryModel repositoryModel, String branchName, File file, PrintWriter printWriter) throws IOException, GitAPIException { - Git git; - if (FileUtil.file(file, Constants.DOT_GIT).exists()) { - if (checkRemoteUrl(repositoryModel.getGitUrl(), file)) { - git = Git.open(file); - // - if (branchName != null) { - PullCommand pull = git.pull(); - if (printWriter != null) { - pull.setProgressMonitor(new TextProgressMonitor(printWriter)); - } - pull.setRemoteBranchName(branchName); - // 更新凭证 - setCredentials(pull, repositoryModel); - pull.call(); - } - } else { - git = reClone(repositoryModel, branchName, file, printWriter); - } - } else { - git = reClone(repositoryModel, branchName, file, printWriter); - } - return git; - } - - /** - * 获取仓库远程的所有分支 - * - * @param repositoryModel 仓库 - * @return Tuple - * @throws GitAPIException api - */ - public static Tuple getBranchAndTagList(RepositoryModel repositoryModel) throws Exception { - String url = repositoryModel.getGitUrl(); - synchronized (url.intern()) { - try { - LsRemoteCommand lsRemoteCommand = Git.lsRemoteRepository() - .setRemote(url); - // 更新凭证 - setCredentials(lsRemoteCommand, repositoryModel); - // - Collection call = lsRemoteCommand - .setHeads(true) - .setTags(true) - .call(); - if (CollUtil.isEmpty(call)) { - return null; - } - Map> refMap = CollStreamUtil.groupByKey(call, ref -> { - String name = ref.getName(); - if (name.startsWith(Constants.R_TAGS)) { - return Constants.R_TAGS; - } else if (name.startsWith(Constants.R_HEADS)) { - return Constants.R_HEADS; - } - return null; - }); - - // branch list - List branchListRef = refMap.get(Constants.R_HEADS); - if (branchListRef == null) { - return null; - } - List branchList = branchListRef.stream().map(ref -> { - String name = ref.getName(); - if (name.startsWith(Constants.R_HEADS)) { - return name.substring((Constants.R_HEADS).length()); - } - return null; - }).filter(Objects::nonNull).sorted((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)).collect(Collectors.toList()); - - // list tag - List tagListRef = refMap.get(Constants.R_TAGS); - List tagList = tagListRef == null ? new ArrayList<>() : tagListRef.stream().map(ref -> { - String name = ref.getName(); - if (name.startsWith(Constants.R_TAGS)) { - return name.substring((Constants.R_TAGS).length()); - } - return null; - }).filter(Objects::nonNull).sorted((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)).collect(Collectors.toList()); - return new Tuple(branchList, tagList); - } catch (Exception t) { - checkTransportException(t, null, null); - return null; - } - } - } - -// /** -// * load repository branch list by git -// * -// * @return list -// * @throws Exception 异常 -// */ -// public static List getBranchList(RepositoryModel repositoryModel) throws Exception { -// Tuple tuple = getBranchAndTagList(repositoryModel); -// -// List branch = tuple == null ? null : tuple.get(0); -// if (CollUtil.isEmpty(branch)) { -// throw new JpomRuntimeException("该仓库还没有任何分支"); -// } -// return tuple.get(0); -// } - - public static Tuple getBranchAndTagListChek(RepositoryModel repositoryModel) throws Exception { - Tuple branchAndTagList = getBranchAndTagList(repositoryModel); - Assert.notNull(branchAndTagList, "获取仓库分支失败"); - return branchAndTagList; - } - - /** - * 模糊匹配 - * - * @param list 待匹配待列表 - * @param pattern 迷糊的表达式 - * @return 匹配到到值 - */ - public static String fuzzyMatch(List list, String pattern) { - Assert.notEmpty(list, "仓库没有任何分支或者标签"); - if (ANT_PATH_MATCHER.isPattern(pattern)) { - List collect = list.stream().filter(s -> ANT_PATH_MATCHER.match(pattern, s)).collect(Collectors.toList()); - return CollUtil.getFirst(collect); - } - return pattern; - } - - /** - * 拉取对应分支最新代码 - * - * @param repositoryModel 仓库 - * @param file 仓库路径 - * @param branchName 分支名 - * @return 返回最新一次提交信息 - * @throws IOException IO - * @throws GitAPIException api - */ - public static String checkoutPull(RepositoryModel repositoryModel, File file, String branchName, PrintWriter printWriter) throws Exception { - synchronized (repositoryModel.getGitUrl().intern()) { - try (Git git = initGit(repositoryModel, branchName, file, printWriter)) { - // 拉取代码 - PullResult pull = pull(git, repositoryModel, branchName, null, printWriter); - // 最后一次提交记录 - return getLastCommitMsg(file, branchName); - } catch (Exception t) { - checkTransportException(t, file, printWriter); - } - } - return StrUtil.EMPTY; - } - - /** - * 拉取远程最新代码 - * - * @param git 仓库对象 - * @param branchName 分支 - * @param repositoryModel 仓库 - * @param tagOpt tag 操作 - * @param printWriter 日志流 - * @return pull result - * @throws Exception 异常 - */ - private static PullResult pull(Git git, RepositoryModel repositoryModel, String branchName, TagOpt tagOpt, PrintWriter printWriter) throws Exception { - // 判断本地是否存在对应分支 - List list = git.branchList().call(); - boolean createBranch = true; - for (Ref ref : list) { - String name = ref.getName(); - if (name.startsWith(Constants.R_HEADS + branchName)) { - createBranch = false; - break; - } - } - // 切换分支 - TextProgressMonitor progressMonitor = new TextProgressMonitor(printWriter); - if (tagOpt == null && !StrUtil.equals(git.getRepository().getBranch(), branchName)) { - println(printWriter, "start switch branch from {} to {}", git.getRepository().getBranch(), branchName); - git.checkout(). - setCreateBranch(createBranch). - setName(branchName). - setForceRefUpdate(true). - setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM). - setProgressMonitor(progressMonitor). - call(); - } - PullCommand pull = git.pull(); - if (tagOpt != null) { - pull.setTagOpt(tagOpt); - } - // - setCredentials(pull, repositoryModel); - // - PullResult call = pull - .setRemoteBranchName(branchName) - .setProgressMonitor(progressMonitor) - .call(); - // 输出拉取结果 - if (call != null) { - String fetchedFrom = call.getFetchedFrom(); - FetchResult fetchResult = call.getFetchResult(); - MergeResult mergeResult = call.getMergeResult(); - RebaseResult rebaseResult = call.getRebaseResult(); - if (mergeResult != null) { - println(printWriter, "mergeResult {}", mergeResult); - } - if (rebaseResult != null) { - println(printWriter, "rebaseResult {}", rebaseResult); - } - if (fetchedFrom != null) { - println(printWriter, "fetchedFrom {}", fetchedFrom); - } - // if (fetchResult != null) { - // println(printWriter, "fetchResult {}", fetchResult); - // } - } - return call; - } - - /** - * 拉取对应分支最新代码 - * - * @param branchName 分支名 - * @param printWriter 日志输出留 - * @param repositoryModel 仓库 - * @param file 仓库路径 - * @param tagName 标签名 - * @throws IOException IO - * @throws GitAPIException api - */ - public static String checkoutPullTag(RepositoryModel repositoryModel, File file, String branchName, String tagName, PrintWriter printWriter) throws Exception { - String url = repositoryModel.getGitUrl(); - synchronized (url.intern()) { - try (Git git = initGit(repositoryModel, null, file, printWriter)) { - // 拉取最新代码 - PullResult pull = pull(git, repositoryModel, branchName, TagOpt.FETCH_TAGS, printWriter); - // 切换到对应的 tag - git.checkout() - .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM) - .setForceRefUpdate(true) - .setName(tagName) - .setForced(true) - .call(); - // 获取最后提交信息 - Collection reflogEntries = git.reflog().setRef(Constants.HEAD).call(); - ReflogEntry first = CollUtil.getFirst(reflogEntries); - if (first != null) { - return getLastCommitMsg(file, tagName, first.getNewId()); - } - - } catch (Exception t) { - checkTransportException(t, file, printWriter); - } - } - return StrUtil.EMPTY; - } - - /** - * 检查异常信息 - * - * @param ex 异常信息 - * @param gitFile 仓库地址 - * @param printWriter 日志流 - * @throws TransportException 非账号密码异常 - */ - private static void checkTransportException(Exception ex, File gitFile, PrintWriter printWriter) throws Exception { - if (ex instanceof TransportException) { - String msg = ex.getMessage(); - if (msg.contains(JGitText.get().notAuthorized) || msg.contains(JGitText.get().authenticationNotSupported)) { - throw new JpomRuntimeException("git账号密码不正常", ex); - } - throw ex; - } else if (ex instanceof NoHeadException) { - println(printWriter, "拉取代码异常,已经主动清理本地仓库缓存内容,请手动重试。" + ex.getMessage()); - if (gitFile == null) { - throw ex; - } else { - FileUtil.del(gitFile); - } - throw ex; - } else if (ex instanceof CheckoutConflictException) { - println(printWriter, "拉取代码发生冲突,可以尝试清除构建或者解决仓库里面的冲突后重新操作。:" + ex.getMessage()); - throw ex; - } else { - println(printWriter, "拉取代码发生未知异常建议清除构建重新操作:" + ex.getMessage()); - throw ex; - } - } - - /** - * 输出日志信息 - * - * @param printWriter 日志流 - * @param template 日志内容模版 - * @param params 参数 - */ - private static void println(PrintWriter printWriter, CharSequence template, Object... params) { - if (printWriter == null) { - return; - } - printWriter.println(StrUtil.format(template, params)); - // 需要 flush 让输出立即生效 - IoUtil.flush(printWriter); - } - - /** - * 解析仓库指定分支最新提交 - * - * @param git 仓库 - * @param branchName 分支 - * @return objectID - * @throws GitAPIException 异常 - */ - private static ObjectId getAnyObjectId(Git git, String branchName) throws GitAPIException { - List list = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call(); - for (Ref ref : list) { - String name = ref.getName(); - if (name.startsWith(Constants.R_HEADS + branchName)) { - return ref.getObjectId(); - } - } - return null; - } - - /** - * 获取对应分支的最后一次提交记录 - * - * @param file 仓库文件夹 - * @param branchName 分支 - * @return 描述 - * @throws IOException IO - * @throws GitAPIException api - */ - public static String getLastCommitMsg(File file, String branchName) throws IOException, GitAPIException { - try (Git git = Git.open(file)) { - ObjectId anyObjectId = getAnyObjectId(git, branchName); - Objects.requireNonNull(anyObjectId, "没有" + branchName + "分支"); - return getLastCommitMsg(file, branchName, anyObjectId); - } - } - - /** - * 解析提交信息 - * - * @param file 仓库文件夹 - * @param desc 描述 - * @param objectId 提交信息 - * @return 描述 - * @throws IOException IO - */ - public static String getLastCommitMsg(File file, String desc, ObjectId objectId) throws IOException { - try (Git git = Git.open(file)) { - RevWalk walk = new RevWalk(git.getRepository()); - RevCommit revCommit = walk.parseCommit(objectId); - String time = new DateTime(revCommit.getCommitTime() * 1000L).toString(); - PersonIdent personIdent = revCommit.getAuthorIdent(); - return StrUtil.format("{} {} {}[{}] {} {}", - desc, - revCommit.getShortMessage(), - personIdent.getName(), - personIdent.getEmailAddress(), - time, - revCommit.getParentCount()); - } - } - -// /** -// * git clone with ssh way -// * -// * @param url 远程仓库地址 -// * @param file 本地存档的文件地址 -// * @param branchName 分支名称 -// * @param rsaFile 私钥文件 -// * @param rsaPass 私钥密码 -// * @param printWriter -// */ -// public static String gitCloneWithSSH(String url, File file, String branchName, -// File rsaFile, String rsaPass, PrintWriter printWriter) throws GitAPIException { -// // if file exists,delete first -// if (FileUtil.exist(file)) { -// FileUtil.del(file); -// } -// -// // git clone -// Git.cloneRepository() -// .setProgressMonitor(new TextProgressMonitor(printWriter)) -// .setURI(url) -// .setDirectory(file) -// .setBranch(branchName) -// .setTransportConfigCallback(transport -> { -// SshTransport sshTransport = (SshTransport) transport; -// sshTransport.setSshSessionFactory(new JschConfigSessionFactory() { -// -// @Override -// protected JSch createDefaultJSch(FS fs) throws JSchException { -// JSch jSch = super.createDefaultJSch(fs); -// // 添加私钥文件 -// jSch.addIdentity(rsaFile.getPath(), rsaPass); -// return jSch; -// } -// }); -// }) -// .call(); -// return StrUtil.EMPTY; -// } -} diff --git a/modules/server/src/main/java/io/jpom/util/JwtUtil.java b/modules/server/src/main/java/io/jpom/util/JwtUtil.java deleted file mode 100644 index 6f91d2a487..0000000000 --- a/modules/server/src/main/java/io/jpom/util/JwtUtil.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateField; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.jwt.JWT; -import cn.hutool.jwt.JWTHeader; -import cn.hutool.jwt.JWTValidator; -import cn.hutool.jwt.signers.JWTSignerUtil; -import cn.jiangzeyin.common.DefaultSystemLog; -import io.jpom.model.data.UserModel; -import io.jpom.system.ServerExtConfigBean; - -/** - * jwt 工具类 - * - * @author bwcx_jzy - * @date 2020/7/25 - */ -public class JwtUtil { - - /** - * 加密算法 - */ - private static final String ALGORITHM = "HS256"; - /** - * token的的加密key - */ - private static byte[] KEY; - public static final String KEY_USER_ID = "userId"; - - private static byte[] getKey() { - if (KEY == null) { - KEY = ServerExtConfigBean.getInstance().getAuthorizeKey(); - } - return KEY; - } - - public static JWT parseBody(String token) { - if (StrUtil.isEmpty(token)) { - return null; - } - JWT jwt = JWT.of(token); - if (jwt.verify(JWTSignerUtil.hs256(getKey()))) { - return jwt; - } - return null; - } - - - /** - * 读取token 信息 过期也能读取 - * - * @param token token - * @return claims - */ - public static JWT readBody(String token) { - try { - return parseBody(token); - } catch (Exception e) { - DefaultSystemLog.getLog().warn("token 解析失败:" + token, e); - return null; - } - } - - /** - * 读取用户id - * - * @param jwt jwt - * @return 用户id - */ - public static String readUserId(JWT jwt) { - return Convert.toStr(jwt.getPayload(KEY_USER_ID)); - } - - /** - * 获取jwt的唯一身份标识 - * - * @param jwt jwt - * @return id - */ - public static String getId(JWT jwt) { - if (null == jwt) { - return null; - } - return Convert.toStr(jwt.getPayload(JWT.JWT_ID)); - } - - /** - * 判断是否过期 - * - * @param jwt claims - * @param leeway 容忍空间,单位:秒。当不能晚于当前时间时,向后容忍;不能早于向前容忍。 - * @return 是否过期 - */ - public static boolean expired(JWT jwt, long leeway) { - if (jwt == null) { - return true; - } - try { - JWTValidator of = JWTValidator.of(jwt); - of.validateDate(DateUtil.date(), leeway); - } catch (Exception e) { - return true; - } - return false; - } - - /** - * 生成token - * - * @param userModel 用户 - * @return token - */ - public static String builder(UserModel userModel, String jwtId) { - int authorizeExpired = ServerExtConfigBean.getInstance().getAuthorizeExpired(); - DateTime now = DateTime.now(); - JWT jwt = JWT.create(); - jwt.setHeader(JWTHeader.ALGORITHM, ALGORITHM); - jwt.setPayload(KEY_USER_ID, userModel.getId()) - .setJWTId(jwtId) - .setIssuer("Jpom") - .setIssuedAt(now) - .setExpiresAt(now.offsetNew(DateField.HOUR, authorizeExpired)); - return jwt.sign(JWTSignerUtil.hs256(getKey())); - } - - -} diff --git a/modules/server/src/main/java/io/jpom/util/SvnKitUtil.java b/modules/server/src/main/java/io/jpom/util/SvnKitUtil.java deleted file mode 100644 index 1cc9c79269..0000000000 --- a/modules/server/src/main/java/io/jpom/util/SvnKitUtil.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.util; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import io.jpom.build.BuildUtil; -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.system.JpomRuntimeException; -import org.tmatesoft.svn.core.*; -import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; -import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; -import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; -import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; -import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; -import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; -import org.tmatesoft.svn.core.wc.*; - -import java.io.File; - -/** - * svn 工具 - *

- * https://www.cnblogs.com/lekko/p/6005382.html - * - * @author bwcx_jzy - * @date 2019/8/6 - */ -public class SvnKitUtil { - - static { - // 初始化库。 必须先执行此操作。具体操作封装在setupLibrary方法中。 - /* - * For using over http:// and https:// - */ - DAVRepositoryFactory.setup(); - /* - * For using over svn:// and svn+xxx:// - */ - SVNRepositoryFactoryImpl.setup(); - - /* - * For using over file:/// - */ - FSRepositoryFactory.setup(); - } - - private static final DefaultSVNOptions OPTIONS = SVNWCUtil.createDefaultOptions(true); - - /** - * 对SVNKit连接进行认证,并获取连接 - * - * @param repositoryModel 仓库 - */ - public static SVNClientManager getAuthClient(RepositoryModel repositoryModel) { - Integer protocol = repositoryModel.getProtocol(); - String userName = repositoryModel.getUserName(); - String password = StrUtil.emptyToDefault(repositoryModel.getPassword(), StrUtil.EMPTY); - // - ISVNAuthenticationManager authManager; - // - if (protocol == GitProtocolEnum.HTTP.getCode()) { - authManager = SVNWCUtil.createDefaultAuthenticationManager(userName, password.toCharArray()); - } else if (protocol == GitProtocolEnum.SSH.getCode()) { - File dir = SVNWCUtil.getDefaultConfigurationDirectory(); - // ssh - File rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModel); - char[] pwdEmpty = StrUtil.EMPTY.toCharArray(); - if (rsaFile == null) { - authManager = SVNWCUtil.createDefaultAuthenticationManager(dir, userName, pwdEmpty, null, pwdEmpty, true); - } else { - if (StrUtil.isEmpty(password)) { - authManager = SVNWCUtil.createDefaultAuthenticationManager(dir, userName, pwdEmpty, rsaFile, pwdEmpty, true); - } else { - authManager = SVNWCUtil.createDefaultAuthenticationManager(dir, userName, pwdEmpty, rsaFile, password.toCharArray(), true); - } - } - } else { - throw new IllegalStateException("不支持到协议类型"); - } - // 实例化客户端管理类 - return SVNClientManager.newInstance(OPTIONS, authManager); - } - - /** - * 判断当前仓库url是否匹配 - * - * @param wcDir 仓库路径 - * @param repositoryModel 仓库 - * @return true 匹配 - * @throws SVNException 异常 - */ - private static Boolean checkUrl(File wcDir, RepositoryModel repositoryModel) throws SVNException { - String url = repositoryModel.getGitUrl(); - // 实例化客户端管理类 - SVNClientManager clientManager = getAuthClient(repositoryModel); - try { - // 通过客户端管理类获得updateClient类的实例。 - SVNWCClient wcClient = clientManager.getWCClient(); - SVNInfo svnInfo = null; - do { - try { - svnInfo = wcClient.doInfo(wcDir, SVNRevision.HEAD); - } catch (SVNException svn) { - if (svn.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { - checkOut(clientManager, url, wcDir); - } else { - throw svn; - } - } - } while (svnInfo == null); - String reUrl = svnInfo.getURL().toString(); - return reUrl.equals(url); - } finally { - clientManager.dispose(); - } - } - - /** - * SVN检出 - * - * @param repositoryModel 仓库 - * @param targetPath 目录 - * @return Boolean - * @throws SVNException svn - */ - public static String checkOut(RepositoryModel repositoryModel, File targetPath) throws SVNException { - // 实例化客户端管理类 - SVNClientManager ourClientManager = getAuthClient(repositoryModel); - try { - if (targetPath.exists()) { - if (!FileUtil.file(targetPath, SVNFileUtil.getAdminDirectoryName()).exists()) { - if (!FileUtil.del(targetPath)) { - FileUtil.del(targetPath.toPath()); - } - } else { - // 判断url是否变更 - if (!checkUrl(targetPath, repositoryModel)) { - if (!FileUtil.del(targetPath)) { - FileUtil.del(targetPath.toPath()); - } - } else { - ourClientManager.getWCClient().doCleanup(targetPath); - } - } - } - return checkOut(ourClientManager, repositoryModel.getGitUrl(), targetPath); - } finally { - ourClientManager.dispose(); - } - } - - private static String checkOut(SVNClientManager ourClientManager, String url, File targetPath) throws SVNException { - // 通过客户端管理类获得updateClient类的实例。 - SVNUpdateClient updateClient = ourClientManager.getUpdateClient(); - updateClient.setIgnoreExternals(false); - // 相关变量赋值 - SVNURL svnurl = SVNURL.parseURIEncoded(url); - try { - // 要把版本库的内容check out到的目录 - long workingVersion = updateClient.doCheckout(svnurl, targetPath, SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.INFINITY, true); - return String.format("把版本:%s check out ", workingVersion); - } catch (SVNAuthenticationException s) { - throw new JpomRuntimeException("账号密码不正确", s); - } - } -} diff --git a/modules/server/src/main/java/org/dromara/jpom/ApiDoc.java b/modules/server/src/main/java/org/dromara/jpom/ApiDoc.java new file mode 100644 index 0000000000..c97ddb7b66 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/ApiDoc.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +/** + * apiDoc 通用文档块 + * + * @author bwcx_jzy + * @since 2022/2/28 + */ +public interface ApiDoc { + + /** + * 登录用户返回消息体 + * + * @author bwcx_jzy + * + * @apiDefine loginUser + * @apiUse defResultJson + * @apiHeader {String} Authorization 用户token + * @apiPermission login-user + * @apiSuccess (800) {none} data 需要登录 + * @apiSuccess (801) {none} data 登录信息过期,但是可以续期 + * @apiSuccess (302) {none} data 当前用户没有操作权限 + * @apiSuccess (999) {none} data 当前 IP 不能访问 + */ + void loginUser(); + + /** + * 默认的通用返回消息体 + * + * @author bwcx_jzy + * + * @apiDefine defResultJson + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "code": "200", + * "msg": "成功", + * "data": {}, + * } + */ + void defResultJson(); +} diff --git a/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java b/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java new file mode 100644 index 0000000000..9e2090386d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.JpomAppType; +import cn.keepbx.jpom.Type; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.model.data.SystemIpConfigModel; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.dromara.jpom.service.user.UserService; +import org.springframework.boot.Banner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * jpom 启动类 + * + * @author bwcx_jzy + * @since 2017/9/14 + */ +@SpringBootApplication(scanBasePackages = {"org.dromara.jpom"}) +@ServletComponentScan(basePackages = {"org.dromara.jpom"}) +@Slf4j +@JpomAppType(Type.Server) +public class JpomServerApplication { + + /** + * 启动执行 + *

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

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

+ * --rest:super_user_pwd 重置超级管理员密码 + *

+ * --recover:h2db 当 h2 数据出现奔溃无法启动需要执行恢复逻辑 + *

+ * --close:super_user_mfa 关闭超级管理员 mfa + *

+ * --backup-h2 备份数据库 + *

+ * --import-h2-sql=/xxxx.sql 导入指定文件 sql + *

+ * --replace-import-h2-sql=/xxxx.sql 替换导入指定文件 sql(会删除掉已经存的数据) + *

+ * --transform-sql 转换 sql 内容(低版本兼容高版本),仅在导入 sql 文件时候生效:--import-h2-sql=/xxxx.sql、--replace-import-h2-sql=/xxxx.sql + *

+ * --h2-migrate-mysql --h2-user=jpom --h2-pass=jpom 将 h2 数据库迁移到 mysql + *

+ * --h2-migrate-postgresql --h2-user=jpom --h2-pass=jpom 将 h2 数据库迁移到 postgresql + *

+ * --h2-migrate-mariadb --h2-user=jpom --h2-pass=jpom 将 h2 数据库迁移到 mariadb + * + * @param args 参数 + * @throws Exception 异常 + */ + public static void main(String[] args) throws Exception { + long time = SystemClock.now(); + // + SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(JpomServerApplication.class); + springApplicationBuilder.bannerMode(Banner.Mode.LOG); + springApplicationBuilder.run(args); + // 重置 ip 授权配置 + if (ArrayUtil.containsIgnoreCase(args, "--rest:ip_config")) { + SystemParametersServer parametersServer = SpringUtil.getBean(SystemParametersServer.class); + parametersServer.delByKey(SystemIpConfigModel.ID); + log.info("Clear IP whitelist configuration successfully"); + } + // 重置超级管理员密码 + if (ArrayUtil.containsIgnoreCase(args, "--rest:super_user_pwd")) { + UserService userService = SpringUtil.getBean(UserService.class); + String restResult = userService.restSuperUserPwd(); + if (restResult != null) { + log.info(restResult); + } else { + log.error("There is no super administrator account in the system"); + } + } + // 关闭超级管理员 mfa + if (ArrayUtil.containsIgnoreCase(args, "--close:super_user_mfa")) { + UserService userService = SpringUtil.getBean(UserService.class); + String restResult = userService.closeSuperUserMfa(); + if (restResult != null) { + log.info(restResult); + } else { + log.error("There is no super administrator account in the system"); + } + } + + + log.info("Time-consuming to start this time:{}", DateUtil.formatBetween(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND)); + } +} 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 new file mode 100644 index 0000000000..78756c37c6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java @@ -0,0 +1,1049 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.FileCopier; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.net.url.UrlQuery; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.*; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.BaseIdModel; +import cn.keepbx.jpom.plugins.IPlugin; +import lombok.Builder; +import lombok.Lombok; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +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; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.model.data.RepositoryModel; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.log.BuildHistoryLog; +import org.dromara.jpom.model.script.ScriptExecuteLogModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.*; +import org.dromara.jpom.webhook.DefaultWebhookPluginImpl; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/3/30 + */ +@Builder +@Slf4j +public class BuildExecuteManage implements Runnable { + /** + * 缓存构建中 + */ + public static final Map BUILD_MANAGE_MAP = new SafeConcurrentHashMap<>(); + + private final TaskData taskData; + private final BuildExtraModule buildExtraModule; + private final String logId; + // + private Process process; + private LogRecorder logRecorder; + private File gitFile; + private Thread currentThread; + private ReleaseManage releaseManage; + private String language; + + /** + * 提交任务时间 + */ + private Long submitTaskTime; + + private static BuildExecuteService buildExecuteService; + private static ScriptServer scriptServer; + private static ScriptExecuteLogServer scriptExecuteLogServer; + private static BuildInfoService buildService; + private static DbBuildHistoryLogService dbBuildHistoryLogService; + private static DockerInfoService dockerInfoService; + private static MachineDockerServer machineDockerServer; + private static BuildExtConfig buildExtConfig; + private static FileStorageService fileStorageService; + private static BuildExecutorPoolService buildExecutorPoolService; + + private void loadService() { + buildExecuteService = ObjectUtil.defaultIfNull(buildExecuteService, () -> SpringUtil.getBean(BuildExecuteService.class)); + scriptServer = ObjectUtil.defaultIfNull(scriptServer, () -> SpringUtil.getBean(ScriptServer.class)); + scriptExecuteLogServer = ObjectUtil.defaultIfNull(scriptExecuteLogServer, () -> SpringUtil.getBean(ScriptExecuteLogServer.class)); + buildService = ObjectUtil.defaultIfNull(buildService, () -> SpringUtil.getBean(BuildInfoService.class)); + dbBuildHistoryLogService = ObjectUtil.defaultIfNull(dbBuildHistoryLogService, () -> SpringUtil.getBean(DbBuildHistoryLogService.class)); + dockerInfoService = ObjectUtil.defaultIfNull(dockerInfoService, () -> SpringUtil.getBean(DockerInfoService.class)); + machineDockerServer = ObjectUtil.defaultIfNull(machineDockerServer, () -> SpringUtil.getBean(MachineDockerServer.class)); + buildExtConfig = ObjectUtil.defaultIfNull(buildExtConfig, () -> SpringUtil.getBean(BuildExtConfig.class)); + fileStorageService = ObjectUtil.defaultIfNull(fileStorageService, () -> SpringUtil.getBean(FileStorageService.class)); + buildExecutorPoolService = ObjectUtil.defaultIfNull(buildExecutorPoolService, () -> SpringUtil.getBean(BuildExecutorPoolService.class)); + } + + /** + * 正在构建的数量 + * + * @return 构建数量 + */ + public static Set buildKeys() { + return BUILD_MANAGE_MAP.keySet(); + } + + + /** + * 提交任务 + */ + public void submitTask() { + this.loadService(); + submitTaskTime = SystemClock.now(); + language = I18nMessageUtil.getLanguageByRequest(); + // 创建线程池 + ThreadPoolExecutor threadPoolExecutor = buildExecutorPoolService.getThreadPoolExecutor(); + // + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + File logFile = BuildUtil.getLogFile(buildInfoModel.getId(), buildInfoModel.getBuildId()); + this.logRecorder = LogRecorder.builder().file(logFile).build(); + // + int queueSize = threadPoolExecutor.getQueue().size(); + int size = BUILD_MANAGE_MAP.size(); + logRecorder.system(I18nMessageUtil.get("i18n.build_task_count_and_queue_count.f0b6"), size, queueSize, + size > buildExtConfig.getPoolSize() ? I18nMessageUtil.get("i18n.build_task_queue_waiting.5f06") : StrUtil.EMPTY); + //BuildInfoManage manage = new BuildInfoManage(taskData); + BUILD_MANAGE_MAP.put(buildInfoModel.getId(), this); + threadPoolExecutor.execute(this); + } + + /** + * 取消任务(拒绝执行) + */ + public void rejectedExecution() { + ThreadPoolExecutor threadPoolExecutor = buildExecutorPoolService.getThreadPoolExecutor(); + int queueSize = threadPoolExecutor.getQueue().size(); + int limitPoolSize = threadPoolExecutor.getPoolSize(); + int corePoolSize = threadPoolExecutor.getCorePoolSize(); + String format = StrUtil.format(I18nMessageUtil.get("i18n.build_status_message.42a7"), BUILD_MANAGE_MAP.size(), queueSize, limitPoolSize, corePoolSize); + logRecorder.system(format); + this.cancelTask(format); + } + + /** + * 取消任务 + */ + private void cancelTask(String desc) { + CommandUtil.kill(process); + ApacheExecUtil.kill(this.logId); + Integer buildMode = taskData.buildInfoModel.getBuildMode(); + if (buildMode != null && buildMode == 1) { + // 容器构建 删除容器 + try { + Optional.ofNullable(taskData.dockerParameter).ifPresent(parameter -> { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + parameter.put("containerId", taskData.buildContainerId); + try { + plugin.execute("removeContainer", parameter); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.build_resource_cleanup_failed.c4cf"), e); + } + } + String buildId = taskData.buildInfoModel.getId(); + buildExecuteService.updateStatus(buildId, logId, taskData.buildInfoModel.getBuildId(), BuildStatus.Cancel, desc); + Optional.ofNullable(currentThread).ifPresent(Thread::interrupt); + BUILD_MANAGE_MAP.remove(buildId); + IoUtil.close(logRecorder); + } + + /** + * 打包构建产物 + */ + private String packageFile() { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + Integer buildMode = taskData.buildInfoModel.getBuildMode(); + String resultDirFile = buildInfoModel.getResultDirFile(); + String excludeReleaseAnt = this.buildExtraModule.getExcludeReleaseAnt(); + boolean releaseHideFile = ObjectUtil.defaultIfNull(this.buildExtraModule.getReleaseHideFile(), false); + List excludeReleaseAnts = StrUtil.splitTrim(excludeReleaseAnt, StrUtil.COMMA); + ResultDirFileAction resultDirFileAction = ResultDirFileAction.parse(resultDirFile); + final int[] excludeReleaseAntCount = {0}; + Predicate predicate = file -> { + if (CollUtil.isEmpty(excludeReleaseAnts)) { + return true; + } + for (String releaseAnt : excludeReleaseAnts) { + if (AntPathUtil.ANT_PATH_MATCHER.match(releaseAnt, file)) { + // 过滤 + excludeReleaseAntCount[0]++; + return false; + } + } + return true; + }; + if (buildMode != null && buildMode == 1) { + // 容器构建直接下载到 结果目录 + File toFile = BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId(), resultDirFileAction.getPath()); + if (!FileUtil.exist(toFile)) { + String format = StrUtil.format(I18nMessageUtil.get("i18n.non_existent_build_product.1df4"), resultDirFileAction.getPath()); + logRecorder.systemError(format); + return format; + } + logRecorder.system(I18nMessageUtil.get("i18n.backup_product.53c0"), resultDirFileAction.getPath(), buildInfoModel.getBuildId()); + return null; + } + if (resultDirFileAction.getType() == ResultDirFileAction.Type.ANT_PATH) { + // 通配模式 + List paths = AntPathUtil.antPathMatcher(this.gitFile, resultDirFileAction.getPath()); + int matcherSize = CollUtil.size(paths); + if (matcherSize <= 0) { + String format = StrUtil.format(I18nMessageUtil.get("i18n.no_matching_files.b7a6"), resultDirFileAction.getPath()); + logRecorder.systemError(format); + return format; + } + logRecorder.system(I18nMessageUtil.get("i18n.fuzzy_match_files.139d"), resultDirFileAction.getPath(), matcherSize); + String antSubMatch = resultDirFileAction.antSubMatch(); + ResultDirFileAction.AntFileUploadMode antFileUploadMode = resultDirFileAction.getAntFileUploadMode(); + Assert.notNull(antFileUploadMode, I18nMessageUtil.get("i18n.file_upload_mode_not_configured.b3b2")); + File historyPackageFile = BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId(), StrUtil.SLASH); + int subMatchCount = paths.stream() + .filter(s -> { + // 需要能满足二级匹配 + return StrUtil.isEmpty(antSubMatch) || AntPathUtil.ANT_PATH_MATCHER.matchStart(antSubMatch + "**", s); + }) + .filter(predicate) + .mapToInt(path -> { + File toFile; + if (antFileUploadMode == ResultDirFileAction.AntFileUploadMode.KEEP_DIR) { + // 剔除文件夹层级 + List list = StrUtil.splitTrim(path, StrUtil.SLASH); + int notMathIndex; + int pathItemSize = list.size(); + if (StrUtil.isEmpty(antSubMatch) || StrUtil.equals(antSubMatch, StrUtil.SLASH)) { + notMathIndex = 0; + } else { + notMathIndex = ArrayUtil.INDEX_NOT_FOUND; + for (int i = pathItemSize - 1; i >= 0; i--) { + String suffix = i == pathItemSize - 1 ? StrUtil.EMPTY : StrUtil.SLASH; + String itemS = StrUtil.SLASH + CollUtil.join(CollUtil.sub(list, 0, i + 1), StrUtil.SLASH) + suffix; + if (AntPathUtil.ANT_PATH_MATCHER.match(antSubMatch, itemS)) { + notMathIndex = i + 1; + // 结束本次循环 + break; + } + } + if (notMathIndex == ArrayUtil.INDEX_NOT_FOUND) { + return 0; + } + } + // 保留文件夹层级 + String itemEnd = CollUtil.join(CollUtil.sub(list, notMathIndex, pathItemSize), StrUtil.SLASH); + toFile = FileUtil.file(historyPackageFile, itemEnd); + } else if (antFileUploadMode == ResultDirFileAction.AntFileUploadMode.SAME_DIR) { + toFile = historyPackageFile; + } else { + throw new IllegalStateException(I18nMessageUtil.get("i18n.unsupported_mode.a3d3") + antFileUploadMode); + } + // 创建文件夹,避免出现文件全部为相关文件名(result) + BuildUtil.mkdirHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId()); + File srcFile = FileUtil.file(this.gitFile, path); + // + FileCopier.create(srcFile, toFile) + .setCopyContentIfDir(true) + .setOverride(true) + .setCopyAttributes(true) + .setCopyFilter(file -> releaseHideFile || !file.isHidden()) + .copy(); + return 1; + }).sum(); + if (subMatchCount <= 0) { + String format = StrUtil.format(I18nMessageUtil.get("i18n.no_matching_files.b7a6"), antSubMatch); + logRecorder.systemError(format); + return format; + } + logRecorder.system(I18nMessageUtil.get("i18n.secondary_directory_match.0aec"), antSubMatch, subMatchCount, antFileUploadMode); + // 更新产物路径为普通路径 + dbBuildHistoryLogService.updateResultDirFile(this.logId, StrUtil.SLASH); + buildInfoModel.setResultDirFile(StrUtil.SLASH); + this.buildExtraModule.setResultDirFile(StrUtil.SLASH); + } else if (resultDirFileAction.getType() == ResultDirFileAction.Type.ORIGINAL) { + File file = FileUtil.file(this.gitFile, resultDirFile); + if (!file.exists()) { + String format = StrUtil.format(I18nMessageUtil.get("i18n.non_existent_build_product.1df4"), resultDirFile); + logRecorder.systemError(format); + return format; + } + BuildUtil.mkdirHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId()); + File toFile = BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId(), resultDirFile); + // + String rootDir = FileUtil.getAbsolutePath(gitFile); + FileCopier.create(file, toFile) + .setCopyContentIfDir(true) + .setOverride(true) + .setCopyAttributes(true) + .setCopyFilter(file12 -> { + if (!releaseHideFile && file12.isHidden()) { + return false; + } + String subPath = FileUtil.subPath(rootDir, file12); + subPath = FileUtil.normalize(StrUtil.SLASH + subPath); + return predicate.test(subPath); + }) + .copy(); + } + if (CollUtil.isNotEmpty(excludeReleaseAnts)) { + logRecorder.system(I18nMessageUtil.get("i18n.cumulative_filter_files.448d"), excludeReleaseAnt, excludeReleaseAntCount[0]); + } + return null; + } + + /** + * 准备构建 + * + * @return false 执行异常需要结束 + */ + private String startReady() { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + this.gitFile = BuildUtil.getSourceById(buildInfoModel.getId()); + + Integer delay = taskData.delay; + logRecorder.system(I18nMessageUtil.get("i18n.start_building_with_number_and_path.c41c"), buildInfoModel.getBuildId(), FileUtil.getAbsolutePath(this.gitFile)); + if (delay != null && delay > 0) { + // 延迟执行 + logRecorder.system(I18nMessageUtil.get("i18n.wait_for_seconds.ff7b"), delay); + ThreadUtil.sleep(delay, TimeUnit.SECONDS); + } + // 删除缓存 + Boolean cacheBuild = this.buildExtraModule.getCacheBuild(); + if (cacheBuild != null && !cacheBuild) { + logRecorder.system(I18nMessageUtil.get("i18n.delete_build_cache.c7f3")); + CommandUtil.systemFastDel(this.gitFile); + } + // + taskData.environmentMapBuilder.put("BUILD_ID", this.buildExtraModule.getId()); + taskData.environmentMapBuilder.put("BUILD_NAME", this.buildExtraModule.getName()); + taskData.environmentMapBuilder.put("BUILD_SOURCE_FILE", FileUtil.getAbsolutePath(this.gitFile)); + taskData.environmentMapBuilder.put("BUILD_NUMBER_ID", String.valueOf(this.taskData.buildInfoModel.getBuildId())); + taskData.environmentMapBuilder.put("BUILD_ORIGINAL_RESULT_DIR_FILE", buildInfoModel.getResultDirFile()); + // 配置的分支名称,可能存在模糊匹配的情况 + taskData.environmentMapBuilder.put("BUILD_CONFIG_BRANCH_NAME", this.taskData.buildInfoModel.getBranchName()); + return null; + } + + /** + * 拉取代码后并缓存环境变量 + * + * @return pull 的结果 + */ + private String pullAndCacheBuildEnv() { + String pull = this.pull(); + if (pull == null) { + BuildHistoryLog buildInfoModel = new BuildHistoryLog(); + buildInfoModel.setId(logId); + buildInfoModel.setBuildEnvCache(taskData.environmentMapBuilder.toDataJsonStr()); + dbBuildHistoryLogService.updateById(buildInfoModel); + } + return pull; + } + + /** + * 拉取代码 + * + * @return false 执行异常需要结束 + */ + private String pull() { + RepositoryModel repositoryModel = taskData.repositoryModel; + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + try { + String msg; + Integer repoTypeCode = repositoryModel.getRepoType(); + RepositoryModel.RepoType repoType = EnumUtil.likeValueOf(RepositoryModel.RepoType.class, repoTypeCode); + Boolean checkRepositoryDiff = Optional.ofNullable(taskData.checkRepositoryDiff).orElse(buildExtraModule.getCheckRepositoryDiff()); + String repositoryLastCommitId = buildInfoModel.getRepositoryLastCommitId(); + if (repoType == RepositoryModel.RepoType.Git) { + // git with password + IPlugin plugin = PluginFactory.getPlugin("git-clone"); + Map map = repositoryModel.toMap(); + // 指定 clone 深度 + Integer cloneDepth = buildExtraModule.getCloneDepth(); + map.put("depth", cloneDepth); + if (cloneDepth != null) { + // 使用系统 + map.put("gitProcessType", "SystemGit"); + } + Tuple tuple = (Tuple) plugin.execute("branchAndTagList", map); + //GitUtil.getBranchAndTagList(repositoryModel); + Assert.notNull(tuple, I18nMessageUtil.get("i18n.get_repository_branch_failure.37cc")); + map.put("reduceProgressRatio", buildExtConfig.getLogReduceProgressRatio()); + map.put("logWriter", logRecorder.getPrintWriter()); + map.put("savePath", gitFile); + map.put("strictlyEnforce", buildExtraModule.strictlyEnforce()); + // 模糊匹配 标签 + String branchTagName = buildInfoModel.getBranchTagName(); + String[] result; + if (StrUtil.isNotEmpty(branchTagName)) { + String newBranchTagName = fuzzyMatch(tuple.get(1), branchTagName); + if (StrUtil.isEmpty(newBranchTagName)) { + String format = StrUtil.format("{} Did not match the corresponding tag", branchTagName); + logRecorder.systemError(format); + return format; + } + // author bwcx_jzy 2022.11.28 map.put("branchName", newBranchName); + map.put("tagName", newBranchTagName); + //author bwcx_jzy 2022.11.28 buildEnv.put("BUILD_BRANCH_NAME", newBranchName); + taskData.environmentMapBuilder.put("BUILD_TAG_NAME", newBranchTagName); + // 标签拉取模式 + logRecorder.system("repository tag [{}] clone pull from {}", branchTagName, newBranchTagName); + result = (String[]) plugin.execute("pullByTag", map); + } else { + String branchName = buildInfoModel.getBranchName(); + // 模糊匹配分支 + String newBranchName = fuzzyMatch(tuple.get(0), branchName); + if (StrUtil.isEmpty(newBranchName)) { + String format = StrUtil.format("{} Did not match the corresponding branch", branchName); + logRecorder.systemError(format); + //buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, this.taskData.buildInfoModel.getBuildId(), BuildStatus.Error); + return format; + } + // 分支模式 + map.put("branchName", newBranchName); + // 真实使用的分支名 + taskData.environmentMapBuilder.put("BUILD_BRANCH_NAME", newBranchName); + logRecorder.system("repository [{}] clone pull from {}", branchName, newBranchName); + result = (String[]) plugin.execute("pull", map); + } + msg = result[1]; + // 判断是否执行失败 + String errorMsg = ArrayUtil.get(result, 2); + if (errorMsg != null) { + logRecorder.systemError(I18nMessageUtil.get("i18n.pull_code_failed.70d6"), errorMsg); + return errorMsg; + } + // 判断hash 码和上次构建是否一致 + if (checkRepositoryDiff != null && checkRepositoryDiff) { + if (StrUtil.equals(repositoryLastCommitId, result[0])) { + // 如果一致,则不构建 + String format = StrUtil.format(I18nMessageUtil.get("i18n.no_changes_in_repository_code_with_details.fd9f"), result[0], msg); + logRecorder.systemError(format); + throw new DiyInterruptException(format); + } + } + taskData.repositoryLastCommitId = result[0]; + } else if (repoType == RepositoryModel.RepoType.Svn) { + // svn + Map map = repositoryModel.toMap(); + + IPlugin plugin = PluginFactory.getPlugin("svn-clone"); + String[] result = (String[]) plugin.execute(gitFile, map); + //msg = SvnKitUtil.checkOut(repositoryModel, gitFile); + msg = ArrayUtil.get(result, 1); + // 判断版本号和上次构建是否一致 + if (checkRepositoryDiff != null && checkRepositoryDiff) { + if (StrUtil.equals(repositoryLastCommitId, result[0])) { + // 如果一致,则不构建 + String format = StrUtil.format(I18nMessageUtil.get("i18n.no_changes_in_repository_code.b1aa"), result[0]); + logRecorder.systemError(format); + throw new DiyInterruptException(format); + } + } + taskData.repositoryLastCommitId = result[0]; + } else { + String format = StrUtil.format(I18nMessageUtil.get("i18n.unsupported_type_with_placeholder.71a2"), repoType.getDesc()); + logRecorder.systemError(format); + return format; + } + taskData.environmentMapBuilder.put("BUILD_COMMIT_ID", taskData.repositoryLastCommitId); + logRecorder.system(msg); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + // env file + String attachEnv = this.buildExtraModule.getAttachEnv(); + Opt.ofBlankAble(attachEnv).ifPresent(s -> { + UrlQuery of = UrlQuery.of(attachEnv, CharsetUtil.CHARSET_UTF_8); + Map queryMap = of.getQueryMap(); + logRecorder.system(I18nMessageUtil.get("i18n.read_additional_variables.5eb0"), attachEnv, CollUtil.size(queryMap)); + // + Optional.ofNullable(queryMap).ifPresent(map -> { + for (Map.Entry entry : map.entrySet()) { + CharSequence value = entry.getValue(); + if (value != null) { + taskData.environmentMapBuilder.put(String.valueOf(entry.getKey()), String.valueOf(value)); + } + } + }); + Map envFileMap = FileUtils.readEnvFile(this.gitFile, s); + taskData.environmentMapBuilder.putStr(envFileMap); + }); + // 输出环境变量 + taskData.environmentMapBuilder.eachStr(logRecorder::system); + return null; + } + + private String dockerCommand() { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + String script = buildInfoModel.getScript(); + DockerYmlDsl dockerYmlDsl = DockerYmlDsl.build(script); + String fromTag = dockerYmlDsl.getFromTag(); + // 根据 tag 查询 + List dockerInfoModels = dockerInfoService + .queryByTag(buildInfoModel.getWorkspaceId(), fromTag); + Map map = machineDockerServer.dockerParameter(dockerInfoModels); + Assert.notNull(map, fromTag + I18nMessageUtil.get("i18n.no_available_docker_server.9fc6")); + taskData.dockerParameter = new HashMap<>(map); + logRecorder.system("use docker {}", map.get("name")); + logRecorder.info(""); + String workingDir = "/home/jpom/"; + + map.put("runsOn", dockerYmlDsl.getRunsOn()); + map.put("workingDir", workingDir); + map.put("hostConfig", dockerYmlDsl.getHostConfig()); + map.put("tempDir", JpomApplication.getInstance().getTempPath()); + String buildInfoModelId = buildInfoModel.getId(); + taskData.buildContainerId = "jpom-build-" + buildInfoModelId; + map.put("dockerName", taskData.buildContainerId); + map.put("logRecorder", logRecorder); + // + List copy = ObjectUtil.defaultIfNull(dockerYmlDsl.getCopy(), new ArrayList<>()); + // 将仓库文件上传到容器 + copy.add(FileUtil.getAbsolutePath(this.gitFile) + StrUtil.COLON + workingDir + StrUtil.COLON + "true"); + map.put("copy", copy); + map.put("binds", ObjectUtil.defaultIfNull(dockerYmlDsl.getBinds(), new ArrayList<>())); + + Map dockerEnv = ObjectUtil.defaultIfNull(dockerYmlDsl.getEnv(), new HashMap<>(10)); + Map env = taskData.environmentMapBuilder.environment(); + env.putAll(dockerEnv); + env.put("JPOM_BUILD_ID", buildInfoModelId); + env.put("JPOM_WORKING_DIR", workingDir); + map.put("env", env); + map.put("steps", dockerYmlDsl.getSteps()); + // 构建产物 + String resultDirFile = buildInfoModel.getResultDirFile(); + String resultFile = FileUtil.normalize(workingDir + StrUtil.SLASH + resultDirFile); + map.put("resultFile", resultFile); + // 产物输出目录 + File toFile = BuildUtil.getHistoryPackageFile(buildInfoModelId, buildInfoModel.getBuildId(), resultDirFile); + map.put("resultFileOut", FileUtil.getAbsolutePath(toFile)); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + try { + Object execute = plugin.execute("build", map); + int resultCode = Convert.toInt(execute, -100); + // 严格模式 + if (buildExtraModule.strictlyEnforce()) { + return resultCode == 0 ? null : StrUtil.format(I18nMessageUtil.get("i18n.command_non_zero_exit_code.a6e1"), resultCode); + } + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.build_call_container_exception.6e04"), e); + return e.getMessage(); + } + return null; + } + + /** + * 执行构建命令 + * + * @return false 执行异常需要结束 + */ + private String executeCommand() { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + Integer buildMode = buildInfoModel.getBuildMode(); + if (buildMode != null && buildMode == 1) { + // 容器构建 + return this.dockerCommand(); + } + String script = buildInfoModel.getScript(); + if (StrUtil.isEmpty(script)) { + String info = I18nMessageUtil.get("i18n.no_command_to_execute.340b"); + logRecorder.systemError(info); + return info; + } + if (StrUtil.startWith(script, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(script, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKey(scriptId); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + script = keyAndGlobal.getContext(); + logRecorder.system(I18nMessageUtil.get("i18n.introducing_script_content.a55b"), keyAndGlobal.getName(), scriptId); + } + Map environment = taskData.environmentMapBuilder.environment(); + + InputStream templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX); + String s1 = IoUtil.readUtf8(templateInputStream); + try { + int waitFor = JpomApplication.getInstance() + .execScript(s1 + script, file -> { + try { + String execMode = this.buildExtraModule.getCommandExecMode(); + // ApacheExecUtil.exec + if (StrUtil.equals(execMode, "apache_exec")) { + return ApacheExecUtil.exec(this.logId, file, this.gitFile, environment, StrUtil.EMPTY, logRecorder); + } else { + 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(I18nMessageUtil.get("i18n.script_exit_code.716e"), waitFor); + // 判断是否为严格执行 + if (buildExtraModule.strictlyEnforce()) { + return waitFor == 0 ? null : StrUtil.format(I18nMessageUtil.get("i18n.command_non_zero_exit_code.a6e1"), waitFor); + } + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e); + return e.getMessage(); + } + return null; + } + + /** + * 打包发布 + * + * @return false 执行需要结束 + */ + private String release() { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + UserModel userModel = taskData.userModel; + // 发布文件 + this.releaseManage = ReleaseManage.builder() + .buildNumberId(buildInfoModel.getBuildId()) + .buildExtraModule(buildExtraModule) + .userModel(userModel) + .logId(logId) + .buildEnv(taskData.environmentMapBuilder) + .logRecorder(logRecorder) + .build(); + try { + return releaseManage.start(resultFileSize -> taskData.resultFileSize = resultFileSize, buildInfoModel); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + /** + * 结束流程 + * + * @return 流程执行是否成功 + */ + private String finish() { + BuildInfoModel buildInfoModel1 = taskData.buildInfoModel; + if (StrUtil.isNotEmpty(taskData.repositoryLastCommitId)) { + BuildInfoModel buildInfoModel = new BuildInfoModel(); + buildInfoModel.setId(buildInfoModel1.getId()); + buildInfoModel.setRepositoryLastCommitId(taskData.repositoryLastCommitId); + buildService.updateById(buildInfoModel); + } + // + BuildStatus buildStatus = buildInfoModel1.getReleaseMethod() != BuildReleaseMethod.No.getCode() ? BuildStatus.PubSuccess : BuildStatus.Success; + buildExecuteService.updateStatus(buildInfoModel1.getId(), this.logId, this.taskData.buildInfoModel.getBuildId(), buildStatus, I18nMessageUtil.get("i18n.task_ended_successfully.e176")); + // 判断是否保留产物 + Boolean saveBuildFile = this.buildExtraModule.getSaveBuildFile(); + if (saveBuildFile != null && !saveBuildFile) { + // 删除 产物文件夹 + File historyPackageFile = BuildUtil.getHistoryPackageFile(buildExtraModule.getId(), buildInfoModel1.getBuildId(), StrUtil.SLASH); + CommandUtil.systemFastDel(historyPackageFile); + // 被删除后 + this.taskData.resultFileSize = 0L; + } + return null; + } + + private Map createProcess() { + // 初始化构建流程 准备构建->拉取仓库代码->执行构建命令->打包产物->发布产物->构建结束 + Map suppliers = new LinkedHashMap<>(10); + suppliers.put("startReady", new IProcessItem() { + @Override + public String name() { + return I18nMessageUtil.get("i18n.prepare_to_build.1830"); + } + + @Override + public String execute() { + return BuildExecuteManage.this.startReady(); + } + }); + suppliers.put("pull", new IProcessItem() { + @Override + public String name() { + return I18nMessageUtil.get("i18n.pull_repository_code.3f51"); + } + + @Override + public String execute() { + return BuildExecuteManage.this.pullAndCacheBuildEnv(); + } + }); + suppliers.put("executeCommand", new IProcessItem() { + @Override + public String name() { + return I18nMessageUtil.get("i18n.build_command_execution.a55c"); + } + + @Override + public String execute() { + return BuildExecuteManage.this.executeCommand(); + } + }); + suppliers.put("packageFile", new IProcessItem() { + @Override + public String name() { + return I18nMessageUtil.get("i18n.package_product.bfbb"); + } + + @Override + public String execute() { + return BuildExecuteManage.this.packageFile(); + } + }); + suppliers.put("release", new IProcessItem() { + @Override + public String name() { + return I18nMessageUtil.get("i18n.publish_product.5925"); + } + + @Override + public String execute() { + return BuildExecuteManage.this.release(); + } + }); + suppliers.put("finish", new IProcessItem() { + @Override + public String name() { + return I18nMessageUtil.get("i18n.build_finished.7f38"); + } + + @Override + public String execute() { + return BuildExecuteManage.this.finish(); + } + }); + return suppliers; + } + + /** + * 清理构建资源 + */ + private void clearResources() { + // + BuildInfoModel buildInfoModel1 = taskData.buildInfoModel; + File historyPackageZipFile = BuildUtil.getHistoryPackageZipFile(buildExtraModule.getId(), buildInfoModel1.getBuildId()); + CommandUtil.systemFastDel(historyPackageZipFile); + // 计算文件占用大小 + long size = logRecorder.size(); + BuildHistoryLog buildInfoModel = new BuildHistoryLog(); + buildInfoModel.setId(logId); + buildInfoModel.setResultFileSize(taskData.resultFileSize); + buildInfoModel.setBuildLogFileSize(size); + dbBuildHistoryLogService.updateById(buildInfoModel); + } + + public void runTask() { + currentThread = Thread.currentThread(); + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_build_task.a5ac"), DateUtil.formatBetween(SystemClock.now() - submitTaskTime)); + + // 判断任务是否被取消 + BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(this.logId); + if (buildHistoryLog == null) { + logRecorder.systemError(I18nMessageUtil.get("i18n.build_record_lost.f6a2")); + return; + } + if (buildHistoryLog.getStatus() == null || buildHistoryLog.getStatus() == BuildStatus.Cancel.getCode()) { + logRecorder.systemError(I18nMessageUtil.get("i18n.build_status_abnormal.8ca1")); + return; + } + BuildInfoModel buildInfoModel = this.taskData.buildInfoModel; + buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, buildInfoModel.getBuildId(), BuildStatus.Ing, I18nMessageUtil.get("i18n.start_building_with_thread_execution.83cd")); + // + Map processItemMap = this.createProcess(); + // 依次执行流程,发生异常结束整个流程 + String processName = StrUtil.EMPTY; + long startTime = SystemClock.now(); + if (taskData.triggerBuildType == 2) { + // 系统触发构建 + BaseServerController.resetInfo(UserModel.EMPTY); + } else { + BaseServerController.resetInfo(taskData.userModel); + } + + try { + boolean stop = false; + for (Map.Entry stringSupplierEntry : processItemMap.entrySet()) { + processName = stringSupplierEntry.getKey(); + IProcessItem processItem = stringSupplierEntry.getValue(); + // + long processItemStartTime = SystemClock.now(); + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_process.9cb8"), processItem.name()); + String interruptMsg = this.asyncWebHooks(processName); + if (interruptMsg != null) { + // 事件脚本中断构建流程 + logRecorder.system(I18nMessageUtil.get("i18n.execution_interrupted_reason.e3d7"), processItem.name()); + this.asyncWebHooks("stop", "process", processName, "statusMsg", interruptMsg); + buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, buildInfoModel.getBuildId(), BuildStatus.Interrupt, interruptMsg); + stop = true; + break; + } + String errorMsg = processItem.execute(); + if (errorMsg != null) { + // 有条件结束构建流程 + logRecorder.systemError(I18nMessageUtil.get("i18n.execution_exception_with_flow.6d4b"), processItem.name(), errorMsg); + this.asyncWebHooks("stop", "process", processName, "statusMsg", errorMsg); + buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, buildInfoModel.getBuildId(), BuildStatus.Error, errorMsg); + stop = true; + break; + } + logRecorder.system(I18nMessageUtil.get("i18n.execution_ended_with_duration.a59b"), processItem.name(), DateUtil.formatBetween(SystemClock.now() - processItemStartTime)); + } + if (!stop) { + // 没有执行 stop + this.asyncWebHooks("success"); + } + } catch (LogRecorderCloseException logRecorderCloseException) { + log.warn(I18nMessageUtil.get("i18n.build_log_recorder_closed.1cc7"), processName); + } catch (DiyInterruptException diyInterruptException) { + // 主动中断 + this.asyncWebHooks("stop", "process", processName, "statusMsg", diyInterruptException.getMessage()); + buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, buildInfoModel.getBuildId(), BuildStatus.Interrupt, diyInterruptException.getMessage()); + } catch (java.util.concurrent.CancellationException interruptException) { + // 异常中断 + String string = I18nMessageUtil.get("i18n.system_interruption.e37c"); + this.asyncWebHooks("stop", "process", processName, "statusMsg", string); + buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, buildInfoModel.getBuildId(), BuildStatus.Interrupt, string); + } catch (Exception e) { + buildExecuteService.updateStatus(buildInfoModel.getId(), this.logId, buildInfoModel.getBuildId(), BuildStatus.Error, e.getMessage()); + logRecorder.error(I18nMessageUtil.get("i18n.build_failed.a79a") + processName, e); + this.asyncWebHooks("error", "process", processName, "statusMsg", e.getMessage()); + } finally { + this.clearResources(); + logRecorder.system(I18nMessageUtil.get("i18n.build_finished_duration.7f7c"), DateUtil.formatBetween(SystemClock.now() - startTime)); + this.asyncWebHooks("done"); + IoUtil.close(logRecorder); + BaseServerController.removeAll(); + } + } + + public void run() { + BuildInfoModel buildInfoModel = this.taskData.buildInfoModel; + try { + I18nMessageUtil.setLanguage(this.language); + this.runTask(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.build_unknown_error.dad6"), e); + } finally { + BUILD_MANAGE_MAP.remove(buildInfoModel.getId()); + I18nMessageUtil.clearLanguage(); + } + } + + /** + * 执行 webhooks 通知 + * + * @param type 类型 + * @param other 其他参数 + * @return 是否还继续整个构建流程 + */ + private String asyncWebHooks(String type, Object... other) { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + Map map = new HashMap<>(10); + // + for (int i = 0; i < other.length; i += 2) { + map.put(other[i].toString(), other[i + 1]); + } + map.put("buildId", buildInfoModel.getId()); + map.put("buildNumberId", this.taskData.buildInfoModel.getBuildId()); + map.put("buildName", buildInfoModel.getName()); + map.put("buildSourceFile", FileUtil.getAbsolutePath(this.gitFile)); + map.put("type", type); + map.put("triggerBuildType", taskData.triggerBuildType); + map.put("triggerTime", SystemClock.now()); + String triggerUser = Optional.ofNullable(taskData.userModel).map(BaseIdModel::getId).orElse(UserModel.SYSTEM_ADMIN); + map.put("triggerUser", triggerUser); + String resultDirFile = buildExtraModule.getResultDirFile(); + map.put("buildResultDirFile", resultDirFile); + map.put("buildResultFile", BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), this.taskData.buildInfoModel.getBuildId(), resultDirFile)); + + Opt.ofBlankAble(buildInfoModel.getWebhook()) + .ifPresent(s -> + I18nThreadUtil.execute(() -> { + try { + IPlugin plugin = PluginFactory.getPlugin("webhook"); + map.put("JPOM_WEBHOOK_EVENT", DefaultWebhookPluginImpl.WebhookEvent.BUILD); + plugin.execute(s, map); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.webhooks_invocation_error.9792"), e); + } + }) + ); + // 执行对应的事件脚本 + try { + return this.noticeScript(type, map); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.notice_script_invocation_error.9002"), e); + logRecorder.error(I18nMessageUtil.get("i18n.execute_event_script_error.7c69"), e); + // 执行事件脚本发送异常不终止构建流程 + return null; + } + } + + /** + * 执行事件脚本 + * + * @param type 事件类型 + * @param map 相关参数 + * @return 是否还继续整个构建流程 + */ + private String noticeScript(String type, Map map) { + String noticeScriptId = this.buildExtraModule.getNoticeScriptId(); + if (StrUtil.isEmpty(noticeScriptId)) { + return null; + } + List list = StrUtil.splitTrim(noticeScriptId, StrUtil.COMMA); + for (String noticeScriptIdItem : list) { + String error = this.noticeScript(noticeScriptIdItem, type, map); + if (error != null) { + return error; + } + } + return null; + } + + /** + * 执行事件脚本 + * + * @param noticeScriptId 脚本id + * @param type 事件类型 + * @param map 相关参数 + * @return 是否还继续整个构建流程 + */ + private String noticeScript(String noticeScriptId, String type, Map map) { + ScriptModel scriptModel = scriptServer.getByKey(noticeScriptId); + if (scriptModel == null) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.event_script_does_not_exist.e726"), type, noticeScriptId); + return null; + } + // 判断是否包含需要执行的事件 + if (!StrUtil.containsAnyIgnoreCase(scriptModel.getDescription(), type, "all")) { + log.warn(I18nMessageUtil.get("i18n.ignore_execution_event_script.8872"), type, scriptModel.getName(), noticeScriptId); + return null; + } + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_event_script.377e"), type); + // 环境变量 + Map environment = taskData.environmentMapBuilder.environment(map); + ScriptExecuteLogModel logModel = scriptExecuteLogServer.create(scriptModel, 3, this.taskData.buildInfoModel.getWorkspaceId()); + File logFile = scriptModel.logFile(logModel.getId()); + File scriptFile = null; + LogRecorder scriptLog = LogRecorder.builder().file(logFile).build(); + final String[] lastMsg = new String[1]; + try { + // 创建执行器 + scriptFile = scriptExecuteLogServer.toExecLogFile(scriptModel); + scriptExecuteLogServer.updateStatus(logModel.getId(), CommandExecLogModel.Status.ING); + int waitFor = JpomApplication.getInstance().execScript(scriptModel.getContext(), file -> { + try { + // 输出环境变量 + taskData.environmentMapBuilder.eachStr(s -> { + logRecorder.system(s); + scriptLog.info(s); + }, map); + // + return CommandUtil.execWaitFor(file, null, environment, null, (s, process) -> { + logRecorder.info(s); + scriptLog.info(s); + lastMsg[0] = s; + }); + } catch (IOException | InterruptedException e) { + scriptExecuteLogServer.updateStatus(logModel.getId(), CommandExecLogModel.Status.ERROR); + throw Lombok.sneakyThrow(e); + } + }); + logRecorder.system(I18nMessageUtil.get("i18n.execute_script_exit_code.64a8"), type, waitFor); + scriptExecuteLogServer.updateStatus(logModel.getId(), CommandExecLogModel.Status.DONE, waitFor); + // 判断是否为严格执行 + if (buildExtraModule.strictlyEnforce() && waitFor != 0) { + //logRecorder.systemError("严格执行模式,事件脚本返回状态码异常"); + return I18nMessageUtil.get("i18n.strict_execution_mode_event_script_error.c82a") + waitFor; + } + if (StrUtil.startWithIgnoreCase(lastMsg[0], "interrupt " + type)) { + return I18nMessageUtil.get("i18n.event_script_interrupted.8c79") + lastMsg[0]; + } + return null; + } finally { + try { + FileUtil.del(scriptFile); + } catch (Exception ignored) { + } + IoUtil.close(scriptLog); + } + } + + /** + * 取消构建 + * + * @param id id + * @return bool + */ + public static boolean cancelTaskById(String id) { + return Optional.ofNullable(BuildExecuteManage.BUILD_MANAGE_MAP.get(id)).map(buildExecuteManage1 -> { + buildExecuteManage1.cancelTask(I18nMessageUtil.get("i18n.manual_cancel_task.e592")); + return true; + }).orElse(false); + } + + /** + * 模糊匹配 + * + * @param list 待匹配待列表 + * @param pattern 迷糊的表达式 + * @return 匹配到到值 + */ + private static String fuzzyMatch(List list, String pattern) { + Assert.notEmpty(list, I18nMessageUtil.get("i18n.no_branches_or_tags_in_repository.76b6")); + if (AntPathUtil.ANT_PATH_MATCHER.isPattern(pattern)) { + List collect = list.stream().filter(s -> AntPathUtil.ANT_PATH_MATCHER.match(pattern, s)).collect(Collectors.toList()); + return CollUtil.getFirst(collect); + } + return pattern; + } +} 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 new file mode 100644 index 0000000000..2cc893688d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteService.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.data.RepositoryModel; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.log.BuildHistoryLog; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; +import org.dromara.jpom.service.dblog.RepositoryService; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.StringUtil; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.io.File; +import java.util.Objects; + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +@Service +@Slf4j +public class BuildExecuteService { + + + private final BuildInfoService buildService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; + private final RepositoryService repositoryService; + private final WorkspaceEnvVarService workspaceEnvVarService; + private final BuildExecutorPoolService buildExecutorPoolService; + + public BuildExecuteService(BuildInfoService buildService, + DbBuildHistoryLogService dbBuildHistoryLogService, + RepositoryService repositoryService, + WorkspaceEnvVarService workspaceEnvVarService, + BuildExecutorPoolService buildExecutorPoolService) { + this.buildService = buildService; + this.dbBuildHistoryLogService = dbBuildHistoryLogService; + this.repositoryService = repositoryService; + this.workspaceEnvVarService = workspaceEnvVarService; + this.buildExecutorPoolService = buildExecutorPoolService; + } + + + /** + * check status + * + * @param buildInfoModel 构建信息 + * @return 错误消息 + */ + public String checkStatus(BuildInfoModel buildInfoModel) { + if (buildInfoModel == null) { + return I18nMessageUtil.get("i18n.build_info_not_exist.4470"); + } + Integer status = buildInfoModel.getStatus(); + if (status == null) { + return null; + } + BuildStatus nowStatus = BaseEnum.getEnum(BuildStatus.class, status); + Objects.requireNonNull(nowStatus); + if (nowStatus.isProgress()) { + return buildInfoModel.getName() + I18nMessageUtil.get("i18n.current_status.81c0") + nowStatus.getDesc(); + } + return null; + } + + /** + * start build + * + * @param buildInfoId 构建Id + * @param userModel 用户信息 + * @param delay 延迟的时间 + * @param triggerBuildType 触发构建类型 + * @param buildRemark 构建备注 + * @param parametersEnv 外部环境变量 + * @return json + */ + public IJsonMessage start(String buildInfoId, UserModel userModel, Integer delay, int triggerBuildType, String buildRemark, Object... parametersEnv) { + return this.start(buildInfoId, userModel, delay, triggerBuildType, buildRemark, null, parametersEnv); + } + + /** + * start build + * + * @param buildInfoId 构建Id + * @param userModel 用户信息 + * @param delay 延迟的时间 + * @param triggerBuildType 触发构建类型 + * @param buildRemark 构建备注 + * @param checkRepositoryDiff 差异构建 + * @param parametersEnv 外部环境变量 + * @return json + */ + public IJsonMessage start(String buildInfoId, UserModel userModel, Integer delay, + int triggerBuildType, String buildRemark, String checkRepositoryDiff, + Object... parametersEnv) { + synchronized (buildInfoId.intern()) { + BuildInfoModel buildInfoModel = buildService.getByKey(buildInfoId); + String e = this.checkStatus(buildInfoModel); + Assert.isNull(e, () -> e); + // + boolean containsKey = BuildExecuteManage.BUILD_MANAGE_MAP.containsKey(buildInfoModel.getId()); + Assert.state(!containsKey, I18nMessageUtil.get("i18n.build_in_progress.4d33")); + // + BuildExtraModule buildExtraModule = buildInfoModel.extraData(); + Assert.notNull(buildExtraModule, I18nMessageUtil.get("i18n.build_info_missing.0ab0")); + // load repository + RepositoryModel repositoryModel = repositoryService.getByKey(buildInfoModel.getRepositoryId(), false); + Assert.notNull(repositoryModel, I18nMessageUtil.get("i18n.repository_info_does_not_exist.4142")); + EnvironmentMapBuilder environmentMapBuilder = workspaceEnvVarService.getEnv(buildInfoModel.getWorkspaceId()); + // 解析外部变量 + environmentMapBuilder.putObjectArray(parametersEnv).putStr(StringUtil.parseEnvStr(buildInfoModel.getBuildEnvParameter())); + // set buildId field + buildInfoModel.setBuildId(this.nextBuildId(buildInfoModel)); + // + TaskData.TaskDataBuilder taskBuilder = TaskData.builder() + .buildInfoModel(buildInfoModel) + .repositoryModel(repositoryModel) + .userModel(userModel) + .buildRemark(buildRemark) + .delay(delay) + .environmentMapBuilder(environmentMapBuilder) + .triggerBuildType(triggerBuildType); + // + Opt.ofBlankAble(checkRepositoryDiff).map(Convert::toBool).ifPresent(taskBuilder::checkRepositoryDiff); + this.runTask(taskBuilder.build(), buildExtraModule); + String startMsg = I18nMessageUtil.get("i18n.start_building.1039"); + String delayMsg = StrUtil.format(I18nMessageUtil.get("i18n.delay_build.7d62"), delay); + String msg = (delay == null || delay <= 0) ? startMsg : delayMsg; + return JsonMessage.success(msg, buildInfoModel.getBuildId()); + } + } + + /** + * 回滚 + * + * @param oldLog 构建历史 + * @param item 构建项 + * @param userModel 用户信息 + */ + public int rollback(BuildHistoryLog oldLog, BuildInfoModel item, UserModel userModel) { + synchronized (item.getId().intern()) { + String e = this.checkStatus(item); + Assert.isNull(e, () -> e); + Integer fromBuildNumberId = ObjectUtil.defaultIfNull(oldLog.getFromBuildNumberId(), oldLog.getBuildNumberId()); + int buildId = this.nextBuildId(item); + item.setBuildId(buildId); + // 创建新的构建记录 + BuildHistoryLog buildHistoryLog = oldLog.toJson().to(BuildHistoryLog.class); + buildHistoryLog.setId(null); + buildHistoryLog.setCreateUser(null); + buildHistoryLog.setCreateTimeMillis(null); + buildHistoryLog.setModifyUser(null); + buildHistoryLog.setModifyTimeMillis(null); + buildHistoryLog.setResultFileSize(null); + BuildStatus pubIng = BuildStatus.PubIng; + buildHistoryLog.setStatus(pubIng.getCode()); + buildHistoryLog.setTriggerBuildType(3); + buildHistoryLog.setBuildNumberId(buildId); + buildHistoryLog.setFromBuildNumberId(fromBuildNumberId); + buildHistoryLog.setStartTime(SystemClock.now()); + buildHistoryLog.setEndTime(null); + dbBuildHistoryLogService.insert(buildHistoryLog); + // + buildService.updateStatus(buildHistoryLog.getBuildDataId(), pubIng, I18nMessageUtil.get("i18n.start_rolling_back_execution.a019")); + + BuildExtraModule buildExtraModule = BuildExtraModule.build(buildHistoryLog); + // + 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()) + .userModel(userModel) + .buildNumberId(buildHistoryLog.getBuildNumberId()) + .fromBuildNumberId(fromBuildNumberId) + .logRecorder(logRecorder) + .buildEnv(environmentMapBuilder) + .build(); + // + logRecorder.system(I18nMessageUtil.get("i18n.prepare_rollback.dba6"), fromBuildNumberId, buildId); + // + buildExecutorPoolService.execute(() -> manage.rollback(item)); + return buildId; + } + } + + private int nextBuildId(BuildInfoModel buildInfoModel) { + // set buildId field + int buildId = ObjectUtil.defaultIfNull(buildInfoModel.getBuildId(), 0); + BuildInfoModel update = new BuildInfoModel(); + update.setBuildId(buildId + 1); + update.setId(buildInfoModel.getId()); + buildService.updateById(update); + return update.getBuildId(); + } + + /** + * 创建构建 + * + * @param taskData 任务 + * @param buildExtraModule 构建更多配置信息 + */ + private void runTask(TaskData taskData, BuildExtraModule buildExtraModule) { + String logId = this.insertLog(buildExtraModule, taskData); + // + BuildExecuteManage.BuildExecuteManageBuilder builder = BuildExecuteManage.builder() + .taskData(taskData) + .logId(logId) + .buildExtraModule(buildExtraModule); + builder.build().submitTask(); + } + + + /** + * 插入记录 + */ + private String insertLog(BuildExtraModule buildExtraModule, TaskData taskData) { + BuildInfoModel buildInfoModel = taskData.buildInfoModel; + buildExtraModule.updateValue(buildInfoModel); + BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); + // 更新其他配置字段 + //buildHistoryLog.fillLogValue(buildExtraModule); + buildHistoryLog.setTriggerBuildType(taskData.triggerBuildType); + // + buildHistoryLog.setBuildNumberId(buildInfoModel.getBuildId()); + buildHistoryLog.setBuildName(buildInfoModel.getName()); + buildHistoryLog.setBuildDataId(buildInfoModel.getId()); + buildHistoryLog.setWorkspaceId(buildInfoModel.getWorkspaceId()); + buildHistoryLog.setResultDirFile(buildInfoModel.getResultDirFile()); + buildHistoryLog.setReleaseMethod(buildExtraModule.getReleaseMethod()); + // + BuildStatus waitExec = BuildStatus.WaitExec; + buildHistoryLog.setStatus(waitExec.getCode()); + buildHistoryLog.setStartTime(SystemClock.now()); + buildHistoryLog.setBuildRemark(taskData.buildRemark); + // 缓存数据 - 保证数据一直 + buildHistoryLog.setExtraData(buildExtraModule.toJson().toString()); + dbBuildHistoryLogService.insert(buildHistoryLog); + // + buildService.updateStatus(buildHistoryLog.getBuildDataId(), waitExec, I18nMessageUtil.get("i18n.start_queuing_for_execution.7417")); + return buildHistoryLog.getId(); + } + + /** + * 更新状态 + * + * @param buildId 构建ID + * @param logId 记录ID + * @param buildStatus to status + */ + public void updateStatus(String buildId, String logId, int buildNumberId, BuildStatus buildStatus, String msg) { + BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); + buildHistoryLog.setId(logId); + buildHistoryLog.setStatusMsg(msg); + buildHistoryLog.setStatus(buildStatus.getCode()); + if (!buildStatus.isProgress()) { + // 结束 + buildHistoryLog.setEndTime(SystemClock.now()); + } + dbBuildHistoryLogService.updateById(buildHistoryLog); + buildService.updateStatus(buildId, buildNumberId, buildStatus, msg); + } + + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecutorPoolService.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecutorPoolService.java new file mode 100644 index 0000000000..24b8dd575b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecutorPoolService.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.thread.ExecutorBuilder; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ThreadPoolExecutor; + +@Service +@Slf4j +public class BuildExecutorPoolService { + /** + * 构建线程池 + */ + private volatile ThreadPoolExecutor threadPoolExecutor; + private final BuildExtConfig buildExtConfig; + + public BuildExecutorPoolService(BuildExtConfig buildExtConfig) { + this.buildExtConfig = buildExtConfig; + } + + public ThreadPoolExecutor getThreadPoolExecutor() { + this.initPool(); + return threadPoolExecutor; + } + + public void execute(Runnable command) { + this.initPool(); + threadPoolExecutor.execute(command); + } + + /** + * 创建构建线程池 + */ + private void initPool() { + if (threadPoolExecutor == null) { + synchronized (BuildExecutorPoolService.class) { + if (threadPoolExecutor == null) { + ExecutorBuilder executorBuilder = ExecutorBuilder.create(); + int poolSize = buildExtConfig.getPoolSize(); + if (poolSize > 0) { + executorBuilder.setCorePoolSize(poolSize).setMaxPoolSize(poolSize); + } + executorBuilder.useArrayBlockingQueue(Math.max(buildExtConfig.getPoolWaitQueue(), 1)); + executorBuilder.setHandler(new ThreadPoolExecutor.DiscardPolicy() { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + if (r instanceof BuildExecuteManage) { + // 取消任务 + BuildExecuteManage buildExecuteManage = (BuildExecuteManage) r; + buildExecuteManage.rejectedExecution(); + } else { + log.warn(I18nMessageUtil.get("i18n.build_thread_pool_rejected_task.3bad"), r.getClass()); + } + } + }); + threadPoolExecutor = executorBuilder.build(); + JpomApplication.register("build", threadPoolExecutor); + } + } + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java new file mode 100644 index 0000000000..9234fdab44 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExtraModule.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.io.FileUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.BaseModel; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.log.BuildHistoryLog; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; + +import java.io.File; + +/** + * 构建物基类 + * + * @author bwcx_jzy + * @since 2019/7/19 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class BuildExtraModule extends BaseModel { + /** + * 发布方式 + * + * @see BuildReleaseMethod + * @see BuildInfoModel#getReleaseMethod() + */ + private int releaseMethod; + /** + * 发布方法的数据id + * + * @see BuildInfoModel#getReleaseMethodDataId() + */ + private String releaseMethodDataId; + /** + * 分发后的操作 + * 仅在项目发布类型生效 + * + * @see AfterOpt + * @see BuildInfoModel#getExtraData() + */ + private int afterOpt; + /** + * 是否清空旧包发布 + */ + private boolean clearOld; + /** + * 构建产物目录 + */ + private String resultDirFile; + /** + * 发布后命令 ssh 才能用上 + */ + private String releaseCommand; + /** + * 发布前命令 ssh 才能用上 + */ + private String releaseBeforeCommand; + /** + * 发布到ssh中的目录 + */ + private String releasePath; + /** + * 工作空间 ID + */ + private String workspaceId; + /** + * 增量同步 + */ + private boolean diffSync; + + private String fromTag; + + private String dockerfile; + + private String dockerTag; + /** + * docker 集群ID + */ + private String dockerSwarmId; + /** + * 集群服务名 + */ + private String dockerSwarmServiceName; + /** + * 缓存构建目录 + */ + private Boolean cacheBuild; + /** + * 是否保留构建历史产物 + */ + private Boolean saveBuildFile; + + /** + * 构建的时候判断仓库代码是否有变动,true 表示需要判断代码有变动才触发构建 + */ + private Boolean checkRepositoryDiff; + + /** + * 事件通知执行的脚本 ID + */ + private String noticeScriptId; + + /** + * 是否执行推送到仓库中 + */ + private Boolean pushToRepository; + /** + * docker tag 版本字段递增 + */ + private Boolean dockerTagIncrement; + + /** + * 附加环境变量,比如常见的 .env 文件 + */ + private String attachEnv; + + /** + * 容器构建参数 如:key1=values1&keyvalue2 + */ + private String dockerBuildArgs; + + /** + * 构建镜像尝试去更新基础镜像的新版本 + */ + private Boolean dockerBuildPull; + /** + * 构建镜像的过程不使用缓存 + */ + private Boolean dockerNoCache; + /** + * 镜像标签 + */ + private String dockerImagesLabels; + /** + * 项目二级目录 + */ + private String projectSecondaryDirectory; + + /** + * 保存项目文件前先关闭 + */ + private Boolean projectUploadCloseFirst; + + /** + * 是否为严格执行脚本,严格执行脚本执行结果返回状态码必须是 0 + */ + private Boolean strictlyEnforce; + /** + * 是否同步到文件管理中心 + */ + private Boolean syncFileStorage; + + /** + * 排除指定目录发布 + */ + private String excludeReleaseAnt; + + /** + * 是否发布隐藏文件 + */ + private Boolean releaseHideFile; + + /** + * 克隆深度 + */ + private Integer cloneDepth; + + /** + * 构建历史保留个数 + */ + private Integer resultKeepCount; + /** + * 文件中心保留天数 + */ + private Integer fileStorageKeepDay; + /** + * 本地构建执行命令方式 + */ + private String commandExecMode; + + public boolean strictlyEnforce() { + return strictlyEnforce != null && strictlyEnforce; + } + + public String getResultDirFile() { + if (resultDirFile == null) { + return null; + } + return FileUtil.normalize(this.resultDirFile.trim()); + } + + public File resultDirFile(int buildNumberId) { + return BuildUtil.getHistoryPackageFile(this.getId(), buildNumberId, this.getResultDirFile()); + } + + /** + * 更新 字段值 + * + * @param buildInfoModel 构建对象 + */ + public void updateValue(BuildInfoModel buildInfoModel) { + this.setId(buildInfoModel.getId()); + this.setName(buildInfoModel.getName()); + this.setReleaseMethod(buildInfoModel.getReleaseMethod()); + this.setResultDirFile(buildInfoModel.getResultDirFile()); + this.setReleaseMethodDataId(buildInfoModel.getReleaseMethodDataId()); + this.setWorkspaceId(buildInfoModel.getWorkspaceId()); + } + + public static BuildExtraModule build(BuildHistoryLog buildHistoryLog) { + BuildExtraModule buildExtraModule = StringUtil.jsonConvert(buildHistoryLog.getExtraData(), BuildExtraModule.class); + Assert.notNull(buildExtraModule, I18nMessageUtil.get("i18n.incomplete_data_not_supported.b5d3")); + buildExtraModule.setId(buildHistoryLog.getBuildDataId()); + buildExtraModule.setName(buildHistoryLog.getBuildName()); + buildExtraModule.setReleaseMethod(buildHistoryLog.getReleaseMethod()); + buildExtraModule.setResultDirFile(buildHistoryLog.getResultDirFile()); + buildExtraModule.setWorkspaceId(buildHistoryLog.getWorkspaceId()); + // + return buildExtraModule; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildUtil.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildUtil.java new file mode 100644 index 0000000000..b7eadd5a4e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildUtil.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.core.util.ZipUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.compress.CompressUtil; +import cn.hutool.extra.compress.archiver.Archiver; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.data.RepositoryModel; +import org.springframework.util.Assert; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.function.BiFunction; + +/** + * 构建工具类 + * + * @author bwcx_jzy + * @since 2019/7/19 + */ +public class BuildUtil { + + public static Long buildCacheSize = 0L; + + public static final String USE_TAR_GZ = "USE_TAR_GZ"; + + /** + * 刷新存储文件大小 + */ + public static void reloadCacheSize() { + File buildDataDir = BuildUtil.getBuildDataDir(); + BuildUtil.buildCacheSize = FileUtil.size(buildDataDir); + // + } + + public static File getBuildDataFile(String id) { + return FileUtil.file(getBuildDataDir(), id); + } + +// /** +// * 获取代码路径 +// * +// * @param buildModel 实体 +// * @return file +// * @see BuildUtil#getSourceById +// */ +// @Deprecated +// public static File getSource(BuildModel buildModel) { +// return FileUtil.file(BuildUtil.getBuildDataFile(buildModel.getId()), "source"); +// } + + /** + * @param id 构建ID + * @return file + * @author Hotstrip + * 新版本获取代码路径 + * @since 2021-08-22 + */ + public static File getSourceById(String id) { + return FileUtil.file(BuildUtil.getBuildDataFile(id), "source"); + } + + public static File getBuildDataDir() { + return FileUtil.file(JpomApplication.getInstance().getDataPath(), "build"); + } + + /** + * 获取构建产物存放路径 + * + * @param buildModelId 构建实体 + * @param buildId id + * @param resultFile 结果目录 + * @return file + */ + public static File getHistoryPackageFile(String buildModelId, int buildId, String resultFile) { + if (buildId <= 0) { + // 没有 0 号构建id,避免生成 #0 文件夹 + return null; + } + if (StrUtil.isEmpty(buildModelId) || StrUtil.isEmpty(resultFile)) { + return null; + } + ResultDirFileAction resultDirFileAction = ResultDirFileAction.parse(resultFile); + ResultDirFileAction.Type type = resultDirFileAction.getType(); + if (type == ResultDirFileAction.Type.ANT_PATH) { + // ANT 模式 不能直接获取,避免提前创建文件夹 + return null; + } + File result = FileUtil.file(getBuildDataFile(buildModelId), "history", BuildInfoModel.getBuildIdStr(buildId), "result"); + return FileUtil.file(result, resultFile); + } + + /** + * 插件构建产物存放路径 + * + * @param buildModelId 构建实体 + * @param buildId id + */ + public static void mkdirHistoryPackageFile(String buildModelId, int buildId) { + File result = FileUtil.file(getBuildDataFile(buildModelId), "history", BuildInfoModel.getBuildIdStr(buildId), "result"); + FileUtil.mkdir(result); + } + + /** + * 获取构建产物存放路径 + * + * @param buildModelId 构建实体 + * @param buildId id + * @return file + */ + public static File getHistoryPackageZipFile(String buildModelId, int buildId) { + return FileUtil.file(getBuildDataFile(buildModelId), + "history", + BuildInfoModel.getBuildIdStr(buildId), + "zip"); + } + + /** + * 获取日志记录文件 + * + * @param buildModelId buildModelId + * @param buildId 构建编号 + * @return file + */ + public static File getLogFile(String buildModelId, int buildId) { + if (StrUtil.isEmpty(buildModelId)) { + return null; + } + return FileUtil.file(getBuildDataFile(buildModelId), + "history", + BuildInfoModel.getBuildIdStr(buildId), + "info.log"); + } + + /** + * 如果为文件夹自动打包为zip ,反之返回null + * + * @param file file + * @return 压缩包文件 + */ + private static File isDirPackage(String id, int buildNumberId, File file, boolean tarGz) { + Assert.state(file != null && file.exists(), I18nMessageUtil.get("i18n.product_file_does_not_exist.ee13")); + if (file.isFile()) { + return null; + } + Assert.state(!FileUtil.isDirEmpty(file), I18nMessageUtil.get("i18n.empty_folder_cannot_be_packed.5a75") + buildNumberId); + String name = FileUtil.getName(file); + // 如果产物配置 / 时无法获取文件名,采用 result + name = StrUtil.emptyToDefault(name, "result"); + // 保存目录存放值 history 路径 + File packageFile = BuildUtil.getHistoryPackageZipFile(id, buildNumberId); + File zipFile = tarGz ? FileUtil.file(packageFile, name + ".tar.gz") : FileUtil.file(packageFile, name + ".zip"); + // 不存在则打包 + if (tarGz) { + try (Archiver archiver = CompressUtil.createArchiver(Charset.defaultCharset(), "tar.gz", zipFile)) { + archiver.add(file); + } + } else { + ZipUtil.zip(file.getAbsolutePath(), zipFile.getAbsolutePath()); + } + return zipFile; + } + + /** + * 如果为文件夹自动打包为zip ,反之返回null + * + * @param file file + * @param id 构建Id + * @param buildNumberId 构建序号 + * @param tarGz 是否打包 为 tar + * @param consumer 文件回调 + * @return 执行结果 + */ + public static T loadDirPackage(String id, int buildNumberId, File file, boolean tarGz, BiFunction consumer) { + File dirPackage = isDirPackage(id, buildNumberId, file, tarGz); + if (dirPackage == null) { + return consumer.apply(false, file); + } else { + return consumer.apply(true, dirPackage); + } + } + + + /** + * get rsa file + * + * @param path 文件名 + * @return file + */ + public static File getRepositoryRsaFile(String path) { + File sshDir = FileUtil.file(JpomApplication.getInstance().getDataPath(), ServerConst.SSH_KEY); + return FileUtil.file(sshDir, path); + } + + /** + * get rsa file + * + * @param repositoryModel 仓库 + * @return 文件 + */ + public static File getRepositoryRsaFile(RepositoryModel repositoryModel) { + if (StrUtil.isEmpty(repositoryModel.getRsaPrv())) { + return null; + } + // ssh + File rsaFile; + if (StrUtil.startWith(repositoryModel.getRsaPrv(), URLUtil.FILE_URL_PREFIX)) { + String rsaPath = StrUtil.removePrefix(repositoryModel.getRsaPrv(), URLUtil.FILE_URL_PREFIX); + rsaFile = FileUtil.file(rsaPath); + } else { + if (StrUtil.isEmpty(repositoryModel.getId())) { + rsaFile = FileUtil.file(JpomApplication.getInstance().getTempPath(), ServerConst.SSH_KEY, SecureUtil.sha1(repositoryModel.getGitUrl()) + ServerConst.ID_RSA); + } else { + rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModel.getId() + ServerConst.ID_RSA); + } + // 写入 + FileUtil.writeUtf8String(repositoryModel.getRsaPrv(), rsaFile); + } + Assert.state(FileUtil.isFile(rsaFile), I18nMessageUtil.get("i18n.repository_key_file_does_not_exist_or_is_abnormal.1d78")); + return rsaFile; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/DiyInterruptException.java b/modules/server/src/main/java/org/dromara/jpom/build/DiyInterruptException.java new file mode 100644 index 0000000000..4a83dbc71b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/DiyInterruptException.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +/** + * @author bwcx_jzy + * @since 2023/3/2 + */ +public class DiyInterruptException extends RuntimeException { + + public DiyInterruptException(String message) { + super(message); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/DockerYmlDsl.java b/modules/server/src/main/java/org/dromara/jpom/build/DockerYmlDsl.java new file mode 100644 index 0000000000..487db85e62 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/DockerYmlDsl.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.*; +import cn.hutool.setting.yaml.YamlUtil; +import cn.keepbx.jpom.model.BaseJsonModel; +import cn.keepbx.jpom.plugins.IPlugin; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.IDockerConfigPlugin; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * docker 构建 配置 + *

+ * https://www.jianshu.com/p/54cfa5721d5f + * + * @author bwcx_jzy + * @since 2022/1/25 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Slf4j +public class DockerYmlDsl extends BaseJsonModel { + + /** + * 基础镜像 + */ + private String runsOn; + /** + * 使用对应到 docker tag 构建 + */ + private String fromTag; + /** + * 构建步骤 + */ + private List> steps; + + /** + * 将本地文件复制到 容器 + *

+ * ::true + *

+ * * If this flag is set to true, all children of the local directory will be copied to the remote without the root directory. For ex: if + * * I have root/titi and root/tata and the remote path is /var/data. dirChildrenOnly = true will create /var/data/titi and /var/data/tata + * * dirChildrenOnly = false will create /var/data/root/titi and /var/data/root/tata + * * + * * @param dirChildrenOnly + * * if root directory is ignored + */ + private List copy; + /** + * bind mounts 将宿主机上的任意位置的文件或者目录挂在到容器 (--mount type=bind,src=源目录,dst=目标目录) + * /host:/container:ro + */ + private List binds; + /** + * 环境变量 + */ + private Map env; + /** + * https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerCreate + *

+ * cpuCount + *

+ * cpuPercent + *

+ * memoryReservation + *

+ * cpusetCpus 允许执行的CPU(例如,0-3, 0,1)。 + *

+ * cpuShares + */ + private Map hostConfig; + + /** + * 验证信息是否正确 + * + * @param dockerInfoService 容器server + * @param machineDockerServer 机器server + * @param workspaceId 工作空间id + * @param plugin 插件 + */ + public void check(DockerInfoService dockerInfoService, + MachineDockerServer machineDockerServer, + String workspaceId, + IDockerConfigPlugin plugin) { + Assert.hasText(runsOn, I18nMessageUtil.get("i18n.please_fill_in_runs_on.c2ff")); + Validator.validateMatchRegex(StringUtil.GENERAL_STR, runsOn, I18nMessageUtil.get("i18n.invalid_runs_on_image_name.4b96")); + Assert.state(CollUtil.isNotEmpty(steps), I18nMessageUtil.get("i18n.please_fill_in_steps.229d")); + this.stepsCheck(dockerInfoService, machineDockerServer, workspaceId, plugin); + } + + /** + * 检查 steps + */ + private void stepsCheck(DockerInfoService dockerInfoService, MachineDockerServer machineDockerServer, + String workspaceId, + IDockerConfigPlugin plugin) { + Set usesSet = new HashSet<>(); + boolean containsRun = false; + for (Map step : steps) { + if (!containsRun && step.containsKey("run")) { + containsRun = true; + } + if (step.containsKey("env")) { + Object env1 = step.get("env"); + Assert.isInstanceOf(Map.class, env1, I18nMessageUtil.get("i18n.env_must_be_map_type.f8ad")); + } + if (step.containsKey("uses")) { + Object uses1 = step.get("uses"); + Assert.isInstanceOf(String.class, uses1, I18nMessageUtil.get("i18n.uses_only_supports_string_type.ac54")); + String uses = (String) step.get("uses"); + if ("node".equals(uses)) { + nodePluginCheck(step); + } else if ("java".equals(uses)) { + javaPluginCheck(step); + } else if ("gradle".equals(uses)) { + gradlePluginCheck(step); + } else if ("maven".equals(uses)) { + mavenPluginCheck(step, dockerInfoService, machineDockerServer, workspaceId); + } else if ("cache".equals(uses)) { + cachePluginCheck(step); + } else if ("go".equals(uses)) { + goPluginCheck(step); + } else if ("python3".equals(uses)) { + python3PluginCheck(step); + } else { + // 其他自定义插件 + File tmpDir = FileUtil.file(FileUtil.getTmpDir(), "check-users"); + File pluginInstallResource = null; + try { + pluginInstallResource = plugin.getResourceToFile("uses/" + uses + "/install.sh", tmpDir); + Assert.notNull(pluginInstallResource, StrUtil.format(I18nMessageUtil.get("i18n.unsupported_plugin_message.2889"), uses)); + } finally { + FileUtil.del(pluginInstallResource); + } + } + usesSet.add(uses); + } + } + if (usesSet.contains("maven") && !usesSet.contains("java")) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.maven_plugin_depends_on_java.23f8")); + } + if (usesSet.contains("gradle") && !usesSet.contains("java")) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.gradle_plugin_depends_on_java.2bb3")); + } + Assert.isTrue(containsRun, I18nMessageUtil.get("i18n.no_run_found_in_steps.a141")); + } + + private void cachePluginCheck(Map step) { + Object path = step.get("path"); + Assert.notNull(path, I18nMessageUtil.get("i18n.cache_plugin_path_required.2093")); + } + + /** + * 检查 maven 插件 + * + * @param step 参数 + */ + private void mavenPluginCheck(Map step, DockerInfoService dockerInfoService, MachineDockerServer machineDockerServer, String workspaceId) { + Object version1 = step.get("version"); + Assert.notNull(version1, I18nMessageUtil.get("i18n.maven_plugin_version_required.71f1")); + String version = String.valueOf(version1); + String link = String.format("https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/%s/binaries/apache-maven-%s-bin.tar.gz", version, version); + HttpRequest request = HttpUtil.createRequest(Method.HEAD, link); + try (HttpResponse httpResponse = request.execute()) { + boolean success = httpResponse.isOk() + || httpResponse.getStatus() == HttpStatus.HTTP_MOVED_TEMP + || httpResponse.getStatus() == HttpStatus.HTTP_BAD_METHOD; + if (success) { + return; + } + } + // 判断容器中是否存在 + try { + // 根据 tag 查询 + List dockerInfoModels = + dockerInfoService + .queryByTag(workspaceId, fromTag); + Map map = machineDockerServer.dockerParameter(dockerInfoModels); + if (map != null) { + map.put("pluginName", "maven"); + map.put("version", version); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + boolean exists = Convert.toBool(plugin.execute("hasDependPlugin", map), false); + if (exists) { + return; + } + } + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.check_docker_dependency_error.60f7"), e.getMessage()); + } + // 提示远程版本 + Collection pluginVersion = this.listMavenPluginVersion(); + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.please_fill_in_correct_maven_version.468c") + CollUtil.join(pluginVersion, StrUtil.COMMA)); + } + + + private Collection listMavenPluginVersion() { + String html = HttpUtil.get("https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/"); + //使用正则获取所有可用版本 + List versions = ReUtil.findAll("(.*?)", html, 1); + Set set = versions.stream() + .map(s -> StrUtil.removeSuffix(s, StrUtil.SLASH)) + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toSet()); + Assert.notEmpty(set, I18nMessageUtil.get("i18n.no_available_maven_versions.dffe")); + return set; + } + + /** + * 检查 go 插件 + * + * @param step 参数 + */ + private void javaPluginCheck(Map step) { + Object version1 = step.get("version"); + Assert.notNull(version1, I18nMessageUtil.get("i18n.java_plugin_version_required.de39")); + Integer version = Integer.valueOf(String.valueOf(version1)); + List supportedVersions = ListUtil.of(8, 11, 17, 18); + Assert.isTrue(supportedVersions.contains(version), String.format(I18nMessageUtil.get("i18n.supported_java_plugin_versions.bd70"), supportedVersions)); + } + + + /** + * 检查 gradle 插件 + * + * @param step 参数 + */ + private void gradlePluginCheck(Map step) { + Object version1 = step.get("version"); + Assert.notNull(version1, I18nMessageUtil.get("i18n.gradle_plugin_version_required.b983")); + String version = String.valueOf(version1); + String link = String.format("https://downloads.gradle-dn.com/distributions/gradle-%s-bin.zip", version); + HttpUtil.createRequest(Method.HEAD, link).thenFunction(httpResponse -> { + Assert.isTrue(httpResponse.isOk() || + httpResponse.getStatus() == HttpStatus.HTTP_MOVED_TEMP || + httpResponse.getStatus() == HttpStatus.HTTP_SEE_OTHER, I18nMessageUtil.get("i18n.please_fill_in_correct_gradle_version.6e19")); + return null; + }); + } + + /** + * 检查 node 插件 + * + * @param step 参数 + */ + private void nodePluginCheck(Map step) { + Object version1 = step.get("version"); + Assert.notNull(version1, I18nMessageUtil.get("i18n.node_plugin_version_required.2318")); + String version = String.valueOf(version1); + String link = String.format("https://registry.npmmirror.com/-/binary/node/v%s/node-v%s-linux-x64.tar.gz", version, version); + HttpResponse httpResponse = HttpUtil.createRequest(Method.HEAD, link).execute(); + Assert.isTrue(httpResponse.isOk() || httpResponse.getStatus() == HttpStatus.HTTP_MOVED_TEMP, I18nMessageUtil.get("i18n.please_fill_in_correct_node_version.8483")); + } + + /** + * 检查 go 插件 + * + * @param step 参数 + */ + private void goPluginCheck(Map step) { + Object version1 = step.get("version"); + Assert.notNull(version1, I18nMessageUtil.get("i18n.go_plugin_version_required.ccf6")); + String version = String.valueOf(version1); + String link = String.format("https://studygolang.com/dl/golang/go%s.linux-amd64.tar.gz", version); + HttpUtil.createRequest(Method.HEAD, link).thenFunction(new Function() { + @Override + public Object apply(HttpResponse httpResponse) { + Assert.isTrue(httpResponse.isOk() || + httpResponse.getStatus() == HttpStatus.HTTP_MOVED_TEMP || + httpResponse.getStatus() == HttpStatus.HTTP_SEE_OTHER, I18nMessageUtil.get("i18n.please_fill_in_correct_go_version.44ed")); + return null; + } + }); + } + + /** + * 检查 python3 插件 + * + * @param step 参数 + */ + private void python3PluginCheck(Map step) { + Object version1 = step.get("version"); + Assert.notNull(version1, I18nMessageUtil.get("i18n.python3_plugin_version_required.a0ce")); + String version = String.valueOf(version1); + Assert.state(StrUtil.startWith(version, "3."), () -> { + // + return I18nMessageUtil.get("i18n.please_fill_in_correct_python3_version.abb1"); + }); + String link = String.format("https://repo.huaweicloud.com/python/%s/Python-%s.tar.xz", version, version); + HttpUtil.createRequest(Method.HEAD, link).thenFunction(new Function() { + @Override + public Object apply(HttpResponse httpResponse) { + Assert.isTrue(httpResponse.isOk() || + httpResponse.getStatus() == HttpStatus.HTTP_MOVED_TEMP, I18nMessageUtil.get("i18n.please_fill_in_correct_python3_version.abb1")); + return null; + } + }); + + } + + /** + * 构建对象 + * + * @param yml yml 内容 + * @return DockerYmlDsl + */ + public static DockerYmlDsl build(String yml) { + yml = StrUtil.replace(yml, StrUtil.TAB, StrUtil.SPACE + StrUtil.SPACE); + InputStream inputStream = new ByteArrayInputStream(yml.getBytes()); + return YamlUtil.load(inputStream, DockerYmlDsl.class); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/IProcessItem.java b/modules/server/src/main/java/org/dromara/jpom/build/IProcessItem.java new file mode 100644 index 0000000000..4473deddd4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/IProcessItem.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +/** + * @author bwcx_jzy + * @since 2023/1/8 + */ +public interface IProcessItem { + + /** + * 流程名称 + * + * @return 名称 + */ + String name(); + + /** + * 执行流程 + * + * @return 执行异常消息,存在异常消息需要中断构建 + */ + String execute(); +} 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 new file mode 100644 index 0000000000..41ede70220 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/ReleaseManage.java @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.text.CharPool; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.extra.ssh.JschUtil; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.Session; +import lombok.Builder; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.files.service.FileStorageService; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.BaseEnum; +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.SshModel; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.outgiving.OutGivingRun; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.plugins.JschUtils; +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.ssh.SshService; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.system.JpomRuntimeException; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.MySftp; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * 发布管理 + * + * @author bwcx_jzy + * @since 2019/7/19 + */ +@Builder +@Slf4j +public class ReleaseManage { + + private final UserModel userModel; + private final Integer buildNumberId; + /** + * 回滚来源的构建 id + */ + private Integer fromBuildNumberId; + private final BuildExtraModule buildExtraModule; + private final String logId; + private EnvironmentMapBuilder buildEnv; + + private final LogRecorder logRecorder; + private File resultFile; + private Process process; + + private static BuildExecuteService buildExecuteService; + private static DockerInfoService dockerInfoService; + private static MachineDockerServer machineDockerServer; + private static BuildExtConfig buildExtConfig; + private static FileStorageService fileStorageService; + + private void loadService() { + buildExecuteService = ObjectUtil.defaultIfNull(buildExecuteService, () -> SpringUtil.getBean(BuildExecuteService.class)); + dockerInfoService = ObjectUtil.defaultIfNull(dockerInfoService, () -> SpringUtil.getBean(DockerInfoService.class)); + machineDockerServer = ObjectUtil.defaultIfNull(machineDockerServer, () -> SpringUtil.getBean(MachineDockerServer.class)); + buildExtConfig = ObjectUtil.defaultIfNull(buildExtConfig, () -> SpringUtil.getBean(BuildExtConfig.class)); + fileStorageService = ObjectUtil.defaultIfNull(fileStorageService, () -> SpringUtil.getBean(FileStorageService.class)); + } + + private Integer getRealBuildNumberId() { + return ObjectUtil.defaultIfNull(this.fromBuildNumberId, this.buildNumberId); + } + + private void init() { + this.loadService(); +// if (this.logRecorder == null) { +// // 回滚的时候需要重新创建对象 +// File logFile = BuildUtil.getLogFile(buildExtraModule.getId(), this.buildNumberId); +// this.logRecorder = LogRecorder.builder().file(logFile).build(); +// } + Assert.notNull(buildEnv, I18nMessageUtil.get("i18n.no_environment_variables_found.46ad")); + } + + + private void updateStatus(BuildStatus status, String msg) { + buildExecuteService.updateStatus(this.buildExtraModule.getId(), this.logId, this.buildNumberId, status, msg); + } + + /** + * 不修改为发布中状态 + */ + public String start(Consumer consumer, BuildInfoModel buildInfoModel) throws Exception { + this.init(); + this.resultFile = buildExtraModule.resultDirFile(this.getRealBuildNumberId()); + this.buildEnv.put("BUILD_RESULT_FILE", FileUtil.getAbsolutePath(this.resultFile)); + this.buildEnv.put("BUILD_RESULT_DIR_FILE", buildExtraModule.getResultDirFile()); + // + this.updateStatus(BuildStatus.PubIng, I18nMessageUtil.get("i18n.start_publishing.c0b9")); + if (FileUtil.isEmpty(this.resultFile)) { + String info = I18nMessageUtil.get("i18n.empty_file_or_folder_for_publish.cae8"); + logRecorder.systemError(info); + return info; + } + long resultFileSize = FileUtil.size(this.resultFile); + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_publishing_with_file_size.5039"), FileUtil.readableFileSize(resultFileSize)); + Optional.ofNullable(consumer).ifPresent(consumer1 -> consumer1.accept(resultFileSize)); + // 先同步到文件管理中心 + Boolean syncFileStorage = this.buildExtraModule.getSyncFileStorage(); + if (syncFileStorage != null && syncFileStorage) { + // 处理保留天数 + Integer fileStorageKeepDay = + Optional.ofNullable(this.buildExtraModule.getFileStorageKeepDay()) + .map(integer -> Convert.toInt(buildExtraModule.getFileStorageKeepDay())) + .filter(integer -> integer > 0) + .orElse(null); + String keepMsg = fileStorageKeepDay == null ? StrUtil.EMPTY : StrUtil.format(I18nMessageUtil.get("i18n.retention_days.3c7d"), fileStorageKeepDay); + logRecorder.system(I18nMessageUtil.get("i18n.start_syncing_to_file_management_center.0a03"), keepMsg); + boolean tarGz = this.buildEnv.getBool(BuildUtil.USE_TAR_GZ, false); + File dirPackage = BuildUtil.loadDirPackage(this.buildExtraModule.getId(), this.getRealBuildNumberId(), this.resultFile, tarGz, (unZip, file) -> file); + String string = I18nMessageUtil.get("i18n.build_source.2ef9"); + String successMd5 = fileStorageService.addFile(dirPackage, 1, + buildInfoModel.getWorkspaceId(), + string + buildInfoModel.getName(), + // 默认的别名码为构建id + StrUtil.emptyToDefault(buildInfoModel.getAliasCode(), buildInfoModel.getId()), + fileStorageKeepDay); + if (successMd5 != null) { + logRecorder.system(I18nMessageUtil.get("i18n.build_product_sync_success.f7d1"), successMd5); + } else { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.build_product_file_sync_failed.0e64")); + } + } + // + int releaseMethod = this.buildExtraModule.getReleaseMethod(); + logRecorder.system(I18nMessageUtil.get("i18n.publish_method_format.4622"), BaseEnum.getDescByCode(BuildReleaseMethod.class, releaseMethod)); + + if (releaseMethod == BuildReleaseMethod.Outgiving.getCode()) { + // + this.doOutGiving(); + } else if (releaseMethod == BuildReleaseMethod.Project.getCode()) { + this.doProject(); + } else if (releaseMethod == BuildReleaseMethod.Ssh.getCode()) { + this.doSsh(); + } else if (releaseMethod == BuildReleaseMethod.LocalCommand.getCode()) { + return this.localCommand(); + } else if (releaseMethod == BuildReleaseMethod.DockerImage.getCode()) { + return this.doDockerImage(); + } else if (releaseMethod == BuildReleaseMethod.No.getCode()) { + return null; + } else { + String format = StrUtil.format(I18nMessageUtil.get("i18n.no_implemented_publish_distribution.fcf8"), releaseMethod); + logRecorder.systemError(format); + return format; + } + return null; + } + + /** + * 版本号递增 + * + * @param dockerTagIncrement 是否开启版本号递增 + * @param dockerTag 当前版本号 + * @return 递增后到版本号 + */ + private String dockerTagIncrement(Boolean dockerTagIncrement, String dockerTag) { + if (dockerTagIncrement == null || !dockerTagIncrement) { + return dockerTag; + } + List list = StrUtil.splitTrim(dockerTag, StrUtil.COMMA); + return list.stream() + .map(s -> { + List tag = StrUtil.splitTrim(s, StrUtil.COLON); + String version = CollUtil.getLast(tag); + List versionList = StrUtil.splitTrim(version, StrUtil.DOT); + int tagSize = CollUtil.size(tag); + if (tagSize <= 1 || CollUtil.size(versionList) <= 1) { + logRecorder.systemWarning("version number incrementing error, no match for . or :"); + return s; + } + boolean match = false; + for (int i = versionList.size() - 1; i >= 0; i--) { + String versionParting = versionList.get(i); + int versionPartingInt = Convert.toInt(versionParting, Integer.MIN_VALUE); + if (versionPartingInt != Integer.MIN_VALUE) { + versionList.set(i, this.buildNumberId + StrUtil.EMPTY); + match = true; + break; + } + } + tag.set(tagSize - 1, CollUtil.join(versionList, StrUtil.DOT)); + String newVersion = CollUtil.join(tag, StrUtil.COLON); + if (match) { + logRecorder.system("dockerTag version number incrementing {} -> {}", s, newVersion); + } else { + logRecorder.systemWarning("version number incrementing error,No numeric version number {} ", s); + } + return newVersion; + }) + .collect(Collectors.joining(StrUtil.COMMA)); + } + + private String doDockerImage() { + // 生成临时目录 + File tempPath = FileUtil.file(JpomApplication.getInstance().getTempPath(), "build_temp", "docker_image", this.buildExtraModule.getId() + StrUtil.DASHED + this.buildNumberId); + try { + File sourceFile = BuildUtil.getSourceById(this.buildExtraModule.getId()); + FileUtil.copyContent(sourceFile, tempPath, true); + // 将产物文件 copy 到本地仓库目录 + File historyPackageFile = BuildUtil.getHistoryPackageFile(buildExtraModule.getId(), this.getRealBuildNumberId(), StrUtil.SLASH); + FileUtil.copyContent(historyPackageFile, tempPath, true); + // env file + Map envMap = buildEnv.environment(); + //File envFile = FileUtil.file(tempPath, ".env"); + String dockerTag = StringUtil.formatStrByMap(this.buildExtraModule.getDockerTag(), envMap); + // + dockerTag = this.dockerTagIncrement(this.buildExtraModule.getDockerTagIncrement(), dockerTag); + // docker file + String moduleDockerfile = this.buildExtraModule.getDockerfile(); + List list = StrUtil.splitTrim(moduleDockerfile, StrUtil.COLON); + String dockerFile = CollUtil.getLast(list); + File dockerfile = FileUtil.file(tempPath, dockerFile); + if (!FileUtil.isFile(dockerfile)) { + String format = StrUtil.format(I18nMessageUtil.get("i18n.dockerfile_not_found_in_repository.4168"), dockerFile); + logRecorder.systemError(format); + return format; + } + File baseDir = FileUtil.file(tempPath, list.size() == 1 ? StrUtil.SLASH : CollUtil.get(list, 0)); + // + String fromTag = this.buildExtraModule.getFromTag(); + // 根据 tag 查询 + List dockerInfoModels = dockerInfoService + .queryByTag(this.buildExtraModule.getWorkspaceId(), fromTag); + Map map = machineDockerServer.dockerParameter(dockerInfoModels); + if (map == null) { + String format = StrUtil.format(I18nMessageUtil.get("i18n.no_available_docker_server.6aaa"), fromTag); + logRecorder.systemError(format); + return format; + } + //String dockerBuildArgs = this.buildExtraModule.getDockerBuildArgs(); + for (DockerInfoModel infoModel : dockerInfoModels) { + boolean done = this.doDockerImage(infoModel, envMap, dockerfile, baseDir, dockerTag, this.buildExtraModule); + if (!done) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.container_build_exception.a98f"), infoModel.getName(), dockerTag); + if (buildExtraModule.strictlyEnforce()) { + return I18nMessageUtil.get("i18n.strict_mode_image_build_failure.ecea"); + } + } + } + // 推送 - 只选择一个 docker 服务来推送到远程仓库 + Boolean pushToRepository = this.buildExtraModule.getPushToRepository(); + if (pushToRepository != null && pushToRepository) { + List repositoryList = StrUtil.splitTrim(dockerTag, StrUtil.COMMA); + for (String repositoryItem : repositoryList) { + 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 = logRecorder::info; + map.put("logConsumer", logConsumer); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + try { + plugin.execute("pushImage", map); + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.push_image_container_exception.2090"), e); + } + } + } + // 发布 docker 服务 + this.updateSwarmService(dockerTag, this.buildExtraModule.getDockerSwarmId(), this.buildExtraModule.getDockerSwarmServiceName()); + } finally { + CommandUtil.systemFastDel(tempPath); + } + return null; + } + + private void updateSwarmService(String dockerTag, String swarmId, String serviceName) { + if (StrUtil.isEmpty(swarmId)) { + return; + } + List splitTrim = StrUtil.splitTrim(dockerTag, StrUtil.COMMA); + String first = CollUtil.getFirst(splitTrim); + logRecorder.system("start update swarm service: {} use image {}", serviceName, first); + Map pluginMap = machineDockerServer.dockerParameter(swarmId); + pluginMap.put("serviceId", serviceName); + pluginMap.put("image", first); + try { + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + plugin.execute("updateServiceImage", pluginMap); + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.update_container_service_exception.2249"), e); + throw Lombok.sneakyThrow(e); + } + } + + private boolean doDockerImage(DockerInfoModel dockerInfoModel, Map envMap, File dockerfile, File baseDir, String dockerTag, BuildExtraModule extraModule) { + logRecorder.system("{} start build image {}{}", dockerInfoModel.getName(), dockerTag, System.lineSeparator()); + Map map = machineDockerServer.dockerParameter(dockerInfoModel); + //.toParameter(); + map.put("Dockerfile", dockerfile); + map.put("baseDirectory", baseDir); + // + map.put("tags", dockerTag); + map.put("buildArgs", extraModule.getDockerBuildArgs()); + map.put("pull", extraModule.getDockerBuildPull()); + map.put("noCache", extraModule.getDockerNoCache()); + map.put("labels", extraModule.getDockerImagesLabels()); + map.put("env", envMap); + 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) { + log.error(I18nMessageUtil.get("i18n.build_image_call_container_exception.7e13"), e); + logRecorder.error(I18nMessageUtil.get("i18n.build_image_call_container_exception.7e13"), e); + return false; + } + } + + /** + * 本地命令执行 + */ + private String localCommand() { + // 执行命令 + String releaseCommand = this.buildExtraModule.getReleaseCommand(); + if (StrUtil.isEmpty(releaseCommand)) { + logRecorder.systemError(I18nMessageUtil.get("i18n.no_command_to_execute.340b")); + return null; + } + logRecorder.system("{} start exec{}", DateUtil.now(), System.lineSeparator()); + + File sourceFile = BuildUtil.getSourceById(this.buildExtraModule.getId()); + Map envFileMap = buildEnv.environment(); + + InputStream templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX); + String s1 = IoUtil.readUtf8(templateInputStream); + int waitFor = JpomApplication.getInstance() + .execScript(s1 + releaseCommand, file -> { + try { + 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(I18nMessageUtil.get("i18n.publish_script_exit_code.0f69"), waitFor); + // 判断是否为严格执行 + if (buildExtraModule.strictlyEnforce()) { + return waitFor == 0 ? null : StrUtil.format(I18nMessageUtil.get("i18n.publish_command_non_zero_exit_code.ea80"), waitFor); + } + return null; + } + + /** + * ssh 发布 + */ + private void doSsh() throws IOException { + String releaseMethodDataId = this.buildExtraModule.getReleaseMethodDataId(); + SshService sshService = SpringUtil.getBean(SshService.class); + List strings = StrUtil.splitTrim(releaseMethodDataId, StrUtil.COMMA); + for (String releaseMethodDataIdItem : strings) { + SshModel item = sshService.getByKey(releaseMethodDataIdItem, false); + if (item == null) { + logRecorder.systemError(I18nMessageUtil.get("i18n.no_ssh_entry_found.d0e1"), releaseMethodDataIdItem); + continue; + } + this.doSsh(item, sshService); + } + } + + private void doSsh(SshModel item, SshService sshService) throws IOException { + Map envFileMap = buildEnv.environment(); + MachineSshModel machineSshModel = sshService.getMachineSshModel(item); + Session session = null; + ChannelSftp channelSftp = null; + try { + session = sshService.getSessionByModel(machineSshModel); + Charset charset = machineSshModel.charset(); + int timeout = machineSshModel.timeout(); + String releasePath = this.buildExtraModule.getReleasePath(); + envFileMap.put("SSH_RELEASE_PATH", releasePath); + // 执行发布前命令 + if (StrUtil.isNotEmpty(this.buildExtraModule.getReleaseBeforeCommand())) { + // + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_pre_release_command.6c7e"), item.getName()); + JschUtils.execCallbackLine(session, charset, timeout, this.buildExtraModule.getReleaseBeforeCommand(), StrUtil.EMPTY, envFileMap, logRecorder::info); + } + + if (StrUtil.isEmpty(releasePath)) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.publish_directory_is_empty.79c6")); + } else { + logRecorder.system("{} {} start ftp upload{}", DateUtil.now(), item.getName(), System.lineSeparator()); + MySftp.ProgressMonitor sftpProgressMonitor = sshService.createProgressMonitor(logRecorder); + MySftp sftp = new MySftp(session, charset, timeout, sftpProgressMonitor); + channelSftp = sftp.getClient(); + String prefix = ""; + if (!StrUtil.startWith(releasePath, StrUtil.SLASH)) { + prefix = sftp.pwd(); + } + String normalizePath = FileUtil.normalize(prefix + StrUtil.SLASH + releasePath); + if (this.buildExtraModule.isClearOld()) { + try { + if (sftp.exist(normalizePath)) { + sftp.delDir(normalizePath); + } + } catch (Exception e) { + if (!StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")) { + logRecorder.error(I18nMessageUtil.get("i18n.clear_build_product_failed.edd4"), e); + } + } + } + sftp.syncUpload(this.resultFile, normalizePath); + logRecorder.system("{} ftp upload done", item.getName()); + } + // 执行发布后命令 + if (StrUtil.isEmpty(this.buildExtraModule.getReleaseCommand())) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.no_ssh_commands_to_execute_after_publish.89ba")); + return; + } + // + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_post_release_command.fd06"), item.getName()); + JschUtils.execCallbackLine(session, charset, timeout, this.buildExtraModule.getReleaseCommand(), StrUtil.EMPTY, envFileMap, logRecorder::info); + } finally { + JschUtil.close(channelSftp); + JschUtil.close(session); + } + } + + /** + * 差异上传发布 + * + * @param nodeModel 节点 + * @param projectId 项目ID + * @param afterOpt 发布后的操作 + */ + private void diffSyncProject(NodeModel nodeModel, String projectId, AfterOpt afterOpt, boolean clearOld) { + File resultFile = this.resultFile; + String resultFileParent = resultFile.isFile() ? + FileUtil.getAbsolutePath(resultFile.getParent()) : FileUtil.getAbsolutePath(this.resultFile); + // + List files = FileUtil.loopFiles(resultFile); + List collect = files.stream().map(file -> { + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", StringUtil.delStartPath(file, resultFileParent, true)); + jsonObject.put("sha1", SecureUtil.sha1(file)); + return jsonObject; + }).collect(Collectors.toList()); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", projectId); + jsonObject.put("data", collect); + String directory = this.buildExtraModule.getProjectSecondaryDirectory(); + directory = Opt.ofBlankAble(directory).orElse(StrUtil.SLASH); + jsonObject.put("dir", directory); + JsonMessage requestBody = NodeForward.requestBody(nodeModel, NodeUrl.MANAGE_FILE_DIFF_FILE, jsonObject); + Assert.state(requestBody.success(), I18nMessageUtil.get("i18n.compare_project_failure.e6ab") + requestBody); + + JSONObject data = requestBody.getData(); + JSONArray diff = data.getJSONArray("diff"); + JSONArray del = data.getJSONArray("del"); + int delSize = CollUtil.size(del); + int diffSize = CollUtil.size(diff); + if (clearOld) { + logRecorder.system(I18nMessageUtil.get("i18n.compare_files_result_with_delete.033d"), CollUtil.size(collect), CollUtil.size(diff), delSize); + } else { + logRecorder.system(I18nMessageUtil.get("i18n.compare_files_result.bec4"), CollUtil.size(collect), CollUtil.size(diff)); + } + // 清空发布才先执行删除 + if (delSize > 0 && clearOld) { + jsonObject.put("data", del); + requestBody = NodeForward.requestBody(nodeModel, NodeUrl.MANAGE_FILE_BATCH_DELETE, jsonObject); + Assert.state(requestBody.success(), I18nMessageUtil.get("i18n.delete_project_file_failure_with_full_stop.85b8") + requestBody); + } + for (int i = 0; i < diffSize; i++) { + boolean last = (i == diffSize - 1); + JSONObject diffData = (JSONObject) diff.get(i); + String name = diffData.getString("name"); + File file = FileUtil.file(resultFileParent, name); + // + String startPath = StringUtil.delStartPath(file, resultFileParent, false); + startPath = FileUtil.normalize(startPath + StrUtil.SLASH + directory); + // + Set progressRangeList = ConcurrentHashMap.newKeySet((int) Math.floor((float) 100 / buildExtConfig.getLogReduceProgressRatio())); + int finalI = i; + JsonMessage jsonMessage = OutGivingRun.fileUpload(file, startPath, + projectId, false, last ? afterOpt : AfterOpt.No, nodeModel, false, + this.buildExtraModule.getProjectUploadCloseFirst(), (total, progressSize) -> { + double progressPercentage = Math.floor(((float) progressSize / total) * 100); + int progressRange = (int) Math.floor(progressPercentage / buildExtConfig.getLogReduceProgressRatio()); + if (progressRangeList.add(progressRange)) { + // total, progressSize + String info = I18nMessageUtil.get("i18n.upload_progress_message_format.b91c"); + logRecorder.system(info, file.getName(), + (finalI + 1), diffSize, + FileUtil.readableFileSize(progressSize), FileUtil.readableFileSize(total), + NumberUtil.formatPercent(((float) progressSize / total), 0) + ); + } + }); + Assert.state(jsonMessage.success(), I18nMessageUtil.get("i18n.synchronize_project_files_failed.6aa4") + jsonMessage); + if (last) { + // 最后一个 + logRecorder.system(I18nMessageUtil.get("i18n.publish_project_package_success.b0ce"), jsonMessage); + } + } + } + + /** + * 发布项目 + */ + private void doProject() { + //AfterOpt afterOpt, boolean clearOld, boolean diffSync + AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, this.buildExtraModule.getAfterOpt(), AfterOpt.No); + boolean clearOld = this.buildExtraModule.isClearOld(); + boolean diffSync = this.buildExtraModule.isDiffSync(); + String releaseMethodDataId = this.buildExtraModule.getReleaseMethodDataId(); + String[] strings = StrUtil.splitToArray(releaseMethodDataId, CharPool.COLON); + if (ArrayUtil.length(strings) != 2) { + throw new IllegalArgumentException(releaseMethodDataId + " error"); + } + NodeService nodeService = SpringUtil.getBean(NodeService.class); + NodeModel nodeModel = nodeService.getByKey(strings[0]); + Objects.requireNonNull(nodeModel, I18nMessageUtil.get("i18n.node_does_not_exist.4ce4")); + String projectId = strings[1]; + if (diffSync) { + this.diffSyncProject(nodeModel, projectId, afterOpt, clearOld); + return; + } + boolean tarGz = this.buildEnv.getBool(BuildUtil.USE_TAR_GZ, false); + JsonMessage jsonMessage = BuildUtil.loadDirPackage(this.buildExtraModule.getId(), this.getRealBuildNumberId(), this.resultFile, tarGz, (unZip, zipFile) -> { + String name = zipFile.getName(); + Set progressRangeList = ConcurrentHashMap.newKeySet((int) Math.floor((float) 100 / buildExtConfig.getLogReduceProgressRatio())); + return OutGivingRun.fileUpload(zipFile, + this.buildExtraModule.getProjectSecondaryDirectory(), + projectId, + unZip, + afterOpt, + nodeModel, clearOld, this.buildExtraModule.getProjectUploadCloseFirst(), (total, progressSize) -> { + double progressPercentage = Math.floor(((float) progressSize / total) * 100); + int progressRange = (int) Math.floor(progressPercentage / buildExtConfig.getLogReduceProgressRatio()); + if (progressRangeList.add(progressRange)) { + logRecorder.system(I18nMessageUtil.get("i18n.upload_progress_with_colon.dd5b"), name, + FileUtil.readableFileSize(progressSize), FileUtil.readableFileSize(total), + NumberUtil.formatPercent(((float) progressSize / total), 0)); + } + }); + }); + if (jsonMessage.success()) { + logRecorder.system(I18nMessageUtil.get("i18n.publish_project_package_success.b0ce"), jsonMessage); + } else { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.publish_project_package_failed.9514") + jsonMessage); + } + } + + /** + * 分发包 + */ + private void doOutGiving() throws ExecutionException, InterruptedException { + String releaseMethodDataId = this.buildExtraModule.getReleaseMethodDataId(); + String projectSecondaryDirectory = this.buildExtraModule.getProjectSecondaryDirectory(); + // + String selectProject = buildEnv.get("dispatchSelectProject"); + boolean tarGz = buildEnv.getBool(BuildUtil.USE_TAR_GZ, false); + Future statusFuture = BuildUtil.loadDirPackage(this.buildExtraModule.getId(), this.getRealBuildNumberId(), this.resultFile, tarGz, (unZip, zipFile) -> { + OutGivingRun.OutGivingRunBuilder outGivingRunBuilder = OutGivingRun.builder() + .id(releaseMethodDataId) + .file(zipFile) + .logRecorder(logRecorder) + .userModel(userModel) + .mode("build-trigger") + .modeData(buildExtraModule.getId()) + .unzip(unZip) + // 由构建配置决定是否删除 + .doneDeleteFile(false) + .projectSecondaryDirectory(projectSecondaryDirectory) + .stripComponents(0); + return outGivingRunBuilder.build().startRun(selectProject); + }); + //OutGivingRun.startRun(releaseMethodDataId, zipFile, userModel, unZip, 0); + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_distribution_package.a2cc")); + OutGivingModel.Status status = statusFuture.get(); + logRecorder.system(I18nMessageUtil.get("i18n.distribute_result.a230"), status.getDesc()); + } + + /** + * 回滚 + * + * @param item 构建对象 + */ + public void rollback(BuildInfoModel item) { + try { + BaseServerController.resetInfo(userModel); + this.init(); + // + buildEnv.eachStr(logRecorder::system); + logRecorder.system(I18nMessageUtil.get("i18n.start_rolling_back.f020"), DateTime.now()); + // + String errorMsg = this.start(null, item); + String emptied = StrUtil.emptyToDefault(errorMsg, "ok"); + logRecorder.system(I18nMessageUtil.get("i18n.rollback_ended.fb1d"), emptied); + if (errorMsg == null) { + this.updateStatus(BuildStatus.PubSuccess, I18nMessageUtil.get("i18n.publish_success.2fff")); + } else { + this.updateStatus(BuildStatus.PubError, errorMsg); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.publish_exception.cf0b"), e); + logRecorder.error(I18nMessageUtil.get("i18n.publish_exception.cf0b"), e); + this.updateStatus(BuildStatus.PubError, e.getMessage()); + } finally { + IoUtil.close(this.logRecorder); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/ResultDirFileAction.java b/modules/server/src/main/java/org/dromara/jpom/build/ResultDirFileAction.java new file mode 100644 index 0000000000..492dcd0f60 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/ResultDirFileAction.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.AntPathUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.util.Assert; + +import java.io.File; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/2/8 + */ +@Data +public class ResultDirFileAction { + /** + * 配置的产物路径(或者 ant 表达式) + */ + private String path; + + /** + * 产物匹配模式 + */ + private Type type; + + /** + * ant 文件上传模式 + */ + private AntFileUploadMode antFileUploadMode; + + /** + * ant 使用指定路径下的文件 + */ + private String antSubMatch; + + public String antSubMatch() { + if (StrUtil.isEmpty(this.antSubMatch)) { + // 兼容默认数据,未配置 + return StrUtil.EMPTY; + } + String normalize = FileUtil.normalize(this.antSubMatch); + //需要包裹成目录结构 + return StrUtil.wrapIfMissing(normalize, StrUtil.SLASH, StrUtil.SLASH); + } + + public ResultDirFileAction(String resultDirFile) { + // 存在路径 表达式 + if (StrUtil.contains(resultDirFile, StrUtil.COLON)) { + List resultDirFiles = StrUtil.splitTrim(resultDirFile, StrUtil.COLON); + String first = CollUtil.getFirst(resultDirFiles); + this.setPath(first); + this.setType(AntPathUtil.ANT_PATH_MATCHER.isPattern(first) ? Type.ANT_PATH : Type.ORIGINAL); + if (this.getType() == Type.ANT_PATH) { + // 文件保留方式 + String antFileUploadModeStr = CollUtil.get(resultDirFiles, 1); + antFileUploadModeStr = StrUtil.nullToDefault(antFileUploadModeStr, StrUtil.EMPTY).toUpperCase(); + AntFileUploadMode fileUploadMode = EnumUtil.fromString(AntFileUploadMode.class, antFileUploadModeStr, AntFileUploadMode.KEEP_DIR); + this.setAntFileUploadMode(fileUploadMode); + // ant 使用二级路径 + String antFileUploadPath = CollUtil.get(resultDirFiles, 2); + this.setAntSubMatch(StrUtil.nullToDefault(antFileUploadPath, StrUtil.EMPTY)); + } + } else { + this.setPath(resultDirFile); + this.setType(AntPathUtil.ANT_PATH_MATCHER.isPattern(resultDirFile) ? Type.ANT_PATH : Type.ORIGINAL); + if (this.getType() == Type.ANT_PATH) { + this.setAntFileUploadMode(AntFileUploadMode.KEEP_DIR); + this.setAntSubMatch(StrUtil.EMPTY); + } + } + } + + /** + * ant 模式使用 normalize 方法格式化不规范的路径 + * + * @see AntPathUtil#antPathMatcher(File, String) + */ + public void check() { + if (this.getType() == Type.ORIGINAL) { + FileUtils.checkSlip(getPath(), e -> new IllegalArgumentException(I18nMessageUtil.get("i18n.product_directory_cannot_skip_levels.3ad4") + e.getMessage())); + } else if (this.getType() == Type.ANT_PATH) { + // ant 模式存在特殊字符,直接判断会发生异常并且判断不到 + } + } + + /** + * 解析产物路径 + * + * @param resultDirFile 产物配置 + * @return ResultDirFileAction + */ + public static ResultDirFileAction parse(String resultDirFile) { + Assert.notNull(resultDirFile, I18nMessageUtil.get("i18n.result_dir_file_required.5f02")); + return new ResultDirFileAction(resultDirFile); + } + + public enum AntFileUploadMode { + /** + * 保留文件夹层级 + */ + KEEP_DIR, + /** + * 将所有文件合并到同一个文件夹 + */ + SAME_DIR, + } + + public enum Type { + /** + * 模糊匹配模式 + */ + ANT_PATH, + /** + * 原始目录 + */ + ORIGINAL + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/TaskData.java b/modules/server/src/main/java/org/dromara/jpom/build/TaskData.java new file mode 100644 index 0000000000..7dbcbda2e4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/TaskData.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build; + +import lombok.Builder; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.data.RepositoryModel; +import org.dromara.jpom.model.user.UserModel; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +@Builder +public class TaskData { + + protected final BuildInfoModel buildInfoModel; + protected final RepositoryModel repositoryModel; + protected final UserModel userModel; + /** + * 延迟执行的时间(单位秒) + */ + protected final Integer delay; + /** + * 触发类型 + * 0: "手动", + * 1: "触发器", + * 2: "定时", + */ + protected final int triggerBuildType; + /** + * 构建备注 + */ + protected String buildRemark; + /** + * 环境变量 + * 工作空间环境变量 + */ + protected EnvironmentMapBuilder environmentMapBuilder; + + /** + * 仓库代码最后一次变动信息(ID,git 为 commit hash, svn 最后的版本号) + */ + protected String repositoryLastCommitId; + /** + * 是否差异构建 + */ + protected Boolean checkRepositoryDiff; + /** + * 产物文件大小 + */ + protected Long resultFileSize; + + protected Map dockerParameter; + + protected String buildContainerId; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/build/pipeline/model/PipelineConfig.java b/modules/server/src/main/java/org/dromara/jpom/build/pipeline/model/PipelineConfig.java new file mode 100644 index 0000000000..37ebb3190f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/build/pipeline/model/PipelineConfig.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.build.pipeline.model; + +import lombok.Data; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.data.BuildInfoModel; + +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2024/4/7 + */ +@Data +public class PipelineConfig { + + private Map repositories; + + private List stages; + + public interface IStage { + StageType getStageType(); + } + + public enum StageType { + EXEC, + PUBLISH + } + + @Data + public static class Publish implements IStage { + /** + * 阶段类型 + */ + private StageType stageType; + /** + * 执行描述 + */ + private String desc; + /** + * 执行的脚本 + */ + private String commands; + } + + @Data + public static class BaseStage implements IStage { + /** + * 阶段类型 + */ + private StageType stageType; + /** + * 执行的目录 + *

+ * 仓库的标记 + * + * @see PipelineConfig#getRepositories() + */ + private String repoTag; + } + + @Data + public static class PublishByProject { + + private String nodeId; + + private String projectId; + + private String projectSecondaryDirectory; + + /** + * 保存项目文件前先关闭 + */ + private Boolean projectUploadCloseFirst; + + /** + * 分发后的操作 + * 仅在项目发布类型生效 + * + * @see AfterOpt + * @see BuildInfoModel#getExtraData() + */ + private int afterOpt; + } + + @Data + public static class ExecCommand implements IStage { + /** + * 阶段类型 + */ + private StageType stageType; + /** + * 执行描述 + */ + private String desc; + /** + * 执行的脚本 + */ + private String commands; + /** + * 环境变量 + */ + private Map env; + + /** + * 脚本执行超时时间 + */ + private Integer timeout; + + /** + * 产物 + */ + private List artifacts; + } + + @Data + private static class ArtifactItem { + private String id; + + private List path; + } + + @Data + public static class Repository { + /** + * 仓库ID + */ + private String repositoryId; + /** + * 分支 + */ + private String branchName; + /** + * 标签 + */ + private String branchTagName; + /** + * 克隆深度 + */ + private Integer cloneDepth; + /** + * 工作目录 + */ + private String workPath; + } +} 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 new file mode 100644 index 0000000000..0cd881635d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/BaseServerController.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.LoginInterceptor; +import org.dromara.jpom.common.interceptor.PermissionInterceptor; +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.service.node.NodeService; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * Jpom server 端 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +public abstract class BaseServerController extends BaseJpomController { + private static final ThreadLocal USER_MODEL_THREAD_LOCAL = new ThreadLocal<>(); + public static final Cache SHARDING_IDS = CacheUtil.newLRUCache(10, TimeUnit.DAYS.toMillis(1)); + public static final String NODE_ID = "nodeId"; + + @Resource + protected NodeService nodeService; + @Resource + protected MachineNodeServer machineNodeServer; + + protected NodeModel getNode() { + NodeModel nodeModel = tryGetNode(); + Assert.notNull(nodeModel, I18nMessageUtil.get("i18n.incorrect_node_info_node_does_not_exist.2fd8")); + return nodeModel; + } + + protected NodeModel tryGetNode() { + HttpServletRequest request = getRequest(); + return (NodeModel) request.getAttribute("node"); + } + + /** + * 判断是否传入机器 id + * + * @param machineId 机器id + * @param request 请求 + * @param nodeUrl 节点 url + * @param pars 参数 + * @param 泛型 + * @return data + */ + protected JsonMessage tryRequestMachine(String machineId, HttpServletRequest request, NodeUrl nodeUrl, String... pars) { + if (StrUtil.isNotEmpty(machineId)) { + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, nodeUrl, new String[]{}, pars); + } + 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, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, nodeUrl, new String[]{}, pars); + } + return null; + } + + /** + * 验证 cron 表达式, demo 账号不能开启 cron + * + * @param cron cron + * @return 原样返回 + */ + protected String checkCron(String cron) { + return StringUtil.checkCron(cron, s -> { + UserModel user = getUser(); + Assert.state(!user.isDemoUser(), PermissionInterceptor.DEMO_TIP); + return s; + }); + } + + /** + * 为线程设置 用户 + * + * @param userModel 用户 + */ + public static void resetInfo(UserModel userModel) { + UserModel userModel1 = USER_MODEL_THREAD_LOCAL.get(); + if (userModel1 != null && userModel == UserModel.EMPTY) { + // 已经存在,更新为 empty 、跳过 + return; + } + USER_MODEL_THREAD_LOCAL.set(userModel); + } + + protected UserModel getUser() { + UserModel userByThreadLocal = getUserByThreadLocal(); + Assert.notNull(userByThreadLocal, ServerConst.AUTHORIZE_TIME_OUT_CODE + StrUtil.EMPTY); + return userByThreadLocal; + } + + /** + * 从线程 缓存中获取 用户信息 + * + * @return 用户 + */ + public static UserModel getUserByThreadLocal() { + return Optional.ofNullable(USER_MODEL_THREAD_LOCAL.get()).orElseGet(BaseServerController::getUserModel); +// return ; + } + + public static void removeAll() { + USER_MODEL_THREAD_LOCAL.remove(); + } + + /** + * 只清理 是 empty 对象 + */ + public static void removeEmpty() { + UserModel userModel = USER_MODEL_THREAD_LOCAL.get(); + if (userModel == UserModel.EMPTY) { + USER_MODEL_THREAD_LOCAL.remove(); + } + } + + public static UserModel getUserModel() { + ServletRequestAttributes servletRequestAttributes = tryGetRequestAttributes(); + if (servletRequestAttributes == null) { + return null; + } + return (UserModel) servletRequestAttributes.getAttribute(LoginInterceptor.SESSION_NAME, RequestAttributes.SCOPE_SESSION); + } + + @Override + public void uploadSharding(MultipartFile file, String tempPath, String sliceId, Integer totalSlice, Integer nowSlice, String fileSumMd5, String... extNames) throws IOException { + Assert.state(BaseServerController.SHARDING_IDS.containsKey(sliceId), I18nMessageUtil.get("i18n.invalid_shard_id.46fd")); + super.uploadSharding(file, tempPath, sliceId, totalSlice, nowSlice, fileSumMd5, extNames); + } + + @Override + public File shardingTryMerge(String tempPath, String sliceId, Integer totalSlice, String fileSumMd5) throws IOException { + Assert.state(BaseServerController.SHARDING_IDS.containsKey(sliceId), I18nMessageUtil.get("i18n.invalid_shard_id.46fd")); + try { + return super.shardingTryMerge(tempPath, sliceId, totalSlice, fileSumMd5); + } finally { + // 判断-删除分片id + BaseServerController.SHARDING_IDS.remove(sliceId); + } + } +} 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 new file mode 100644 index 0000000000..ee14ef6f91 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/GlobalDefaultExceptionHandler.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.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; + +import javax.servlet.http.HttpServletRequest; + +/** + * 全局异常处理 + * + * @author bwcx_jzy + * @since 2019/04/17 + */ +@RestControllerAdvice +@Slf4j +public class GlobalDefaultExceptionHandler extends BaseExceptionHandler { + + /** + * 声明要捕获的异常 + * + * @param e 异常 + */ + @ExceptionHandler({AgentAuthorizeException.class}) + public IJsonMessage delExceptionHandler(AgentAuthorizeException e) { + return e.getJsonMessage(); + } + + /** + * 插件端异常 + *

+ * 避免重复记录堆栈 + * + * @param request 请求 + * @param e 异常 + * @author jzy + * @since 2021-08-01 + */ + @ExceptionHandler({AgentException.class, TransportAgentException.class}) + public IJsonMessage agentExceptionHandler(HttpServletRequest request, AgentException e) { + Throwable cause = e.getCause(); + if (cause != null) { + log.error("controller {}", request.getRequestURI(), cause); + } + 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 new file mode 100644 index 0000000000..3cdcc385bc --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/ServerConst.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.util.function.Supplier; + +/** + * @author bwcx_jzy + * @since 2022/8/30 + */ +public class ServerConst extends Const { + + /** + * h2 数据库表名字段 + */ + public static final String TABLE_NAME = "TABLE_NAME"; + + /** + * id_rsa + */ + public static final String ID_RSA = "_id_rsa"; + /** + * sshkey + */ + public static final String SSH_KEY = "sshkey"; + /** + * 引用工作空间环境变量的前缀 + */ + public static final String REF_WORKSPACE_ENV = "$ref.wEnv."; + /** + * 引用工作脚本模板的前缀 + */ + public static final String REF_SCRIPT = "$ref.script."; + + public static final String PROXY_PATH = "Jpom-ProxyPath"; + + /** + * 分发包存储路径 + */ + public static final String OUTGIVING_FILE = "outgiving"; + /** + * token自动续签状态码 + */ + public static final int RENEWAL_AUTHORIZE_CODE = 801; + + /** + * token 失效 + */ + public static final int AUTHORIZE_TIME_OUT_CODE = 800; + + /** + * 账号被锁定 + */ + public static final int ACCOUNT_LOCKED = 802; + public static final Supplier LOGIN_TIP = () -> I18nMessageUtil.get("i18n.login_info_expired_re_login.6bc4"); + public static final Supplier ACCOUNT_LOCKED_TIP = () -> I18nMessageUtil.get("i18n.account_disabled.9361"); + + 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/UrlRedirectUtil.java b/modules/server/src/main/java/org/dromara/jpom/common/UrlRedirectUtil.java new file mode 100644 index 0000000000..2574de655c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/UrlRedirectUtil.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.function.Function; + +/** + * url 重定向 + * 配置nginx 代理实现 + * + * @author bwcx_jzy + * @since 2019/11/14 + */ +public class UrlRedirectUtil { + +// /** +// * 获取 protocol 协议完全跳转 +// * +// * @param request 请求 +// * @param url 跳转url +// * @see javax.servlet.http.HttpUtils#getRequestURL +// */ +// public static String getRedirect(HttpServletRequest request, String url) { +// int port = getPort(request); +// return getRedirect(request, url, port); +// } + +// /** +// * 获取 protocol 协议完全跳转 +// * +// * @param request 请求 +// * @param url 跳转url +// * @see javax.servlet.http.HttpUtils#getRequestURL +// */ +// public static String getRedirect(HttpServletRequest request, String url, int port) { +// String proto = ServletUtil.getHeaderIgnoreCase(request, "X-Forwarded-Proto"); +// if (proto == null) { +// return url; +// } else { +// String host = request.getHeader(HttpHeaders.HOST); +// if (StrUtil.isEmpty(host)) { +// throw new RuntimeException("请配置host header"); +// } +// if ("http".equals(proto) && port == 0) { +// port = 80; +// } else if ("https".equals(proto) && port == 0) { +// port = 443; +// } +// String format = StrUtil.format("{}://{}:{}{}", proto, host, port, url); +// return URLUtil.normalize(format); +// } +// } + +// /** +// * 获取 protocol 协议完全跳转 +// * +// * @param request 请求 +// * @param response 响应 +// * @param url 跳转url +// * @throws IOException io +// * @see javax.servlet.http.HttpUtils#getRequestURL +// */ +// public static void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url, int port) throws IOException { +// String toUrl = getRedirect(request, url, port); +// response.sendRedirect(toUrl); +// } + +// +// /** +// * 获取 protocol 协议完全跳转 +// * +// * @param request 请求 +// * @param response 响应 +// * @param url 跳转url +// * @throws IOException io +// * @see javax.servlet.http.HttpUtils#getRequestURL +// */ +// public static void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException { +// int port = getPort(request); +// sendRedirect(request, response, url, port); +// } + + private static int getPort(HttpServletRequest request) { + String proxyPort = ServletUtil.getHeaderIgnoreCase(request, "X-Forwarded-Port"); + int port = 0; + if (StrUtil.isNotEmpty(proxyPort)) { + port = Integer.parseInt(proxyPort); + } + return port; + } + + /** + * 二级代理路径 + * + * @param request req + * @return context-path+nginx配置 + */ + public static String getHeaderProxyPath(HttpServletRequest request, String headName) { + return getHeaderProxyPath(request, headName, null); + } + + /** + * 二级代理路径 + * + * @param request req + * @return context-path+nginx配置 + */ + public static String getHeaderProxyPath(HttpServletRequest request, String headName, Function function) { + String proxyPath = ServletUtil.getHeaderIgnoreCase(request, headName); + // + if (StrUtil.isEmpty(proxyPath)) { + return request.getContextPath(); + } + // 回调处理 + if (function != null) { + proxyPath = function.apply(proxyPath); + } + // + proxyPath = FileUtil.normalize(request.getContextPath() + StrUtil.SLASH + proxyPath); + if (proxyPath.endsWith(StrUtil.SLASH)) { + proxyPath = proxyPath.substring(0, proxyPath.length() - 1); + } + return proxyPath; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/common/WebConfigurer.java b/modules/server/src/main/java/org/dromara/jpom/common/WebConfigurer.java new file mode 100644 index 0000000000..a9de523579 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/WebConfigurer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common; + +import org.dromara.jpom.common.interceptor.IpInterceptor; +import org.dromara.jpom.common.interceptor.LoginInterceptor; +import org.dromara.jpom.common.interceptor.PermissionInterceptor; +import org.dromara.jpom.common.validator.ParameterInterceptor; +import org.springframework.boot.web.server.MimeMappings; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; + +/** + * @author bwcx_jzy + * @since 2022/12/8 + */ +@Configuration +public class WebConfigurer implements WebMvcConfigurer, WebServerFactoryCustomizer { + + @Resource + private ParameterInterceptor parameterInterceptor; + @Resource + private IpInterceptor ipInterceptor; + @Resource + private LoginInterceptor loginInterceptor; + @Resource + private PermissionInterceptor permissionInterceptor; + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(ipInterceptor).excludePathPatterns(ServerOpenApi.API + "**"); + registry.addInterceptor(loginInterceptor).excludePathPatterns(ServerOpenApi.API + "**"); + registry.addInterceptor(parameterInterceptor).addPathPatterns("/**"); + registry.addInterceptor(permissionInterceptor).excludePathPatterns(ServerOpenApi.API + "**"); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // registry.addResourceHandler("/css/**").addResourceLocations("classpath:/dist/css/"); + // registry.addResourceHandler("/js/**").addResourceLocations("classpath:/dist/js/"); + // registry.addResourceHandler("/img/**").addResourceLocations("classpath:/dist/img/"); + // registry.addResourceHandler("/fonts/**").addResourceLocations("classpath:/dist/fonts/"); + } + + @Override + public void customize(ConfigurableServletWebServerFactory factory) { + MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT); + mappings.add("js", "application/javascript;charset=utf-8"); + factory.setMimeMappings(mappings); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/common/forward/DefaultUrlItem.java b/modules/server/src/main/java/org/dromara/jpom/common/forward/DefaultUrlItem.java new file mode 100644 index 0000000000..4f969072c6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/DefaultUrlItem.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.forward; + +import cn.hutool.core.lang.Opt; +import cn.hutool.extra.spring.SpringUtil; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.configuration.NodeConfig; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.transport.DataContentType; +import org.dromara.jpom.transport.IUrlItem; + +import java.util.Map; +import java.util.Optional; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +public class DefaultUrlItem implements IUrlItem { + private final NodeUrl nodeUrl; + private final Integer timeout; + private final String workspaceId; + private final DataContentType dataContentType; + private final Map header; + + public DefaultUrlItem(NodeUrl nodeUrl, Integer timeout, String workspaceId, DataContentType dataContentType, Map header) { + this.nodeUrl = nodeUrl; + this.timeout = timeout; + this.workspaceId = workspaceId; + this.dataContentType = dataContentType; + this.header = header; + } + + @Override + public String path() { + return nodeUrl.getUrl(); + } + + @Override + public Integer timeout() { + if (nodeUrl.isFileTimeout()) { + ServerConfig serverConfig = SpringUtil.getBean(ServerConfig.class); + NodeConfig configNode = serverConfig.getNode(); + return configNode.getUploadFileTimeout(); + } else { + return Optional.of(nodeUrl.getTimeout()) + .flatMap(timeOut -> { + if (timeOut == 0) { + // 读取节点配置的超时时间 + return Optional.ofNullable(timeout); + } + // 值 < 0 url 指定不超时 + return timeOut > 0 ? Optional.of(timeOut) : Optional.empty(); + }) + .map(timeOut -> { + if (timeOut <= 0) { + return null; + } + // 超时时间不能小于 2 秒 + return Math.max(timeOut, 2); + }) + .orElse(null); + } + } + + @Override + public String workspaceId() { + return Opt.ofBlankAble(workspaceId).orElse(Const.WORKSPACE_DEFAULT_ID); + } + + @Override + public DataContentType contentType() { + return dataContentType; + } + + @Override + public Map header() { + return header; + } +} 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 new file mode 100644 index 0000000000..233b76d21f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/JsonMessageTransformServer.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.forward; + +import cn.hutool.core.exceptions.ExceptionUtil; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.exception.AgentException; +import org.dromara.jpom.transport.INodeInfo; +import org.dromara.jpom.transport.TransformServer; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.NoRouteToHostException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.List; + +/** + * json 消息转换 + * + * @author bwcx_jzy + * @since 2022/12/24 + */ +@Slf4j +public class JsonMessageTransformServer implements TransformServer { + + @Override + public T transform(String data, TypeReference tTypeReference) { + return NodeForward.toJsonMessage(data, tTypeReference); + } + + @Override + public T transformOnlyData(String data, Class tClass) { + JsonMessage transform = this.transform(data, new TypeReference>() { + }); + return transform.getData(tClass); + } + + @Override + public Exception transformException(Exception exception, INodeInfo nodeModel) { + if (exception instanceof NullPointerException) { + log.error(I18nMessageUtil.get("i18n.node_null_pointer_exception.76fe"), nodeModel.name(), exception); + return new AgentException(nodeModel.name() + I18nMessageUtil.get("i18n.node_exception_null_pointer.d408")); + } + String message = exception.getMessage(); + log.error("node [{}] connect failed...message: [{}]", nodeModel.name(), message); + List throwableList = ExceptionUtil.getThrowableList(exception); + for (Throwable throwable : throwableList) { + if (throwable instanceof ConnectException || throwable instanceof SocketTimeoutException) { + return new AgentException(nodeModel.name() + I18nMessageUtil.get("i18n.node_network_connection_exception_or_timeout.5904") + + I18nMessageUtil.get("i18n.port_configuration_check.d888") + + I18nMessageUtil.get("i18n.cloud_server_network_issues.a865") + message); + } + if (throwable instanceof UnknownHostException) { + return new AgentException(nodeModel.name() + I18nMessageUtil.get("i18n.unable_to_access_node_network.4e09") + message); + } + if (throwable instanceof NoRouteToHostException) { + return new AgentException(nodeModel.name() + I18nMessageUtil.get("i18n.node_communication_failure_signal.5aae") + message); + } + if (throwable instanceof IOException && StrUtil.containsIgnoreCase(message, "Error writing to server")) { + return new AgentException(nodeModel.name() + I18nMessageUtil.get("i18n.node_communication_failure.00fb") + message); + } + } + return new AgentException(nodeModel.name() + I18nMessageUtil.get("i18n.node_exception.bca7") + message); + } +} 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 new file mode 100644 index 0000000000..2dfefeaf45 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeForward.java @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.forward; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.resource.BytesResource; +import cn.hutool.core.io.unit.DataSize; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.BaseIdModel; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.ServerConfig; +import org.dromara.jpom.transport.*; +import org.dromara.jpom.util.StrictSyncFinisher; +import org.springframework.http.HttpHeaders; +import org.springframework.util.Assert; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * 节点请求转发 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Slf4j +public class NodeForward { + + /** + * 创建代理 + * + * @param type 代理类型 + * @param httpProxy 代理地址 + * @return proxy + */ + public static Proxy crateProxy(String type, String httpProxy) { + if (StrUtil.isNotEmpty(httpProxy)) { + List split = StrUtil.splitTrim(httpProxy, StrUtil.COLON); + String host = CollUtil.getFirst(split); + int port = Convert.toInt(CollUtil.getLast(split), 0); + Proxy.Type type1 = EnumUtil.fromString(Proxy.Type.class, type, Proxy.Type.HTTP); + return new Proxy(type1, new InetSocketAddress(host, port)); + } + return null; + } + + public static INodeInfo parseNodeInfo(NodeModel nodeModel) { + Assert.hasText(nodeModel.getMachineId(), I18nMessageUtil.get("i18n.incomplete_node_info_missing_machine_id.1c9a")); + MachineNodeServer machineNodeServer = SpringUtil.getBean(MachineNodeServer.class); + MachineNodeModel model = machineNodeServer.getByKey(nodeModel.getMachineId(), false); + Assert.notNull(model, I18nMessageUtil.get("i18n.machine_info_not_exist.3468")); + return model; + } + + public static INodeInfo coverNodeInfo(MachineNodeModel machineNodeModel) { + if (StrUtil.isEmpty(machineNodeModel.getId())) { + // 新增的情况 + return machineNodeModel; + } + MachineNodeServer machineNodeServer = SpringUtil.getBean(MachineNodeServer.class); + MachineNodeModel model = machineNodeServer.getByKey(machineNodeModel.getId(), false); + Optional.ofNullable(model) + .ifPresent(exits -> { + String password = Opt.ofBlankAble(machineNodeModel.getJpomPassword()).orElse(exits.getJpomPassword()); + machineNodeModel.setJpomPassword(password); + }); + return machineNodeModel; + } + + /** + * 创建节点 url + * + * @param iNodeInfo 节点信息 + * @param nodeUrl 节点功能 url + * @param dataContentType 传输的数据类型 + */ + public static IUrlItem parseUrlItem(INodeInfo iNodeInfo, String workspaceId, NodeUrl nodeUrl, DataContentType dataContentType) { + // + Map header = NodeForward.createHeader(); + return new DefaultUrlItem(nodeUrl, iNodeInfo.timeout(), workspaceId, dataContentType, header); + } + + /** + * 创建节点 url + * + * @param iNodeInfo 节点信息 + * @param nodeUrl 节点功能 url + */ + public static IUrlItem parseUrlItem(INodeInfo iNodeInfo, String workspaceId, NodeUrl nodeUrl) { + // + Map header = NodeForward.createHeader(); + return new DefaultUrlItem(nodeUrl, iNodeInfo.timeout(), workspaceId, DataContentType.FORM_URLENCODED, header); + } + + /** + * 创建节点 url + * + * @param nodeModel 节点信息 + * @param nodeUrl 节点功能 url + * @param dataContentType 传输的数据类型 + */ + public static T createUrlItem(NodeModel nodeModel, NodeUrl nodeUrl, DataContentType dataContentType, BiFunction consumer) { + INodeInfo parseNodeInfo = parseNodeInfo(nodeModel); + Map header = NodeForward.createHeader(); + // + IUrlItem iUrlItem = new DefaultUrlItem(nodeUrl, parseNodeInfo.timeout(), nodeModel.getWorkspaceId(), dataContentType, header); + return consumer.apply(parseNodeInfo, iUrlItem); + } + + private static T createUrlItem(NodeModel nodeModel, NodeUrl nodeUrl, BiFunction consumer) { + return createUrlItem(nodeModel, nodeUrl, DataContentType.FORM_URLENCODED, consumer); + } + + private static T createUrlItem(INodeInfo nodeInfo, String workspaceId, NodeUrl nodeUrl, BiFunction consumer) { + return createUrlItem(nodeInfo, workspaceId, nodeUrl, DataContentType.FORM_URLENCODED, consumer); + } + + private static T createUrlItem(INodeInfo nodeInfo, String workspaceId, NodeUrl nodeUrl, DataContentType dataContentType, BiFunction consumer) { + // + Map header = NodeForward.createHeader(); + IUrlItem iUrlItem = new DefaultUrlItem(nodeUrl, nodeInfo.timeout(), workspaceId, dataContentType, header); + return consumer.apply(nodeInfo, iUrlItem); + } + + private static Map createHeader() { + Map header = new HashMap<>(); + UserModel userByThreadLocal = BaseServerController.getUserByThreadLocal(); + header.put(Const.JPOM_SERVER_USER_NAME, Optional.ofNullable(userByThreadLocal).map(BaseIdModel::getId).orElse(StrUtil.EMPTY)); + // 语言 + header.put(HttpHeaders.ACCEPT_LANGUAGE, I18nMessageUtil.getLanguageByRequest()); + return header; + } + + /** + * 普通消息转发 + * + * @param nodeModel 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @param 泛型 + * @return JSON + */ + public static JsonMessage request(NodeModel nodeModel, HttpServletRequest request, NodeUrl nodeUrl, String... removeKeys) { + return request(nodeModel, request, nodeUrl, removeKeys, new String[]{}); + } + + /** + * 普通消息转发 + * + * @param nodeModel 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @param 泛型 + * @return JSON + */ + public static JsonMessage request(NodeModel nodeModel, HttpServletRequest request, NodeUrl nodeUrl, String[] removeKeys, String... appendData) { + Map map = Optional.ofNullable(request) + .map(ServletUtil::getParamMap) + .map(map1 -> MapUtil.removeAny(map1, removeKeys)) + .map(map2 -> { + for (int i = 0; i < appendData.length; i += 2) { + map2.put(appendData[i], appendData[i + 1]); + } + return map2; + }) + .orElse(null); + + TypeReference> tTypeReference = new TypeReference>() { + }; + return createUrlItem(nodeModel, nodeUrl, + (nodeInfo, urlItem) -> + TransportServerFactory.get().executeToType(nodeInfo, urlItem, map, tTypeReference) + ); + } + + /** + * 普通消息转发 + * + * @param machineNodeModel 机器 + * @param request 请求 + * @param nodeUrl 节点的url + * @param 泛型 + * @return JSON + */ + public static JsonMessage request(MachineNodeModel machineNodeModel, HttpServletRequest request, NodeUrl nodeUrl, String... removeKeys) { + return request(machineNodeModel, request, nodeUrl, removeKeys, new String[]{}); + } + + /** + * 普通消息转发 + * + * @param machineNodeModel 机器 + * @param request 请求 + * @param nodeUrl 节点的url + * @param 泛型 + * @return JSON + */ + public static JsonMessage request(MachineNodeModel machineNodeModel, HttpServletRequest request, NodeUrl nodeUrl, String[] removeKeys, String... appendData) { + Map map = Optional.ofNullable(request) + .map(ServletUtil::getParamMap) + .map(map1 -> MapUtil.removeAny(map1, removeKeys)) + .map(map2 -> { + for (int i = 0; i < appendData.length; i += 2) { + map2.put(appendData[i], appendData[i + 1]); + } + return map2; + }) + .orElse(null); + TypeReference> tTypeReference = new TypeReference>() { + }; + INodeInfo nodeInfo1 = coverNodeInfo(machineNodeModel); + return createUrlItem(nodeInfo1, StrUtil.EMPTY, nodeUrl, + (nodeInfo, urlItem) -> + TransportServerFactory.get().executeToType(nodeInfo, urlItem, map, tTypeReference) + ); + } + + /** + * 普通消息转发 + * + * @param nodeModel 节点 + * @param nodeUrl 节点的url + * @param jsonObject 数据 + * @return JSON + */ + public static JsonMessage request(NodeModel nodeModel, NodeUrl nodeUrl, JSONObject jsonObject) { + TypeReference> tTypeReference = new TypeReference>() { + }; + return createUrlItem(nodeModel, nodeUrl, (nodeInfo, urlItem) -> TransportServerFactory.get().executeToType(nodeInfo, urlItem, jsonObject, tTypeReference)); + } + + /** + * 普通消息转发 + * + * @param machineNodeModel 节点 + * @param nodeUrl 节点的url + * @param jsonObject 数据 + * @return JSON + */ + public static JsonMessage request(MachineNodeModel machineNodeModel, NodeUrl nodeUrl, JSONObject jsonObject) { + TypeReference> typeReference = new TypeReference>() { + }; + INodeInfo nodeInfo = coverNodeInfo(machineNodeModel); + return createUrlItem(nodeInfo, StrUtil.EMPTY, nodeUrl, (nodeInfo1, urlItem) -> TransportServerFactory.get().executeToType(nodeInfo1, urlItem, jsonObject, typeReference)); + } + + /** + * 普通消息转发 + * + * @param nodeModel 节点 + * @param nodeUrl 节点的url + * @param jsonObject 数据 + * @return JSON + */ + public static JsonMessage requestSharding(NodeModel nodeModel, NodeUrl nodeUrl, JSONObject jsonObject, File file, Function> doneCallback, BiConsumer streamProgress) throws IOException { + INodeInfo nodeInfo = parseNodeInfo(nodeModel); + return requestSharding(nodeInfo, nodeModel.getWorkspaceId(), nodeUrl, jsonObject, file, File::getName, doneCallback, streamProgress); + } + + /** + * 普通消息转发 + * + * @param nodeModel 节点 + * @param nodeUrl 节点的url + * @param jsonObject 数据 + * @return JSON + */ + public static JsonMessage requestSharding(NodeModel nodeModel, NodeUrl nodeUrl, JSONObject jsonObject, File file, String fileName, Function> doneCallback, BiConsumer streamProgress) throws IOException { + INodeInfo nodeInfo = parseNodeInfo(nodeModel); + return requestSharding(nodeInfo, nodeModel.getWorkspaceId(), nodeUrl, jsonObject, file, file1 -> fileName, doneCallback, streamProgress); + } + + /** + * 普通消息转发 + * + * @param machineNodeModel 节点 + * @param nodeUrl 节点的url + * @param jsonObject 数据 + * @return JSON + */ + public static JsonMessage requestSharding(MachineNodeModel machineNodeModel, NodeUrl nodeUrl, JSONObject jsonObject, File file, Function> doneCallback, BiConsumer streamProgress) throws IOException { + INodeInfo nodeInfo = coverNodeInfo(machineNodeModel); + return requestSharding(nodeInfo, StrUtil.EMPTY, nodeUrl, jsonObject, file, File::getName, doneCallback, streamProgress); + } + + /** + * 普通消息转发 + * + * @param nodeInfo 节点 + * @param workspaceId 工作空间id + * @param streamProgress 进度回调 + * @param nodeUrl 节点的url + * @param jsonObject 数据 + * @return JSON + */ + private static JsonMessage requestSharding(INodeInfo nodeInfo, String workspaceId, NodeUrl nodeUrl, JSONObject jsonObject, File file, Function fileNameFn, Function> doneCallback, BiConsumer streamProgress) throws IOException { + IUrlItem urlItem = parseUrlItem(nodeInfo, workspaceId, nodeUrl, DataContentType.FORM_URLENCODED); + ServerConfig serverConfig = SpringUtil.getBean(ServerConfig.class); + NodeConfig nodeConfig = serverConfig.getNode(); + long length = file.length(); + String fileName = fileNameFn.apply(file); + Assert.state(length > 0, I18nMessageUtil.get("i18n.empty_file_cannot_upload.88df") + file.getAbsolutePath()); + String md5 = SecureUtil.md5(file); + int fileSliceSize = nodeConfig.getUploadFileSliceSize(); + //如果小数点大于1,整数加一 例如4.1 =》5 + long chunkSize = DataSize.ofMegabytes(fileSliceSize).toBytes(); + int total = (int) Math.ceil((double) length / chunkSize); + Queue queueList = new ConcurrentLinkedDeque<>(); + for (int i = 0; i < total; i++) { + queueList.offer(i); + } + List success = Collections.synchronizedList(new ArrayList<>(total)); + // 并发数 + int concurrent = nodeConfig.getUploadFileConcurrent(); + AtomicReference> failureMessage = new AtomicReference<>(); + AtomicReference> succeedMessage = new AtomicReference<>(); + AtomicLong atomicProgressSize = new AtomicLong(0); + JSONObject sliceData = new JSONObject(); + sliceData.put("sliceId", IdUtil.fastSimpleUUID()); + sliceData.put("totalSlice", total); + sliceData.put("fileSumMd5", md5); + TransportServer transportServer = TransportServerFactory.get(); + TypeReference> typeReference = new TypeReference>() { + }; + // 需要计算 并发数和最大任务数,如果任务数小于并发数则使用任务数 + try (StrictSyncFinisher syncFinisher = new StrictSyncFinisher(Math.min(concurrent, total), total)) { + Runnable runnable = () -> { + // 取出任务 + Integer currentChunk = queueList.poll(); + if (currentChunk == null) { + return; + } + JSONObject uploadData = jsonObject.clone(); + try { + try (FileInputStream inputStream = new FileInputStream(file)) { + try (FileChannel inputChannel = inputStream.getChannel()) { + //分配缓冲区,设定每次读的字节数 + ByteBuffer byteBuffer = ByteBuffer.allocate((int) chunkSize); + // 移动到指定位置开始读取 + inputChannel.position(currentChunk * chunkSize); + inputChannel.read(byteBuffer); + //上面把数据写入到了buffer,所以可知上面的buffer是写模式,调用flip把buffer切换到读模式,读取数据 + byteBuffer.flip(); + byte[] array = new byte[byteBuffer.remaining()]; + byteBuffer.get(array, 0, array.length); + uploadData.put("file", new BytesResource(array, fileName + StrUtil.DOT + currentChunk)); + uploadData.put("nowSlice", currentChunk); + uploadData.putAll(sliceData); + } + } + // 上传 + JsonMessage message = transportServer.executeToType(nodeInfo, urlItem, uploadData, typeReference); + if (message.success()) { + // 使用成功的个数计算 + success.add(currentChunk); + long end = Math.min(length, ((success.size() - 1) * chunkSize) + chunkSize); + // 保存线程安全顺序回调进度信息 + atomicProgressSize.set(Math.max(end, atomicProgressSize.get())); + streamProgress.accept(length, atomicProgressSize.get()); + succeedMessage.set(message); + } else { + log.warn(I18nMessageUtil.get("i18n.chunk_upload_exception.87c1"), nodeUrl, message); + // 终止上传 + queueList.clear(); + failureMessage.set(message); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.chunk_upload_file_exception.0dc3"), e); + // 终止上传 + queueList.clear(); + failureMessage.set(new JsonMessage<>(500, I18nMessageUtil.get("i18n.upload_exception.cd6c") + e.getMessage())); + } + }; + for (int i = 0; i < total; i++) { + syncFinisher.addWorker(runnable); + } + syncFinisher.start(); + } + JsonMessage message = failureMessage.get(); + if (message != null) { + return message; + } + // 判断是否都成功 + Assert.state(success.size() == total, I18nMessageUtil.get("i18n.upload_exception_mismatch.0b25")); + // + return Optional.ofNullable(doneCallback) + .map(function -> function.apply(sliceData)) + .orElseGet(succeedMessage::get); + } + + /** + * 普通消息转发 + * + * @param nodeModel 节点 + * @param nodeUrl 节点的url + * @param pName 主参数名 + * @param pVal 主参数值 + * @param parameters 其他参数 + * @return JSON + */ + public static JsonMessage request(NodeModel nodeModel, NodeUrl nodeUrl, String pName, Object pVal, Object... parameters) { + + INodeInfo parseNodeInfo = parseNodeInfo(nodeModel); + return request(parseNodeInfo, nodeModel.getWorkspaceId(), nodeUrl, pName, pVal, parameters); + } + + /** + * 普通消息转发 + * + * @param machineNodeModel 节点 + * @param workspaceId 工作空间id + * @param nodeUrl 节点的url + * @param pName 主参数名 + * @param pVal 主参数值 + * @param parameters 其他参数 + * @return JSON + */ + public static JsonMessage request(MachineNodeModel machineNodeModel, String workspaceId, NodeUrl nodeUrl, String pName, Object pVal, Object... parameters) { + Map parametersMap = MapUtil.of(pName, pVal); + for (int i = 0; i < parameters.length; i += 2) { + parametersMap.put(parameters[i].toString(), parameters[i + 1]); + } + INodeInfo nodeInfo = coverNodeInfo(machineNodeModel); + return request(nodeInfo, workspaceId, nodeUrl, pName, pVal, parameters); + } + + /** + * 普通消息转发 + * + * @param nodeInfo 节点 + * @param workspaceId 工作空间id + * @param nodeUrl 节点的url + * @param pName 主参数名 + * @param pVal 主参数值 + * @param parameters 其他参数 + * @return JSON + */ + private static JsonMessage request(INodeInfo nodeInfo, String workspaceId, NodeUrl nodeUrl, String pName, Object pVal, Object... parameters) { + Map parametersMap = MapUtil.of(pName, pVal); + for (int i = 0; i < parameters.length; i += 2) { + parametersMap.put(parameters[i].toString(), parameters[i + 1]); + } + IUrlItem iUrlItem = parseUrlItem(nodeInfo, workspaceId, nodeUrl); + return TransportServerFactory.get().executeToType(nodeInfo, iUrlItem, parametersMap, new TypeReference>() { + }); + } + + /** + * post body 消息转发 + * + * @param nodeModel 节点 + * @param nodeUrl 节点的url + * @param jsonData 数据 + * @param 泛型 + * @return JSON + */ + public static JsonMessage requestBody(NodeModel nodeModel, NodeUrl nodeUrl, JSONObject jsonData) { + TypeReference> tTypeReference = new TypeReference>() { + }; + return createUrlItem(nodeModel, nodeUrl, DataContentType.JSON, + (nodeInfo, urlItem) -> TransportServerFactory.get().executeToType(nodeInfo, urlItem, jsonData, tTypeReference)); + + } + + /** + * 普通消息转发,并解析数据 + * + * @param nodeModel 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @param tClass 要解析的类 + * @param 泛型 + * @return T + */ + public static T requestData(NodeModel nodeModel, NodeUrl nodeUrl, HttpServletRequest request, Class tClass) { + INodeInfo parseNodeInfo = parseNodeInfo(nodeModel); + return requestData(parseNodeInfo, nodeModel.getWorkspaceId(), nodeUrl, request, tClass); + } + + /** + * 普通消息转发,并解析数据 + * + * @param machineNodeModel 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @param tClass 要解析的类 + * @param 泛型 + * @return T + */ + public static T requestData(MachineNodeModel machineNodeModel, NodeUrl nodeUrl, HttpServletRequest request, Class tClass) { + INodeInfo nodeInfo = coverNodeInfo(machineNodeModel); + return requestData(nodeInfo, StrUtil.EMPTY, nodeUrl, request, tClass); + } + + /** + * 普通消息转发,并解析数据 + * + * @param nodeInfo1 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @param tClass 要解析的类 + * @param 泛型 + * @return T + */ + private static T requestData(INodeInfo nodeInfo1, String workspaceId, NodeUrl nodeUrl, HttpServletRequest request, Class tClass) { + Map map = Optional.ofNullable(request).map(ServletUtil::getParamMap).orElse(null); + IUrlItem iUrlItem = parseUrlItem(nodeInfo1, workspaceId, nodeUrl); + return TransportServerFactory.get().executeToTypeOnlyData(nodeInfo1, iUrlItem, map, tClass); + } + + + /** + * 上传文件消息转发 + * + * @param nodeModel 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @return json + */ + public static JsonMessage requestMultipart(NodeModel nodeModel, MultipartHttpServletRequest request, NodeUrl nodeUrl) { + INodeInfo parseNodeInfo = parseNodeInfo(nodeModel); + return requestMultipart(parseNodeInfo, nodeModel.getWorkspaceId(), request, nodeUrl); + } + + /** + * 上传文件消息转发 + * + * @param machineNodeModel 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @return json + */ + public static JsonMessage requestMultipart(MachineNodeModel machineNodeModel, MultipartHttpServletRequest request, NodeUrl nodeUrl) { + INodeInfo nodeInfo = coverNodeInfo(machineNodeModel); + return requestMultipart(nodeInfo, StrUtil.EMPTY, request, nodeUrl); + } + + /** + * 上传文件消息转发 + * + * @param nodeInfo 节点 + * @param request 请求 + * @param nodeUrl 节点的url + * @return json + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private static JsonMessage requestMultipart(INodeInfo nodeInfo, String workspaceId, MultipartHttpServletRequest request, NodeUrl nodeUrl) { + IUrlItem iUrlItem = parseUrlItem(nodeInfo, workspaceId, nodeUrl); + // + Map params = ServletUtil.getParamMap(request); + // + Map fileMap = request.getFileMap(); + fileMap.forEach((s, multipartFile) -> { + try { + params.put(s, new BytesResource(multipartFile.getBytes(), multipartFile.getOriginalFilename())); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.file_transfer_exception.bda6"), e); + throw Lombok.sneakyThrow(e); + } + }); + TypeReference> tTypeReference = new TypeReference>() { + }; + return TransportServerFactory.get().executeToType(nodeInfo, iUrlItem, params, tTypeReference); + + } + + /** + * 下载文件消息转发 + * + * @param nodeModel 节点 + * @param request 请求 + * @param response 响应 + * @param nodeUrl 节点的url + */ + public static void requestDownload(NodeModel nodeModel, HttpServletRequest request, HttpServletResponse response, NodeUrl nodeUrl) { + // + Map params = ServletUtil.getParamMap(request); + createUrlItem(nodeModel, nodeUrl, (nodeInfo, urlItem) -> { + TransportServerFactory.get().download(nodeInfo, urlItem, params, downloadCallback -> { + Opt.ofBlankAble(downloadCallback.getContentDisposition()) + .ifPresent(s -> response.setHeader(HttpHeaders.CONTENT_DISPOSITION, s)); + response.setContentType(downloadCallback.getContentType()); + ServletUtil.write(response, downloadCallback.getInputStream()); + }); + return null; + }); + } + + /** + * 下载文件消息转发 + * + * @param nodeModel 节点 + * @param request 请求 + * @param response 响应 + * @param nodeUrl 节点的url + */ + public static void requestDownload(MachineNodeModel nodeModel, HttpServletRequest request, HttpServletResponse response, NodeUrl nodeUrl) { + // + Map params = ServletUtil.getParamMap(request); + INodeInfo nodeInfo = coverNodeInfo(nodeModel); + IUrlItem iUrlItem = parseUrlItem(nodeInfo, StrUtil.EMPTY, nodeUrl); + TransportServerFactory.get().download(nodeInfo, iUrlItem, params, downloadCallback -> { + Opt.ofBlankAble(downloadCallback.getContentDisposition()) + .ifPresent(s -> response.setHeader(HttpHeaders.CONTENT_DISPOSITION, s)); + response.setContentType(downloadCallback.getContentType()); + ServletUtil.write(response, downloadCallback.getInputStream()); + }); + } + + public static T toJsonMessage(String body, TypeReference tTypeReference) { + if (StrUtil.isEmpty(body)) { + throw new AgentException(I18nMessageUtil.get("i18n.agent_response_empty.cc8e")); + } + T data = JSON.parseObject(body, tTypeReference); + if (data instanceof JsonMessage) { + JsonMessage jsonMessage = (JsonMessage) data; + if (jsonMessage.getCode() == Const.AUTHORIZE_ERROR) { + throw new AgentAuthorizeException(new JsonMessage<>(jsonMessage.getCode(), jsonMessage.getMsg())); + } + } else { + throw new IllegalStateException(I18nMessageUtil.get("i18n.message_conversion_exception.cce8")); + } + return data; + } +} 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 new file mode 100644 index 0000000000..5d38181263 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeUrl.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.forward; + +import lombok.Getter; + +/** + * agent 端的请求地址枚举 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Getter +public enum NodeUrl { + /** + * Jpom agent 信息 + */ + Info("/info"), + /** + * + */ +// GetTop("/getTop"), + GetStatInfo("/get-stat-info"), + exportTop("/exportTop"), + Kill("/kill.json"), + DiskInfo("/disk-info"), + HwDiskInfo("/hw-disk--info"), + + NetworkInterfaces("/network-interfaces"), + + ProcessList("/processList", -1), + /** + * socket 连接 ,第一节项目id 第二节用户信息 + */ + TopSocket("/console"), + /** + * 脚本模板 模板id + */ + Script_Run("/script_run"), + /** + * 自由脚本 + */ + FreeScriptRun("/free-script-run"), + /** + * 系统日志 + */ + Socket_SystemLog("/system_log"), + /** + * 节点升级 + */ + NodeUpdate("/node_update"), + + WhitelistDirectory_Submit("/system/whitelistDirectory_submit"), + + WhitelistDirectory_data("/system/whitelistDirectory_data"), + + Manage_SaveProject("/manage/saveProject"), + + Manage_DeleteProject("/manage/deleteProject"), + + Manage_ReleaseOutGiving("/manage/releaseOutGiving"), + Manage_ChangeWorkspaceId("/manage/change-workspace-id"), + + Manage_GetProjectInfo("/manage/getProjectInfo"), + +// Manage_Jude_Lib("/manage/judge_lib.json"), + +// Manage_GetProjectGroup("/manage/getProjectGroup"), + + Manage_GetProjectItem("/manage/getProjectItem"), + + Manage_GetProjectStatus("/manage/getProjectStatus"), + + Manage_Operate("/manage/operate"), + + Manage_GetProjectPort("/manage/getProjectPort"), + + + Manage_Recover_List_Data("/manage/recover/list_data"), + + Manage_Recover_Item_Data("/manage/recover/item_data"), + Manage_File_GetFileList("/manage/file/getFileList"), + MANAGE_FILE_BACKUP_LIST_BACKUP("/manage/file/list-backup"), + MANAGE_FILE_BACKUP_LIST_ITEM_FILES("/manage/file/backup-item-files"), + MANAGE_FILE_BACKUP_DOWNLOAD("/manage/file/backup-download", true), + MANAGE_FILE_BACKUP_DELETE("/manage/file/backup-delete"), + MANAGE_FILE_BACKUP_RECOVER("/manage/file/backup-recover"), + Manage_File_Upload_Sharding("/manage/file/upload-sharding", true), + Manage_File_Sharding_Merge("/manage/file/sharding-merge", true), + Manage_File_Upload_Sharding2("/manage/file2/upload-sharding", true), + Manage_File_Sharding_Merge2("/manage/file2/sharding-merge", true), + + Manage_File_DeleteFile("/manage/file/deleteFile"), + /** + * 对比项目文件 + */ + MANAGE_FILE_DIFF_FILE("/manage/file/diff_file"), + /** + * 批量删除文件 + */ + MANAGE_FILE_BATCH_DELETE("/manage/file/batch_delete"), + + Manage_File_UpdateConfigFile("/manage/file/update_config_file"), + + Manage_File_ReadFile("/manage/file/read_file"), + + Manage_File_Remote_Download("/manage/file/remote_download", true), + MANAGE_FILE_NEW_FILE_FOLDER("/manage/file/new_file_folder.json"), + MANAGE_FILE_RENAME_FILE_FOLDER("/manage/file/rename.json"), + MANAGE_FILE_COPY("/manage/file/copy"), + MANAGE_FILE_COMPRESS("/manage/file/compress"), + + Manage_File_Download("/manage/file/download", true), + + + Manage_Log_LogSize("/manage/log/logSize"), + + Manage_Log_ResetLog("/manage/log/resetLog"), + + Manage_Log_logBack_delete("/manage/log/logBack_delete"), + + Manage_Log_logBack_download("/manage/log/logBack_download", true), + + Manage_Log_logBack("/manage/log/logBack"), + + 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"), + Script_Save("/script/save.json"), + SCRIPT_LOG("/script/log"), + SCRIPT_EXEC("/script/exec"), + SCRIPT_DEL_LOG("/script/del_log"), + // Script_Upload("/script/upload.json"), + Script_Del("/script/del.json"), + + SCRIPT_LIBRARY_LIST("/script-library/list"), + SCRIPT_LIBRARY_DEL("/script-library/del"), + SCRIPT_LIBRARY_SAVE("/script-library/save"), + SCRIPT_LIBRARY_GET("/script-library/get"), + + /** + * Workspace + */ + Workspace_EnvVar_Update("/system/workspace_env/update"), + Workspace_EnvVar_Delete("/system/workspace_env/delete"), + + /** + * 缓存 + */ + Cache("/system/cache"), + /** + * 缓存 + */ + ClearCache("/system/clearCache"), + /** + * 系统日志 + */ + SystemLog("/system/log_data.json"), + + DelSystemLog("/system/log_del.json"), + + DownloadSystemLog("/system/log_download", true), + /** + * 更新系统jar包 + */ + SystemUploadJar("/system/upload-jar-sharding", true), + /** + * 更新系统jar包 + */ + SystemUploadJarMerge("/system/upload-jar-sharding-merge", true), + /** + * 更新系统jar包 + */ + CHECK_VERSION("/system/check_version.json"), + /** + * 远程升级 + */ + REMOTE_UPGRADE("/system/remote_upgrade.json", true), + CHANGE_LOG("/system/change_log"), + + /** + * + */ + SystemGetConfig("/system/getConfig.json"), + SystemSaveConfig("/system/save_config.json"), + ; + /** + * 相对请求地址 + */ + private final String url; + private int timeout; + private boolean fileTimeout = false; + + NodeUrl(String url, int timeout) { + this.url = url; + this.timeout = timeout; + } + + NodeUrl(String url, boolean fileTimeout) { + this.url = url; + this.fileTimeout = fileTimeout; + } + + NodeUrl(String url) { + this.url = url; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/common/i18n/TransportI18nMessageImpl.java b/modules/server/src/main/java/org/dromara/jpom/common/i18n/TransportI18nMessageImpl.java new file mode 100644 index 0000000000..8838cb8fec --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/i18n/TransportI18nMessageImpl.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.i18n; + +import org.dromara.jpom.transport.i18n.II18nMessageUtil; + +/** + * @author bwcx_jzy1 + * @since 2024/6/11 + */ +public class TransportI18nMessageImpl implements II18nMessageUtil { + + @Override + public String get(String key) { + return I18nMessageUtil.get(key); + } +} 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 new file mode 100644 index 0000000000..351860068d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/IpInterceptor.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import cn.hutool.core.lang.Validator; +import cn.hutool.core.net.Ipv4Util; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.SystemIpConfigModel; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.method.HandlerMethod; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * ip 访问限制拦截器 + * + * @author bwcx_jzy + * @since 2021/4/18 + */ +@Configuration +@Slf4j +public class IpInterceptor implements HandlerMethodInterceptor { + + private static final int IP_ACCESS_CODE = 999; + + @Resource + private SystemParametersServer systemParametersServer; + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { + String clientIp = ServletUtil.getClientIP(request); + if (StrUtil.equals(NetUtil.LOCAL_IP, clientIp) || !Validator.isIpv4(clientIp)) { + // 本地 或者 非 ipv4 直接放开 + return true; + } + SystemIpConfigModel config = systemParametersServer.getConfig(SystemIpConfigModel.ID, SystemIpConfigModel.class); + if (config == null) { + return true; + } + // 判断不允许访问 + String prohibited = config.getProhibited(); + try { + if (StrUtil.isNotEmpty(prohibited) && this.checkIp(prohibited, clientIp, false)) { + ServletUtil.write(response, JsonMessage.getString(IP_ACCESS_CODE, "Prohibition of access"), MediaType.APPLICATION_JSON_VALUE); + return false; + } + String allowed = config.getAllowed(); + if (StrUtil.isEmpty(allowed) || this.checkIp(allowed, clientIp, true)) { + return true; + } + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.ip_authorization_interception_exception.8130"), e); + return true; + } + ServletUtil.write(response, JsonMessage.getString(IP_ACCESS_CODE, "Prohibition of access"), MediaType.APPLICATION_JSON_VALUE); + return false; + } + + + /** + * 检查ip 地址是否可以访问 + * + * @param value 配置的值 + * @param ip 被检查的 ip 地址 + * @param checkAll 是否检查开放所有、避免禁止所有 ip 访问 + * @return true 命中检查项 + */ + private boolean checkIp(String value, String ip, boolean checkAll) { + long ipNum = NetUtil.ipv4ToLong(ip); + String[] split = StrUtil.splitToArray(value, StrUtil.LF); + boolean check; + for (String itemIp : split) { + itemIp = itemIp.trim(); + if (itemIp.startsWith("#")) { + continue; + } + if (checkAll && StrUtil.equals(itemIp, "0.0.0.0")) { + // 开放所有 + return true; + } + if (StrUtil.contains(itemIp, Ipv4Util.IP_MASK_SPLIT_MARK)) { + // ip段 + String[] itemIps = StrUtil.splitToArray(itemIp, Ipv4Util.IP_MASK_SPLIT_MARK); + int count1 = StrUtil.count(itemIps[0], StrUtil.DOT); + int count2 = StrUtil.count(itemIps[1], StrUtil.DOT); + if (count1 == 3 && count2 == 3) { + //192.168.1.0/192.168.1.200 + long aBegin = NetUtil.ipv4ToLong(itemIps[0]); + long aEnd = NetUtil.ipv4ToLong(itemIps[1]); + check = (ipNum >= aBegin) && (ipNum <= aEnd); + } else if (count1 == 3 && count2 == 0) { + //192.168.1.0/24 + String startIp = Ipv4Util.getBeginIpStr(itemIps[0], Integer.parseInt(itemIps[1])); + String endIp = Ipv4Util.getEndIpStr(itemIps[0], Integer.parseInt(itemIps[1])); + long aBegin = NetUtil.ipv4ToLong(startIp); + long aEnd = NetUtil.ipv4ToLong(endIp); + check = (ipNum >= aBegin) && (ipNum <= aEnd); + } else { + check = false; + } + + } else { + check = StrUtil.equals(itemIp, ip); + } + if (check) { + return true; + } + } + return false; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/LoginInterceptor.java b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/LoginInterceptor.java new file mode 100644 index 0000000000..a195f8e87f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/LoginInterceptor.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.jwt.JWT; +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.configuration.UserConfig; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.JwtUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.method.HandlerMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * 登录拦截器 + * + * @author bwcx_jzy + * @since 2017/2/4. + */ +@Configuration +public class LoginInterceptor implements HandlerMethodInterceptor { + /** + * session + */ + public static final String SESSION_NAME = "user"; + + private static final Map> MSG_CACHE = new HashMap<>(3); + + private final UserConfig userConfig; + + static { + MSG_CACHE.put(ServerConst.AUTHORIZE_TIME_OUT_CODE, ServerConst.LOGIN_TIP); + MSG_CACHE.put(ServerConst.RENEWAL_AUTHORIZE_CODE, ServerConst.LOGIN_TIP); + MSG_CACHE.put(ServerConst.ACCOUNT_LOCKED, ServerConst.ACCOUNT_LOCKED_TIP); + } + + public LoginInterceptor(ServerConfig serverConfig) { + this.userConfig = serverConfig.getUser(); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { + HttpSession session = request.getSession(); + // + NotLogin notLogin = handlerMethod.getMethodAnnotation(NotLogin.class); + if (notLogin == null) { + notLogin = handlerMethod.getBeanType().getAnnotation(NotLogin.class); + } + if (notLogin == null) { + // 这里需要判断请求头里是否有 Authorization 属性 + String authorization = request.getHeader(ServerOpenApi.HTTP_HEAD_AUTHORIZATION); + if (StrUtil.isNotEmpty(authorization)) { + // jwt token 检测机制 + int code = this.checkHeaderUser(request, session); + if (code > 0) { + this.responseLogin(request, session, response, code); + return false; + } + } else { + // 老版本登录拦截 + int code = this.tryGetHeaderUser(request, session); + if (code > 0) { + this.responseLogin(request, session, response, ServerConst.AUTHORIZE_TIME_OUT_CODE); + return false; + } + } + } + // + return true; + } + + /** + * 尝试获取 header 中的信息 + * + * @param session ses + * @param request req + * @return true 获取成功 + */ + private int checkHeaderUser(HttpServletRequest request, HttpSession session) { + String token = request.getHeader(ServerOpenApi.HTTP_HEAD_AUTHORIZATION); + if (StrUtil.isEmpty(token)) { + return ServerConst.AUTHORIZE_TIME_OUT_CODE; + } + JWT jwt = JwtUtil.readBody(token); + if (JwtUtil.expired(jwt, 0)) { + int renewal = userConfig.getTokenRenewal(); + if (jwt == null || renewal <= 0 || JwtUtil.expired(jwt, TimeUnit.MINUTES.toSeconds(renewal))) { + return ServerConst.AUTHORIZE_TIME_OUT_CODE; + } + return ServerConst.RENEWAL_AUTHORIZE_CODE; + } + UserModel user = (UserModel) session.getAttribute(SESSION_NAME); + UserService userService = SpringUtil.getBean(UserService.class); + String id = JwtUtil.getId(jwt); + UserModel newUser = userService.checkUser(id); + if (newUser == null) { + return ServerConst.AUTHORIZE_TIME_OUT_CODE; + } + if (null != user) { + String tokenUserId = JwtUtil.readUserId(jwt); + boolean b = user.getId().equals(tokenUserId); + if (!b) { + return ServerConst.AUTHORIZE_TIME_OUT_CODE; + } + } + if (newUser.getStatus() != null && newUser.getStatus() == 0) { + // 账号禁用 + return ServerConst.ACCOUNT_LOCKED; + } + session.setAttribute(LoginInterceptor.SESSION_NAME, newUser); + return 0; + } + + + /** + * 尝试获取 header 中的信息 + * + * @param session ses + * @param request req + * @return 状态码 + */ + private int tryGetHeaderUser(HttpServletRequest request, HttpSession session) { + String header = request.getHeader(ServerOpenApi.USER_TOKEN_HEAD); + if (StrUtil.isEmpty(header)) { + // 兼容就版本 登录状态 (下载功能需要使用到 session 的登录状态) + UserModel user = (UserModel) session.getAttribute(SESSION_NAME); + return user != null ? 0 : ServerConst.AUTHORIZE_TIME_OUT_CODE; + } + UserService userService = SpringUtil.getBean(UserService.class); + UserModel userModel = userService.checkUser(header); + if (userModel == null) { + return ServerConst.AUTHORIZE_TIME_OUT_CODE; + } + if (userModel.getStatus() != null && userModel.getStatus() == 0) { + // 账号禁用 + return ServerConst.ACCOUNT_LOCKED; + } + session.setAttribute(LoginInterceptor.SESSION_NAME, userModel); + return 0; + } + + /** + * 提示登录 + * + * @param request req + * @param session 回话 + * @param response res + * @throws IOException 异常 + */ + private void responseLogin(HttpServletRequest request, HttpSession session, HttpServletResponse response, int code) throws IOException { + session.removeAttribute(LoginInterceptor.SESSION_NAME); + Supplier msg = MSG_CACHE.getOrDefault(code, ServerConst.LOGIN_TIP); + ServletUtil.write(response, JsonMessage.getString(code, msg.get()), MediaType.APPLICATION_JSON_VALUE); + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + BaseServerController.removeAll(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/NotLogin.java b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/NotLogin.java new file mode 100644 index 0000000000..9ff87fbec0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/NotLogin.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import java.lang.annotation.*; + +/** + * 游客可以访问的Controller 标记 + * + * @author bwcx_jzy + * @since 2017/5/9. + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface NotLogin { + +} 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 new file mode 100644 index 0000000000..c3684e9da4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/PermissionInterceptor.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +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.common.i18n.I18nMessageUtil; +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; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.permission.*; +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.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.method.HandlerMethod; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.function.Supplier; + +/** + * 权限拦截器 + * + * @author bwcx_jzy + * @since 2019/03/16. + */ +@Configuration +public class PermissionInterceptor implements HandlerMethodInterceptor { + + @Resource + private NodeService nodeService; + @Resource + private UserBindWorkspaceService userBindWorkspaceService; + public static final Supplier DEMO_TIP = () -> I18nMessageUtil.get("i18n.demo_account_cannot_use_feature.a1a1"); + /** + * demo 账号不能使用的功能 + */ + private static final MethodFeature[] DEMO = new MethodFeature[]{ + MethodFeature.DEL, + MethodFeature.UPLOAD, + MethodFeature.REMOTE_DOWNLOAD, + MethodFeature.EXECUTE}; + + + private SystemPermission getSystemPermission(HandlerMethod handlerMethod) { + SystemPermission systemPermission = handlerMethod.getMethodAnnotation(SystemPermission.class); + if (systemPermission == null) { + systemPermission = handlerMethod.getBeanType().getAnnotation(SystemPermission.class); + } + return systemPermission; + } + + private NodeDataPermission getNodeDataPermission(HandlerMethod handlerMethod) { + NodeDataPermission nodeDataPermission = handlerMethod.getMethodAnnotation(NodeDataPermission.class); + if (nodeDataPermission == null) { + nodeDataPermission = handlerMethod.getBeanType().getAnnotation(NodeDataPermission.class); + } + return nodeDataPermission; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { + this.addNode(request); + UserModel userModel = BaseServerController.getUserModel(); + if (userModel == null || userModel.isSuperSystemUser()) { + // 没有登录、或者超级管理直接放过 + return true; + } + // + boolean permission = this.checkSystemPermission(userModel, request, response, handlerMethod); + if (!permission) { + return false; + } + permission = this.checkNodeDataPermission(userModel, request, response, handlerMethod); + if (!permission) { + return false; + } + Feature feature = handlerMethod.getMethodAnnotation(Feature.class); + if (feature == null) { + return true; + } + MethodFeature method = feature.method(); + if (ArrayUtil.contains(DEMO, method) && userModel.isDemoUser()) { + this.errorMsg(response, DEMO_TIP.get()); + return false; + } + ClassFeature classFeature = feature.cls(); + if (classFeature == ClassFeature.NULL) { + Feature feature1 = handlerMethod.getBeanType().getAnnotation(Feature.class); + if (feature1 != null && feature1.cls() != ClassFeature.NULL) { + classFeature = feature1.cls(); + } + } + // 判断功能权限 + if (method != MethodFeature.LIST) { + String workspaceId = BaseWorkspaceService.getWorkspaceId(request); + UserBindWorkspaceModel.PermissionResult permissionResult = userBindWorkspaceService.checkPermission(userModel, workspaceId + StrUtil.DASHED + method.name()); + if (!permissionResult.isSuccess()) { + this.errorMsg(response, permissionResult.errorMsg(StrUtil.format(I18nMessageUtil.get("i18n.corresponding_function.5bb5"), I18nMessageUtil.get(classFeature.getName().get()), I18nMessageUtil.get(method.getName().get())))); + return false; + } + } + return true; + } + + /** + * 检查管理员权限 + * + * @param userModel 用户 + * @param response 响应 + * @param handlerMethod 拦截到到方法 + * @return true 有权限 + */ + private boolean checkNodeDataPermission(UserModel userModel, HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) { + NodeDataPermission nodeDataPermission = this.getNodeDataPermission(handlerMethod); + if (nodeDataPermission == null || userModel.isSuperSystemUser()) { + return true; + } + NodeModel node = (NodeModel) request.getAttribute("node"); + if (node != null) { + String parameterName = nodeDataPermission.parameterName(); + BaseNodeService baseNodeService = SpringUtil.getBean(nodeDataPermission.cls()); + String dataId = request.getParameter(parameterName); + if (StrUtil.isNotEmpty(dataId)) { + BaseNodeModel data = baseNodeService.getData(node.getId(), dataId); + if (data != null) { + UserBindWorkspaceModel.PermissionResult permissionResult = userBindWorkspaceService.checkPermission(userModel, data.getWorkspaceId()); + + if (!permissionResult.isSuccess()) { + this.errorMsg(response, permissionResult.errorMsg()); + return false; + } + } + } + } + return true; + } + + /** + * 检查管理员权限 + * + * @param userModel 用户 + * @param response 响应 + * @param handlerMethod 拦截到到方法 + * @return true 有权限 + */ + private boolean checkSystemPermission(UserModel userModel, HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) { + SystemPermission systemPermission = this.getSystemPermission(handlerMethod); + if (systemPermission == null) { + return true; + } + if (systemPermission.superUser() && !userModel.isSuperSystemUser()) { + this.errorMsg(response, I18nMessageUtil.get("i18n.not_super_admin.962e")); + return false; + } + if (!userModel.isSystemUser()) { + this.errorMsg(response, I18nMessageUtil.get("i18n.no_server_management_permission.ee19")); + return false; + } + return true; + } + + private void addNode(HttpServletRequest request) { + String nodeId = request.getParameter("nodeId"); + if (!StrUtil.isBlankOrUndefined(nodeId)) { + // 节点信息 + NodeModel nodeModel = nodeService.getByKey(nodeId); + if (nodeModel != null && !nodeModel.isOpenStatus()) { + throw new AgentException(nodeModel.getName() + I18nMessageUtil.get("i18n.node_not_enabled.a14d")); + } + request.setAttribute("node", nodeModel); + } + } + + private void errorMsg(HttpServletResponse response, String msg) { + JsonMessage jsonMessage = new JsonMessage<>(302, msg); + ServletUtil.write(response, jsonMessage.toString(), MediaType.APPLICATION_JSON_VALUE); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/ServerDecryptionFilter.java b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/ServerDecryptionFilter.java new file mode 100644 index 0000000000..33e6d25e94 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/ServerDecryptionFilter.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.common.interceptor; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.ContentType; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.transport.BodyRewritingRequestWrapper; +import org.dromara.jpom.common.transport.MultipartRequestWrapper; +import org.dromara.jpom.common.transport.ParameterRequestWrapper; +import org.dromara.jpom.configuration.WebConfig; +import org.dromara.jpom.encrypt.EncryptFactory; +import org.dromara.jpom.encrypt.Encryptor; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * @author bwcx_jzy + * @since 2024/2/22 + */ +@Configuration +@Slf4j +@Order(1) +public class ServerDecryptionFilter implements Filter { + + private final Encryptor encryptor; + + public ServerDecryptionFilter(ServerConfig serverConfig) { + Encryptor encryptor1; + WebConfig config = serverConfig.getWeb(); + String transportEncryption = config.getTransportEncryption(); + transportEncryption = ObjectUtil.defaultIfNull(transportEncryption, StrUtil.EMPTY).toUpperCase(); + switch (transportEncryption) { + case "NONE": + encryptor1 = null; + break; + case "BASE64": + try { + encryptor1 = EncryptFactory.createEncryptor(1); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.get_decrypt_implementation_failure.e77a"), e); + encryptor1 = null; + } + break; + default: + log.warn(I18nMessageUtil.get("i18n.unsupported_encoding_with_placeholder.3bd9"), transportEncryption); + encryptor1 = null; + break; + } + encryptor = encryptor1; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + if (encryptor == null) { + chain.doFilter(servletRequest, response); + return; + } + log.debug(I18nMessageUtil.get("i18n.request_needs_decoding.d4d7"), encryptor.name()); + String contentType = request.getContentType(); + if (ContentType.isDefault(contentType)) { + // 普通表单 + HttpServletRequestWrapper wrapper = new ParameterRequestWrapper(request, encryptor); + chain.doFilter(wrapper, response); + } else if (StrUtil.startWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)) { + String body = ServletUtil.getBody(request); + String temp; + try { + temp = encryptor.decrypt(body); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.decode_failure.822e"), e); + temp = body; + } + BodyRewritingRequestWrapper requestWrapper = new BodyRewritingRequestWrapper(request, temp.getBytes(StandardCharsets.UTF_8)); + chain.doFilter(requestWrapper, response); + } else if (ServletFileUpload.isMultipartContent(request)) { + // 文件上传 + HttpServletRequestWrapper wrapper = new MultipartRequestWrapper(request, encryptor); + chain.doFilter(wrapper, response); + } else { + log.warn(I18nMessageUtil.get("i18n.request_type_not_supported_for_decoding.ea2e"), contentType); + chain.doFilter(servletRequest, response); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/AssetsConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/AssetsConfig.java new file mode 100644 index 0000000000..d6666756b7 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/AssetsConfig.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * 资产配置 + * + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@ConfigurationProperties("jpom.assets") +@Data +@Configuration +@EnableConfigurationProperties({AssetsConfig.SshConfig.class, AssetsConfig.DockerConfig.class}) +public class AssetsConfig { + /** + * 监控线程池大小,小于等于0 为CPU核心数 + */ + private int monitorPoolSize = 0; + + /** + * 监控任务等待数量,超过此数量将取消监控任务,值最小为 1 + */ + private int monitorPoolWaitQueue = 500; + /** + * ssh 资产配置 + */ + private SshConfig ssh; + /** + * docker 资产配置 + */ + private DockerConfig docker; + + public SshConfig getSsh() { + return ObjectUtil.defaultIfNull(this.ssh, () -> { + this.ssh = new SshConfig(); + return ssh; + }); + } + + public DockerConfig getDocker() { + return ObjectUtil.defaultIfNull(this.docker, () -> { + this.docker = new DockerConfig(); + return docker; + }); + } + + /** + * ssh 配置 + */ + @Data + @ConfigurationProperties("jpom.assets.ssh") + public static class SshConfig { + + /** + * 监控频率 + */ + private String monitorCron; + /** + * 禁用监控的分组名 (如果想禁用所有配置 * 即可) + */ + private List disableMonitorGroupName; + + } + + /** + * docker 配置 + */ + @Data + @ConfigurationProperties("jpom.assets.docker") + public static class DockerConfig { + + /** + * 监控频率 + */ + private String monitorCron; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/BuildExtConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/BuildExtConfig.java new file mode 100644 index 0000000000..4370505318 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/BuildExtConfig.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 构建相关配置 + * + * @author bwcx_jzy + * @since 2022/7/7 + */ +@Configuration +@ConfigurationProperties(prefix = "jpom.build") +@Data +public class BuildExtConfig { + + /** + * 构建最多保存多少份历史记录 + */ + private int maxHistoryCount = 1000; + + /** + * 每一项构建最多保存的历史份数 + */ + private int itemMaxHistoryCount = 50; + + private boolean checkDeleteCommand = true; + + /** + * 构建线程池大小,小于 1 则为不限制,默认大小为 5 + */ + private int poolSize = 5; + + /** + * 构建任务等待数量,超过此数量将取消构建任务,值最小为 1 + */ + private int poolWaitQueue = 10; + /** + * 压缩折叠显示进度比例 范围 1-100 + */ + private int logReduceProgressRatio = 5; + + public void setLogReduceProgressRatio(int logReduceProgressRatio) { + // 修正值 + this.logReduceProgressRatio = Math.min(Math.max(logReduceProgressRatio, 1), 100); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/ClusterConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/ClusterConfig.java new file mode 100644 index 0000000000..9a924d0133 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/ClusterConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.dromara.jpom.common.Const; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@Data +@ConfigurationProperties("jpom.cluster") +public class ClusterConfig { + + /** + * 集群Id,默认为 default 不区分大小写,只能是字母或者数字,长度小于 20 + */ + private String id; + /** + * 检查节点心跳间隔时间,最小值 5 秒 + */ + private int heartSecond = 30; + + public int getHeartSecond() { + return Math.max(this.heartSecond, 5); + } + + public String getId() { + return StrUtil.emptyToDefault(this.id, Const.WORKSPACE_DEFAULT_ID).toUpperCase(); + } +} 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 new file mode 100644 index 0000000000..1c522b5aef --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/FileStorageConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 文件管理存储 + * + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@Data +@ConfigurationProperties("jpom.file-storage") +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/configuration/NodeConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/NodeConfig.java new file mode 100644 index 0000000000..cab3de5ced --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/NodeConfig.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.util.RuntimeUtil; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.unit.DataSize; + +/** + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@Data +@ConfigurationProperties("jpom.node") +public class NodeConfig { + /** + * 检查节点心跳间隔时间,最小值 5 秒 + */ + private int heartSecond = 30; + + public int getHeartSecond() { + return Math.max(this.heartSecond, 5); + } + + /** + * 上传文件的超时时间 单位秒,最短5秒中 + */ + private int uploadFileTimeout = 300; + + /** + * 节点文件分片上传大小,单位 M + */ + private int uploadFileSliceSize = 1; + + /** + * 节点文件分片上传并发数,最小1 最大 服务端 CPU 核心数 + */ + private int uploadFileConcurrent = 2; + /** + * web socket 消息最大长度 + */ + private DataSize webSocketMessageSizeLimit = DataSize.ofMegabytes(5); + + public int getUploadFileTimeout() { + return Math.max(this.uploadFileTimeout, 5); + } + + public int getUploadFileSliceSize() { + return Math.max(this.uploadFileSliceSize, 1); + } + + public void setUploadFileConcurrent(int uploadFileConcurrent) { + this.uploadFileConcurrent = Math.min(Math.max(uploadFileConcurrent, 1), RuntimeUtil.getProcessorCount()); + } + + /** + * 节点统计日志保留天数,如果小于等于 0 不自动删除 + */ + private int statLogKeepDays = 3; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/SystemConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/SystemConfig.java new file mode 100644 index 0000000000..8f87593375 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/SystemConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.socket.ServiceFileTailWatcher; +import org.dromara.jpom.system.BaseSystemConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.nio.charset.Charset; + +/** + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ConfigurationProperties("jpom.system") +public class SystemConfig extends BaseSystemConfig { + + @Override + public void setLogCharset(Charset logCharset) { + super.setLogCharset(logCharset); + ServiceFileTailWatcher.setCharset(getLogCharset()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/UserConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/UserConfig.java new file mode 100644 index 0000000000..e6001c672e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/UserConfig.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.time.Duration; +import java.util.Optional; + +/** + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@Data +@ConfigurationProperties("jpom.user") +public class UserConfig { + /** + * 用户连续登录失败次数,超过此数将自动不再被允许登录,零是不限制 + */ + private int alwaysLoginError = 5; + + /** + * IP连续登录失败次数,超过此数将自动不再被允许登录,零是不限制 + */ + private int alwaysIpLoginError = 10; + + /** + * 是否强制提醒用户开启 mfa + */ + private boolean forceMfa = false; + /** + * 当ip连续登录失败,锁定对应IP时长,单位毫秒 + */ + private Duration ipErrorLockTime; + + public Duration getIpErrorLockTime() { + return Optional.ofNullable(this.ipErrorLockTime).orElseGet(() -> { + ipErrorLockTime = Duration.ofHours(5); + return ipErrorLockTime; + }); + } + + /** + * demo 账号的提示 + */ + private String demoTip; + + + /** + * 登录token失效时间(单位:小时),默认为24 + */ + private int tokenExpired = 24; + + public int getTokenExpired() { + return Math.max(this.tokenExpired, 1); + } + + /** + * 登录token失效后自动续签时间(单位:分钟),默认为60, + */ + private int tokenRenewal = 60; + + public int getTokenRenewal() { + return Math.max(this.tokenRenewal, 1); + } + + /** + * 登录token 加密的key 长度建议控制到 16位 + */ + private String tokenJwtKey; + + public byte[] getTokenJwtKeyByte() { + return StrUtil.emptyToDefault(this.tokenJwtKey, "KZQfFBJTW2v6obS1").getBytes(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/WebConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/WebConfig.java new file mode 100644 index 0000000000..e494fd0c4d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/WebConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.configuration; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author bwcx_jzy + * @since 23/12/25 025 + */ +@Data +@ConfigurationProperties("jpom.web") +public class WebConfig { + /** + * 前端接口 超时时间 单位秒 + */ + private int apiTimeout = 20; + + public int getApiTimeout() { + return Math.max(this.apiTimeout, 5); + } + + /** + * 系统名称 + */ + private String name; + + /** + * 系统副名称(标题) 建议4个汉字以内 + */ + private String subTitle; + + /** + * 登录页标题 + */ + private String loginTitle; + + /** + * logo 文件路径 + */ + private String logoFile; + + /** + * icon 文件路径 + */ + private String iconFile; + + /** + * 禁用页面引导导航 + */ + private boolean disabledGuide = false; + /** + * 禁用登录图形验证码 + */ + private boolean disabledCaptcha = false; + + /** + * 前端消息弹出位置,可选 topLeft topRight bottomLeft bottomRight + */ + private String notificationPlacement; + /** + * 消息传输加密或者编码 + * NONE + *

+ * BASE64 + */ + private String transportEncryption = "NONE"; + + public String getName() { + return StrUtil.emptyToDefault(name, I18nMessageUtil.get("i18n.jpom_project_maintenance_system.7f8e")); + } + + public String getSubTitle() { + return StrUtil.emptyToDefault(subTitle, I18nMessageUtil.get("i18n.project_operations.03d9")); + } + + public String getLoginTitle() { + return StrUtil.emptyToDefault(loginTitle, I18nMessageUtil.get("i18n.login_JPOM.0de6")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/AboutController.java b/modules/server/src/main/java/org/dromara/jpom/controller/AboutController.java new file mode 100644 index 0000000000..735e46e0a2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/AboutController.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

Jpom 为开源软件,请基于开源协议用于商业用途

+ *

+ * 开源不等同于免费,如果您基于 Jpom 二次开发修改了 logo、名称、版权等,请联系我们授权,否则会有法律风险。 我们有权利追诉破坏开源并因此获利的团队个人的全部违法所得,也欢迎给我们提供侵权线索。 + *

+ * + *

二次修改不可删除或者修改版权,否则可能承担法律责任

+ * + * @author bwcx_jzy + */ +@RestController +@RequestMapping(value = "about") +@Slf4j +public class AboutController { + + /** + * 擅自修改或者删除版权信息有法律风险,请尊重开源协议,不要擅自修改版本信息,否则可能承担法律责任。 + * + * @return json + */ + @GetMapping(value = "license", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage license() { + InputStream inputStream = ResourceUtil.getStream("classpath:/LICENSE"); + return JsonMessage.success("", IoUtil.readUtf8(inputStream)); + } + + /** + * 擅自修改或者删除版权信息有法律风险,请尊重开源协议,不要擅自修改版本信息,否则可能承担法律责任。 + * + * @return json + */ + @GetMapping(value = "privacy", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage privacy() throws IOException { + PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + Resource[] csvResources = pathMatchingResourcePatternResolver.getResources("classpath*:/privacy/*.md"); + List resourceList = Arrays.stream(csvResources) + .sorted((o1, o2) -> StrUtil.compare(o1.getFilename(), o2.getFilename(), true)) + .collect(Collectors.toList()); + Resource first = CollUtil.getFirst(resourceList); + Assert.notNull(first, I18nMessageUtil.get("i18n.private_file_not_found.ee45")); + return JsonMessage.success("", IoUtil.readUtf8(first.getInputStream())); + } + + /** + * 擅自修改或者删除版权信息有法律风险,请尊重开源协议,不要擅自修改版本信息,否则可能承担法律责任。 + *

+ * 请严格遵循相关组件开源协议,擅自修改造成的侵权行为,由修改者自行承担全部法律责任。 + * + * @return json + */ + @GetMapping(value = "thank-dependency", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage thankDependency() { + InputStream inputStream = ResourceUtil.getStream("classpath:/thank-dependency.json"); + String data = IoUtil.readUtf8(inputStream); + JSONArray jsonArray = JSONArray.parseArray(data); + jsonArray.sort((o1, o2) -> { + JSONObject jsonObject1 = (JSONObject) o1; + JSONObject jsonObject2 = (JSONObject) o2; + String name = jsonObject1.getString("name"); + String name1 = jsonObject2.getString("name"); + return StrUtil.compareIgnoreCase(name, name1, true); + }); + return JsonMessage.success("", jsonArray); + } +} 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 0000000000..7f6d2e326e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/DataStatController.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.core.util.ObjectUtil; +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.assets.server.MachineDockerServer; +import org.dromara.jpom.func.assets.server.MachineNodeServer; +import org.dromara.jpom.func.assets.server.MachineSshServer; +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.model.user.UserModel; +import org.dromara.jpom.permission.SystemPermission; +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.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.service.user.UserService; +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.List; +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; + private final UserService userService; + private final WorkspaceService workspaceService; + private final ClusterInfoService clusterInfoService; + private final MachineNodeServer machineNodeServer; + private final MachineSshServer machineSshServer; + private final MachineDockerServer machineDockerServer; + + public DataStatController(NodeService nodeService, + ProjectInfoCacheService projectInfoCacheService, + NodeScriptServer nodeScriptServer, + OutGivingServer outGivingServer, + SshService sshService, + SshCommandService sshCommandService, + ScriptServer scriptServer, + DockerInfoService dockerInfoService, + FileStorageService fileStorageService, + StaticFileStorageService staticFileStorageService, + DockerSwarmInfoService dockerSwarmInfoService, + UserService userService, + WorkspaceService workspaceService, + ClusterInfoService clusterInfoService, + MachineNodeServer machineNodeServer, + MachineSshServer machineSshServer, + MachineDockerServer machineDockerServer) { + 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; + this.userService = userService; + this.workspaceService = workspaceService; + this.clusterInfoService = clusterInfoService; + this.machineNodeServer = machineNodeServer; + this.machineSshServer = machineSshServer; + this.machineDockerServer = machineDockerServer; + } + + @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); + } + + @RequestMapping(value = "system", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @SystemPermission + public IJsonMessage> system(HttpServletRequest request) { + Map map = new HashMap<>(10); + { + long count = userService.count(); + map.put("userCount", count); + { + UserModel userModel = new UserModel(); + userModel.setSystemUser(1); + long systemUserCount = userService.count(userModel); + map.put("systemUserCount", systemUserCount); + } + { + UserModel userModel = new UserModel(); + userModel.setStatus(0); + long disableUserCount = userService.count(userModel); + map.put("disableUserCount", disableUserCount); + } + String sql = "select count(1) from " + userService.getTableName() + " where twoFactorAuthKey is null or twoFactorAuthKey=''"; + Number closeTwoFactorAuth = ObjectUtil.defaultIfNull(userService.queryNumber(sql), 0); + map.put("openTwoFactorAuth", count - closeTwoFactorAuth.intValue()); + } + { + long workspaceCount = workspaceService.count(); + long clusterCount = clusterInfoService.count(); + map.put("workspaceCount", workspaceCount); + map.put("clusterCount", clusterCount); + } + { + String sql = "select status,count(1) as count from " + machineDockerServer.getTableName() + " group by status"; + List query = machineDockerServer.query(sql); + map.put("dockerStat", query); + } + { + String sql = "select status,count(1) as count from " + machineSshServer.getTableName() + " group by status"; + List query = machineSshServer.query(sql); + map.put("sshStat", query); + } + { + String sql = "select status,count(1) as count from " + machineNodeServer.getTableName() + " group by status"; + List query = machineNodeServer.query(sql); + map.put("nodeStat", query); + } + 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 new file mode 100644 index 0000000000..a15e18c302 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/IndexControl.java @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.cache.Cache; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.*; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.ContentType; +import cn.hutool.system.SystemUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +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.JpomManifest; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.UrlRedirectUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.configuration.NodeConfig; +import org.dromara.jpom.configuration.WebConfig; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.func.user.controller.UserNotificationController; +import org.dromara.jpom.func.user.dto.UserNotificationDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.system.ServerConfig; +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.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 首页 + * + * @author bwcx_jzy + */ +@RestController +@RequestMapping(value = "/") +@Slf4j +public class IndexControl extends BaseServerController { + + private final UserService userService; + private final SystemParametersServer systemParametersServer; + private final WebConfig webConfig; + private final NodeConfig nodeConfig; + private final DbExtConfig dbExtConfig; + + public IndexControl(UserService userService, + SystemParametersServer systemParametersServer, + ServerConfig serverConfig, + DbExtConfig dbExtConfig) { + this.userService = userService; + this.systemParametersServer = systemParametersServer; + this.webConfig = serverConfig.getWeb(); + this.nodeConfig = serverConfig.getNode(); + this.dbExtConfig = dbExtConfig; + } + + + /** + * 加载首页 + * + * @api {get} / 加载首页 服务端前端页面 + * @apiGroup index + * @apiSuccess {String} BODY HTML + */ + @GetMapping(value = {"index", "", "/", "index.html"}, produces = MediaType.TEXT_HTML_VALUE) + @NotLogin + public void index(HttpServletResponse response, HttpServletRequest request) { + this.toIndex(response, request, StrUtil.EMPTY); + } + + @GetMapping(value = "oauth2-{provide}", produces = MediaType.TEXT_HTML_VALUE) + @NotLogin + public void oauth2(HttpServletResponse response, HttpServletRequest request, @PathVariable String provide) { + this.toIndex(response, request, provide); + } + + private void toIndex(HttpServletResponse response, HttpServletRequest request, String oauth2Provide) { + InputStream inputStream = ResourceUtil.getStream("classpath:/dist/index.html"); + String html = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); + //

+ String path = ExtConfigBean.getPath(); + File file = FileUtil.file(String.format("%s/script/common.js", path)); + if (file.exists()) { + String jsCommonContext = FileUtil.readString(file, CharsetUtil.CHARSET_UTF_8); + //
+ String[] commonJsTemps = new String[]{"
", "
"}; + for (String item : commonJsTemps) { + html = StrUtil.replace(html, item, jsCommonContext); + } + } + String language = I18nMessageUtil.tryGetSystemLanguage(); + // + String proxyPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); + html = StrUtil.replace(html, "", proxyPath); + // + html = StrUtil.replace(html, "", ""); + // + int webApiTimeout = webConfig.getApiTimeout(); + html = StrUtil.replace(html, "", String.valueOf(TimeUnit.SECONDS.toMillis(webApiTimeout))); + html = StrUtil.replace(html, "", String.valueOf(nodeConfig.getUploadFileSliceSize())); + html = StrUtil.replace(html, "", String.valueOf(nodeConfig.getUploadFileConcurrent())); + html = StrUtil.replace(html, "", oauth2Provide); + html = StrUtil.replace(html, "", webConfig.getTransportEncryption()); + html = StrUtil.replace(html, "", language); + // 修改网页标题 + String title = ReUtil.get(".*?", html, 0); + if (StrUtil.isNotEmpty(title)) { + html = StrUtil.replace(html, title, "" + webConfig.getName() + ""); + } + ServletUtil.write(response, html, ContentType.TEXT_HTML.getValue()); + } + + /** + * logo 图片 + * + * @api {get} logo_image logo 图片 + * @apiGroup index + * @apiSuccess {Object} BODY image + */ + @RequestMapping(value = "logo-image", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage logoImage(HttpServletResponse response) { + String logoFile = webConfig.getLogoFile(); + String imageSrc = this.loadImageSrc(response, logoFile, "classpath:/logo/jpom.png", "jpg", "png", "gif"); + return JsonMessage.success("", imageSrc); + } + + /** + * logo 图片 + * + * @api {get} logo_image logo 图片 + * @apiGroup index + * @apiSuccess {Object} BODY image + */ + @RequestMapping(value = "favicon.ico", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE) + @NotLogin + public void favicon(HttpServletResponse response) throws IOException { + String iconFile = webConfig.getIconFile(); + this.loadImage(response, iconFile, "classpath:/logo/favicon.ico", "ico", "png"); + } + + private void loadImage(HttpServletResponse response, String imgFile, String defaultResource, String... suffix) throws IOException { + if (StrUtil.isNotEmpty(imgFile)) { + if (Validator.isMatchRegex(RegexPool.URL_HTTP, imgFile)) { + // 重定向 + response.sendRedirect(imgFile); + return; + } + File file = FileUtil.file(imgFile); + if (FileUtil.isFile(file)) { + String type = FileTypeUtil.getType(file); + String extName = FileUtil.extName(file); + if (StrUtil.equalsAnyIgnoreCase(type, suffix) || StrUtil.equalsAnyIgnoreCase(extName, suffix)) { + ServletUtil.write(response, file); + return; + } + } + } + // favicon ico + InputStream inputStream = ResourceUtil.getStream(defaultResource); + ServletUtil.write(response, inputStream, MediaType.IMAGE_PNG_VALUE); + } + + private String loadImageSrc(HttpServletResponse response, String imgFile, String defaultResource, String... suffix) { + if (StrUtil.isNotEmpty(imgFile)) { + if (Validator.isMatchRegex(RegexPool.URL_HTTP, imgFile)) { + // 重定向 + return imgFile; + } + File file = FileUtil.file(imgFile); + if (FileUtil.isFile(file)) { + String type = FileTypeUtil.getType(file); + String extName = FileUtil.extName(file); + if (StrUtil.equalsAnyIgnoreCase(type, suffix) || StrUtil.equalsAnyIgnoreCase(extName, suffix)) { + ServletUtil.write(response, file); + String encode = Base64.encode(file); + String mimeType = FileUtil.getMimeType(file.toPath()); + return URLUtil.getDataUriBase64(mimeType, encode); + } + } + } + // favicon ico + InputStream inputStream = ResourceUtil.getStream(defaultResource); + String encode = Base64.encode(inputStream); + return URLUtil.getDataUriBase64(MediaType.IMAGE_PNG_VALUE, encode); + } + + + /** + * @return json + * @author Hotstrip + *

+ * check if need to init system + * @api {get} check-system 检查是否需要初始化系统 + * @apiGroup index + * @apiUse defResultJson + * @apiSuccess {String} routerBase 二级地址 + * @apiSuccess {String} name 系统名称 + * @apiSuccess {String} subTitle 主页面副标题 + * @apiSuccess {String} loginTitle 登录也标题 + * @apiSuccess {String} disabledGuide 是否禁用引导 + * @apiSuccess (222) {Object} data 系统还没有超级管理员需要初始化 + */ + @NotLogin + @RequestMapping(value = ServerConst.CHECK_SYSTEM, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage checkSystem(HttpServletRequest request) { + JSONObject data = new JSONObject(); + data.put("routerBase", UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH)); + // + data.put("name", webConfig.getName()); + data.put("subTitle", webConfig.getSubTitle()); + data.put("loginTitle", webConfig.getLoginTitle()); + data.put("disabledGuide", webConfig.isDisabledGuide()); + //data.put("disabledCaptcha", webConfig.isDisabledCaptcha()); + data.put("notificationPlacement", webConfig.getNotificationPlacement()); + data.put("installId", JpomManifest.getInstance().getInstallId()); + // 用于判断是否属于容器部署 + boolean inDocker = StrUtil.isNotEmpty(SystemUtil.get("JPOM_PKG")); + List extendPlugins = new ArrayList<>(); + if (inDocker) { + extendPlugins.add("inDocker"); + } + // 验证 git 仓库信息 + try { + IPlugin plugin = PluginFactory.getPlugin("git-clone"); + Map map = new HashMap<>(); + boolean systemGit = (boolean) plugin.execute("systemGit", map); + if (systemGit) { + extendPlugins.add("system-git"); + } + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.check_git_client_exception.42a3"), e); + } + data.put("extendPlugins", extendPlugins); + if (userService.canUse()) { + return new JsonMessage<>(200, "", data); + } + return new JsonMessage<>(222, I18nMessageUtil.get("i18n.need_initialize_system.fb62"), data); + } + + + /** + * @return json + * @api {post} menus_data.json 获取系统菜单相关数据 + * @apiGroup index + * @apiUse loginUser + * @apiParam {String} nodeId 节点ID + * @apiSuccess {JSON} data 菜单相关字段 + */ + @RequestMapping(value = "menus_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> menusData(HttpServletRequest request) { + UserModel userModel = getUserModel(); + String workspaceId = nodeService.getCheckUserWorkspace(request); + JSONObject config = systemParametersServer.getConfigDefNewInstance(StrUtil.format("menus_config_{}", workspaceId), JSONObject.class); + String language = I18nMessageUtil.tryGetNormalLanguage(); + // 菜单 + InputStream inputStream = ResourceUtil.getStream("classpath:/menus/" + language + "/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, 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, showArray, request); + }).collect(Collectors.toList()); + if (collect.isEmpty()) { + return false; + } + jsonObject.put("childs", collect); + } + return true; + }).collect(Collectors.toList()); + Assert.notEmpty(jsonArray, I18nMessageUtil.get("i18n.no_menus_contact_admin.cfec")); + return JsonMessage.success("", collect1); + } + + /** + * @return json + * @api {post} menus_data.json 获取系统菜单相关数据 + * @apiGroup index + * @apiUse loginUser + * @apiParam {String} nodeId 节点ID + * @apiSuccess {JSON} data 菜单相关字段 + */ + @RequestMapping(value = "system_menus_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @SystemPermission + public IJsonMessage> systemMenusData(HttpServletRequest request) { + UserModel userModel = getUserModel(); + String language = I18nMessageUtil.tryGetNormalLanguage(); + // 菜单 + InputStream inputStream = ResourceUtil.getStream("classpath:/menus/" + language + "/system.json"); + 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, 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, request); + }).collect(Collectors.toList()); + if (collect.isEmpty()) { + return false; + } + jsonObject.put("childs", collect); + } + return true; + }).collect(Collectors.toList()); + Assert.notEmpty(jsonArray, I18nMessageUtil.get("i18n.no_menus_contact_admin.cfec")); + return JsonMessage.success("", collect1); + } + + 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)) { + return false; + } + } + String role = jsonObject.getString("role"); + if (StrUtil.equals(role, UserModel.SYSTEM_ADMIN) && !userModel.isSuperSystemUser()) { + // 超级理员权限 + return false; + } + // 判断菜单显示 + if (CollUtil.isNotEmpty(showArray) && !userModel.isSuperSystemUser()) { + String id = jsonObject.getString("id"); + if (!CollUtil.contains(showArray, id)) { + boolean present = showArray.stream().anyMatch(o -> { + String str = StrUtil.toString(o); + return StrUtil.startWith(str, id + StrUtil.COLON) || StrUtil.endWith(str, StrUtil.COLON + id); + }); + if (!present) { + return false; + } + } + } + // 系统管理员权限 + boolean system = StrUtil.equals(role, "system"); + if (system) { + return userModel.isSystemUser(); + } + return true; + } + + /** + * 生成分片id + * + * @return json + */ + @GetMapping(value = "generate-sharding-id", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage generateShardingId() { + Cache shardingIds = BaseServerController.SHARDING_IDS; + int size = shardingIds.size(); + Assert.state(size <= 100, I18nMessageUtil.get("i18n.max_concurrent_shard_ids.f89c")); + String uuid = IdUtil.fastSimpleUUID(); + shardingIds.put(uuid, uuid); + return JsonMessage.success(uuid, uuid); + } + + /** + * 获取通知 + * + * @return json + */ + @GetMapping(value = "system-notification", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getNotification() { + UserNotificationDto notificationDto = systemParametersServer.getConfigDefNewInstance(UserNotificationController.KEY, UserNotificationDto.class); + return JsonMessage.success("", notificationDto); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/InstallController.java b/modules/server/src/main/java/org/dromara/jpom/controller/InstallController.java new file mode 100644 index 0000000000..f398f553be --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/InstallController.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +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.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.LoginInterceptor; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.common.validator.ValidatorConfig; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.controller.user.UserWorkspaceModel; +import org.dromara.jpom.model.dto.UserLoginDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.user.UserBindWorkspaceService; +import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.util.TwoFactorAuthUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 初始化程序 + * + * @author bwcx_jzy + * @since 2019/2/22 + */ +@RestController +@Slf4j +public class InstallController extends BaseServerController { + + + private final UserService userService; + private final UserBindWorkspaceService userBindWorkspaceService; + + public InstallController(UserService userService, + UserBindWorkspaceService userBindWorkspaceService) { + this.userService = userService; + this.userBindWorkspaceService = userBindWorkspaceService; + } + + /** + * 初始化提交 + * + * @param userName 系统管理员登录名 + * @param userPwd 系统管理员的登录密码 + * @return json + * @api {post} install_submit.json 初始化提交 + * @apiGroup index + * @apiUse defResultJson + * @apiParam {String} userName 系统管理员登录名 + * @apiParam {String} userPwd 设置的登录密码 sha1 后传入 + * @apiSuccess {JSON} data.tokenData token 相关信息 + * @apiSuccess {String} data.mfaKey 二次验证的key + * @apiSuccess {String} data.url 二次验证的二维码相关字符串用户快速扫码导入 + */ + @PostMapping(value = "install_submit.json", produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage installSubmit( + @ValidatorConfig(value = { + @ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "i18n.login_name_cannot_be_empty.9a99"), + @ValidatorItem(value = ValidatorRule.NOT_BLANK, range = UserModel.USER_NAME_MIN_LEN + ":" + Const.ID_MAX_LEN, msg = "i18n.login_name_length_range.fe8d"), + @ValidatorItem(value = ValidatorRule.WORD, msg = "i18n.login_name_cannot_contain_chinese_and_special_characters.48a8") + }) String userName, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.password_cannot_be_empty.89b5") String userPwd) { + // + Assert.state(!userService.canUse(), I18nMessageUtil.get("i18n.system_already_initialized.743c")); + + boolean systemOccupyUserName = StrUtil.equalsAnyIgnoreCase(userName, UserModel.DEMO_USER, Const.SYSTEM_ID, UserModel.SYSTEM_ADMIN); + Assert.state(!systemOccupyUserName, I18nMessageUtil.get("i18n.login_name_already_taken_message.b01f")); + // 创建用户 + UserModel userModel = new UserModel(); + userModel.setName(UserModel.SYSTEM_OCCUPY_NAME.get()); + userModel.setId(userName); + userModel.setSalt(userService.generateSalt()); + userModel.setPassword(SecureUtil.sha1(userPwd + userModel.getSalt())); + userModel.setSystemUser(1); + userModel.setParent(UserModel.SYSTEM_ADMIN); + try { + BaseServerController.resetInfo(userModel); + userService.insert(userModel); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.initialize_user_failure.fe27"), e); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.initialization_failure.19e9") + e.getMessage()); + } + //自动登录 + setSessionAttribute(LoginInterceptor.SESSION_NAME, userModel); + UserLoginDto userLoginDto = userService.getUserJwtId(userModel); + List bindWorkspaceModels = userService.myWorkspace(userModel); + userLoginDto.setBindWorkspaceModels(bindWorkspaceModels); + // 二次验证信息 + JSONObject jsonObject = new JSONObject(); + String tfaKey = TwoFactorAuthUtils.generateTFAKey(); + jsonObject.put("mfaKey", tfaKey); + jsonObject.put("url", TwoFactorAuthUtils.generateOtpAuthUrl(userName, tfaKey)); + jsonObject.put("tokenData", userLoginDto); + return JsonMessage.success(I18nMessageUtil.get("i18n.initialization_success.4725"), jsonObject); + } + +} 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 new file mode 100644 index 0000000000..dd2fd636e4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/LoginControl.java @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.LFUCache; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.captcha.CircleCaptcha; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.ContentType; +import cn.hutool.jwt.JWT; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.LoginInterceptor; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.configuration.UserConfig; +import org.dromara.jpom.configuration.WebConfig; +import org.dromara.jpom.controller.user.UserWorkspaceModel; +import org.dromara.jpom.func.user.server.UserLoginLogServer; +import org.dromara.jpom.model.dto.UserLoginDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.dromara.jpom.oauth2.Oauth2Factory; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.JwtUtil; +import org.dromara.jpom.util.StringUtil; +import org.springframework.beans.factory.InitializingBean; +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 javax.servlet.http.HttpSession; +import java.awt.*; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 登录控制 + * + * @author bwcx_jzy + */ +@RestController +@Slf4j +public class LoginControl extends BaseServerController implements InitializingBean { + /** + * ip 禁止缓存 + */ + public static final LFUCache LFU_CACHE = new LFUCache<>(1000); + /** + * 登录需要两步验证 + */ + private static final TimedCache MFA_TOKEN = CacheUtil.newTimedCache(TimeUnit.MINUTES.toMillis(10)); + + private static final String LOGIN_CODE = "login_code"; + + private final UserService userService; + private final UserConfig userConfig; + private final WebConfig webConfig; + private final UserLoginLogServer userLoginLogServer; + + public LoginControl(UserService userService, + ServerConfig serverConfig, + UserLoginLogServer userLoginLogServer) { + this.userService = userService; + this.userConfig = serverConfig.getUser(); + this.webConfig = serverConfig.getWeb(); + this.userLoginLogServer = userLoginLogServer; + } + + /** + * 验证码 + */ + @RequestMapping(value = "rand-code", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage randCode(String theme) { + if (webConfig.isDisabledCaptcha()) { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.verification_code_disabled.349b")); + } + CircleCaptcha captcha = this.createCaptcha(theme); + setSessionAttribute(LOGIN_CODE, captcha.getCode()); + String base64Data = captcha.getImageBase64Data(); + return new JsonMessage<>(200, "", base64Data); + } + + 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(); + return circleCaptcha; + } + + /** + * 记录 ip 登录失败 + */ + private synchronized void ipError() { + String ip = getIp(); + int count = ObjectUtil.defaultIfNull(LFU_CACHE.get(ip), 0) + 1; + LFU_CACHE.put(ip, count, userConfig.getIpErrorLockTime().toMillis()); + } + + private synchronized void ipSuccess() { + String ip = getIp(); + LFU_CACHE.remove(ip); + } + + /** + * 当登录的ip 错误次数达到配置以上锁定当前ip + * + * @return true + */ + private boolean ipLock() { + if (userConfig.getAlwaysIpLoginError() <= 0) { + return false; + } + String ip = getIp(); + Integer count = LFU_CACHE.get(ip); + if (count == null) { + count = 0; + } + return count > userConfig.getAlwaysIpLoginError(); + } + + + /** + * 登录接口 + * + * @param loginName 登录名 + * @param userPwd 登录密码 + * @param code 验证码 + * @return json + */ + @PostMapping(value = "userLogin", produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + @Feature(cls = ClassFeature.USER, method = MethodFeature.EXECUTE, logResponse = false) + public IJsonMessage userLogin(@ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "i18n.login_info_required.973b") String loginName, + @ValidatorItem(value = ValidatorRule.NOT_EMPTY, msg = "i18n.login_info_required.973b") String userPwd, + String code, + HttpServletRequest request) { + if (this.ipLock()) { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.too_many_attempts.d88d")); + } + synchronized (loginName.intern()) { + UserModel userModel = userService.getByKey(loginName); + if (userModel == null) { + this.ipError(); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.login_failed_please_enter_correct_password_and_account.03b2")); + } + if (userModel.getStatus() != null && userModel.getStatus() == 0) { + userLoginLogServer.fail(userModel, 4, false, request); + return new JsonMessage<>(ServerConst.ACCOUNT_LOCKED, ServerConst.ACCOUNT_LOCKED_TIP.get()); + } + if (!webConfig.isDisabledCaptcha()) { + // 获取验证码 + String sCode = getSessionAttribute(LOGIN_CODE); + Assert.state(StrUtil.equalsIgnoreCase(code, sCode), I18nMessageUtil.get("i18n.correct_verification_code_required.ff0d")); + removeSessionAttribute(LOGIN_CODE); + } + UserModel updateModel = null; + try { + long lockTime = userModel.overLockTime(userConfig.getAlwaysLoginError()); + if (lockTime > 0) { + String msg = DateUtil.formatBetween(lockTime * 1000, BetweenFormatter.Level.SECOND); + updateModel = userModel.errorLock(userConfig.getAlwaysLoginError()); + this.ipError(); + userLoginLogServer.fail(userModel, 2, false, request); + return new JsonMessage<>(400, StrUtil.format(I18nMessageUtil.get("i18n.account_login_failed_too_many_times_locked.23b2"), msg)); + } + // 验证 + if (userService.simpleLogin(loginName, userPwd) != null) { + updateModel = UserModel.unLock(loginName); + this.ipSuccess(); + // 判断是否开启 两步验证 + boolean bindMfa = userService.hasBindMfa(loginName); + if (bindMfa) { + // + JSONObject jsonObject = new JSONObject(); + String uuid = IdUtil.fastSimpleUUID(); + MFA_TOKEN.put(uuid, loginName); + jsonObject.put("tempToken", uuid); + userLoginLogServer.success(userModel, 5, true, request); + return new JsonMessage<>(201, I18nMessageUtil.get("i18n.two_step_verification_code_required.7e86"), jsonObject); + } + UserLoginDto userLoginDto = this.createToken(userModel); + userLoginLogServer.success(userModel, 0, false, request); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.login_success.71fa"), userLoginDto); + } else { + updateModel = userModel.errorLock(userConfig.getAlwaysLoginError()); + this.ipError(); + userLoginLogServer.fail(userModel, 1, false, request); + return new JsonMessage<>(501, I18nMessageUtil.get("i18n.login_failed_please_enter_correct_password_and_account.03b2")); + } + } finally { + if (updateModel != null) { + userService.updateById(updateModel); + } + } + } + } + + /** + * 跳转到认证中心登录 + * + * @param request 请求对象 + * @return json + */ + @GetMapping(value = "oauth2-url", produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage oauth2LoginUrl(HttpServletRequest request, @ValidatorItem String provide) { + AuthRequest authRequest = Oauth2Factory.get(provide); + String authorize = authRequest.authorize(null); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("toUrl", authorize); + return JsonMessage.success("", jsonObject); + } + + /** + * 跳转到认证中心登录 + * + * @param provide 平台 + */ + @GetMapping(value = "oauth2-render-{provide}") + @NotLogin + public void oauth2UrlRender(HttpServletResponse response, @PathVariable String provide) { + try { + AuthRequest authRequest = Oauth2Factory.get(provide); + response.sendRedirect(authRequest.authorize(null)); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.oauth2_redirect_failed.6dcd"), provide, e.getMessage()); + ServletUtil.write(response, JsonMessage.getString(500, e.getMessage()), ContentType.JSON.toString()); + } + } + + /** + * oauth2 登录并获取token + * + * @param code 授权码 + * @param state state + * @param request 请求对象 + * @return json + */ + @PostMapping(value = "oauth2/login", produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + @Feature(cls = ClassFeature.USER, method = MethodFeature.EXECUTE, logResponse = false) + public IJsonMessage oauth2Callback(@ValidatorItem String code, + @ValidatorItem String provide, + String state, + HttpServletRequest request) { + AuthRequest authRequest = Oauth2Factory.get(provide); + AuthCallback authCallback = new AuthCallback(); + authCallback.setCode(code); + authCallback.setState(state); + + AuthResponse authResponse = authRequest.login(authCallback); + if (authResponse.ok()) { + AuthUser authUser = (AuthUser) authResponse.getData(); + String username = this.parseUsername(authUser); + UserModel userModel = userService.getByKey(username); + if (userModel == null) { + BaseOauth2Config oauth2Config = Oauth2Factory.getConfig(provide); + if (oauth2Config.autoCreteUser()) { + userModel = this.createUser(username, authUser, provide, oauth2Config.getPermissionGroup()); + } else { + return new JsonMessage<>(400, username + I18nMessageUtil.get("i18n.user_does_not_exist.8363")); + } + } + if (userModel.getStatus() != null && userModel.getStatus() == 0) { + userLoginLogServer.fail(userModel, 4, false, request); + return new JsonMessage<>(ServerConst.ACCOUNT_LOCKED, ServerConst.ACCOUNT_LOCKED_TIP.get()); + } + // + UserModel updateModel = UserModel.unLock(userModel.getId()); + userService.updateById(updateModel); + // + UserLoginDto userLoginDto = this.createToken(userModel); + userLoginLogServer.success(userModel, 6, false, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.login_success.71fa"), userLoginDto); + } + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.login_failure_O_auth2_message.3e91") + authResponse.getMsg()); + } + + /** + * 解析用户名 + * + * @param authUser 平台账号信息 + * @return 用户名 + */ + private String parseUsername(AuthUser authUser) { + String username = authUser.getUsername(); + String checkId = StrUtil.replace(username, "-", "_"); + if (Validator.isGeneral(checkId, UserModel.USER_NAME_MIN_LEN, Const.ID_MAX_LEN)) { + return username; + } + if (StrUtil.isNotEmpty(authUser.getEmail())) { + return authUser.getEmail(); + } + String uuid = authUser.getUuid(); + if (Validator.isGeneral(uuid, UserModel.USER_NAME_MIN_LEN, Const.ID_MAX_LEN)) { + return uuid; + } + throw new IllegalStateException(I18nMessageUtil.get("i18n.oauth2_login_failure.3841")); + } + + /** + * oauth2 创建用户账号 + * + * @param username 用户名 + * @param authUser 平台信息 + * @param source 来源平台 + * @param permissionGroup 权限组 + * @return 用户 + */ + private UserModel createUser(String username, AuthUser authUser, String source, String permissionGroup) { + // 创建用户 + UserModel where = new UserModel(); + where.setSystemUser(1); + List userModels = userService.listByBean(where); + UserModel first = CollUtil.getFirst(userModels); + Assert.notNull(first, I18nMessageUtil.get("i18n.system_admin_not_found.6f6c")); + UserModel userModel = new UserModel(); + userModel.setName(StrUtil.emptyToDefault(authUser.getNickname(), authUser.getUsername())); + userModel.setId(username); + userModel.setEmail(authUser.getEmail()); + userModel.setSalt(userService.generateSalt()); + String randomPwd = RandomUtil.randomString(UserModel.SALT_LEN); + String sha1Pwd = SecureUtil.sha1(randomPwd); + userModel.setPassword(SecureUtil.sha1(sha1Pwd + userModel.getSalt())); + userModel.setSystemUser(0); + userModel.setParent(first.getId()); + userModel.setSource(source); + // 绑定权限组 + List permissionGroupList = StrUtil.split(permissionGroup, StrUtil.AT, true, true); + if (CollUtil.isNotEmpty(permissionGroupList)) { + userModel.setPermissionGroup(CollUtil.join(permissionGroupList, StrUtil.AT, StrUtil.AT, StrUtil.AT)); + } + BaseServerController.resetInfo(first); + userService.insert(userModel); + return userModel; + } + + private UserLoginDto createToken(UserModel userModel) { + // 判断工作空间 + List bindWorkspaceModels = userService.myWorkspace(userModel); + Assert.notEmpty(bindWorkspaceModels, I18nMessageUtil.get("i18n.account_not_bound_to_any_workspace.fd61")); + UserLoginDto userLoginDto = userService.getUserJwtId(userModel); + // UserLoginDto userLoginDto = new UserLoginDto(JwtUtil.builder(userModel, jwtId), jwtId); + userLoginDto.setBindWorkspaceModels(bindWorkspaceModels); + // + setSessionAttribute(LoginInterceptor.SESSION_NAME, userModel); + return userLoginDto; + } + + @GetMapping(value = "mfa_verify", produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage mfaVerify(String token, String code, HttpServletRequest request) { + String userId = MFA_TOKEN.get(token); + if (StrUtil.isEmpty(userId)) { + return new JsonMessage<>(201, I18nMessageUtil.get("i18n.login_info_expired_please_re_login.fbbc")); + } + boolean mfaCode = userService.verifyMfaCode(userId, code); + Assert.state(mfaCode, I18nMessageUtil.get("i18n.verification_code_incorrect_retry.d88d")); + UserModel userModel = userService.getByKey(userId); + // + UserLoginDto userLoginDto = this.createToken(userModel); + MFA_TOKEN.remove(token); + userLoginLogServer.success(userModel, 0, true, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.login_success.71fa"), userLoginDto); + } + + /** + * 退出登录 + * + * @return json + */ + @RequestMapping(value = "logout2", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage logout(HttpSession session) { + session.invalidate(); + return JsonMessage.success(I18nMessageUtil.get("i18n.exit_successful.8150")); + } + + /** + * 刷新token + * + * @return json + */ + @RequestMapping(value = "renewal", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage renewalToken(HttpServletRequest request) { + String token = request.getHeader(ServerOpenApi.HTTP_HEAD_AUTHORIZATION); + if (StrUtil.isEmpty(token)) { + return new JsonMessage<>(ServerConst.AUTHORIZE_TIME_OUT_CODE, I18nMessageUtil.get("i18n.refresh_token_failure.de7f")); + } + JWT jwt = JwtUtil.readBody(token); + if (JwtUtil.expired(jwt, 0)) { + int renewal = userConfig.getTokenRenewal(); + if (jwt == null || renewal <= 0 || JwtUtil.expired(jwt, TimeUnit.MINUTES.toSeconds(renewal))) { + return new JsonMessage<>(ServerConst.AUTHORIZE_TIME_OUT_CODE, I18nMessageUtil.get("i18n.refresh_token_timeout.3291")); + } + } + UserModel userModel = userService.checkUser(JwtUtil.getId(jwt)); + if (userModel == null) { + return new JsonMessage<>(ServerConst.AUTHORIZE_TIME_OUT_CODE, I18nMessageUtil.get("i18n.no_user.3b69")); + } + UserLoginDto userLoginDto = userService.getUserJwtId(userModel); + userLoginLogServer.success(userModel, 3, false, request); + return JsonMessage.success("", userLoginDto); + } + + /** + * 获取 demo 账号的信息 + */ + @GetMapping(value = "login-config", produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage demoInfo() { + String userDemoTip = userConfig.getDemoTip(); + userDemoTip = StringUtil.convertFileStr(userDemoTip, StrUtil.EMPTY); + + JSONObject jsonObject = new JSONObject(); + if (StrUtil.isNotEmpty(userDemoTip) && userService.hasDemoUser()) { + JSONObject demo = new JSONObject(); + demo.put("msg", userDemoTip); + demo.put("user", UserModel.DEMO_USER); + jsonObject.put("demo", demo); + } + jsonObject.put("disabledCaptcha", webConfig.isDisabledCaptcha()); + Collection provides = Oauth2Factory.provides(); + jsonObject.put("oauth2Provides", provides); + return JsonMessage.success("", jsonObject); + } + + @Override + public void afterPropertiesSet() throws Exception { + try { + this.createCaptcha(null); + log.debug(I18nMessageUtil.get("i18n.server_captcha_available.5570")); + } catch (Throwable e) { + log.warn(I18nMessageUtil.get("i18n.server_captcha_generation_exception.54d0"), e); + webConfig.setDisabledCaptcha(true); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/MyErrorController.java b/modules/server/src/main/java/org/dromara/jpom/controller/MyErrorController.java new file mode 100644 index 0000000000..e94eb9a21f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/MyErrorController.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import org.dromara.jpom.common.interceptor.NotLogin; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author bwcx_jzy + * @since 2022/4/16 + */ +@Controller +@RequestMapping("${server.error.path:${error.path:/error}}") +@NotLogin +public class MyErrorController extends BaseMyErrorController { + public MyErrorController(ErrorAttributes errorAttributes) { + super(errorAttributes); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/ToolsController.java b/modules/server/src/main/java/org/dromara/jpom/controller/ToolsController.java new file mode 100644 index 0000000000..56ea4c6faf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ToolsController.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.net.Ipv4Util; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.pattern.CronPatternUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.Lombok; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 工具类 + * + * @author bwcx_jzy + * @since 2023/3/10 + */ +@RestController +@RequestMapping(value = "/tools") +public class ToolsController { + + @GetMapping(value = "cron", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> cron(@ValidatorItem String cron, @ValidatorItem int count, String date, boolean isMatchSecond) { + Date startDate = null; + Date endDate = null; + if (StrUtil.isNotEmpty(date)) { + List split = StrUtil.splitTrim(date, "~"); + try { + startDate = DateUtil.parse(split.get(0)); + startDate = DateUtil.beginOfDay(startDate); + endDate = DateUtil.parse(split.get(1)); + endDate = DateUtil.endOfDay(endDate); + } catch (Exception e) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.date_format_error.3d1c") + e.getMessage()); + } + } + try { + List dateList; + if (startDate != null) { + dateList = CronPatternUtil.matchedDates(cron, startDate, endDate, count, isMatchSecond); + } else { + dateList = CronPatternUtil.matchedDates(cron, DateTime.now(), count, isMatchSecond); + } + return JsonMessage.success("", dateList.stream().map(Date::getTime).collect(Collectors.toList())); + } catch (Exception e) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.cron_expression_incorrect.b41a") + e.getMessage()); + } + } + + @GetMapping(value = "ip-list", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> ipList() { + Collection networkInterfaces = NetUtil.getNetworkInterfaces(); + List collect = networkInterfaces.stream() + .sorted((o1, o2) -> 0) + .map(networkInterface -> { + boolean virtual = networkInterface.isVirtual(); + String name = networkInterface.getName(); + String displayName = networkInterface.getDisplayName(); + JSONObject jsonObject = new JSONObject(); + jsonObject.set("name", name); + jsonObject.set("displayName", displayName); + jsonObject.set("virtual", virtual); + try { + jsonObject.set("loopback", networkInterface.isLoopback()); + } catch (SocketException e) { + throw Lombok.sneakyThrow(e); + } + final Enumeration inetAddresses = networkInterface.getInetAddresses(); + JSONArray ips = new JSONArray(); + while (inetAddresses.hasMoreElements()) { + final InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress != null && !inetAddress.isLinkLocalAddress()) { + String hostAddress = inetAddress.getHostAddress(); + // 处理 Mac ip 地址 + hostAddress = StrUtil.subBefore(hostAddress, "%", true); + JSONObject parseIp = parseIp(hostAddress); + parseIp.set("ip", hostAddress); + ips.add(parseIp); + } + } + if (CollUtil.isEmpty(ips)) { + return null; + } + jsonObject.set("ips", ips); + return jsonObject; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + return JsonMessage.success("", collect); + } + + @GetMapping(value = "net-ping", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage ping(@ValidatorItem String host, @ValidatorItem int timeout) { + boolean ping = NetUtil.ping(host, (int) TimeUnit.SECONDS.toMillis(Math.max(1, timeout))); + // + JSONObject jsonObject = this.parseIp(host); + jsonObject.set("ping", ping); + return JsonMessage.success("", jsonObject); + } + + @GetMapping(value = "net-telnet", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage telnet(@ValidatorItem String host, int port, @ValidatorItem int timeout) { + InetSocketAddress address = NetUtil.createAddress(host, port); + boolean open = NetUtil.isOpen(address, (int) TimeUnit.SECONDS.toMillis(Math.max(1, timeout))); + // + JSONObject jsonObject = this.parseIp(host); + jsonObject.set("open", open); + return JsonMessage.success("", jsonObject); + } + + /** + * 解析 host 的 IP 地址类型 + * + * @param host 主机地址 + * @return json + */ + private JSONObject parseIp(String host, String... appendLabels) { + boolean ipv4 = Validator.isIpv4(host); + boolean ipv6 = Validator.isIpv6(host); + JSONObject jsonObject = new JSONObject(); + // + JSONArray labels = new JSONArray(); + for (String appendLabel : appendLabels) { + labels.put(appendLabel); + } + if (ipv4) { + labels.put("IPV4"); + // + String type = detectionType(host); + if (type != null) { + labels.add(type); + } + } + if (ipv6) { + labels.put("IPV6"); + } + if (!ipv4 && !ipv6) { + String ipByHost = NetUtil.getIpByHost(host); + if (!StrUtil.equals(ipByHost, host)) { + jsonObject.set("originalIP", ipByHost); + } + labels.put("DOMAIN"); + } + + jsonObject.set("labels", labels); + return jsonObject; + } + + + private static String detectionType(String ipAddress) { + if (Ipv4Util.LOCAL_IP.equals(ipAddress)) { + return "LOCAL"; + } + long ipNum = Ipv4Util.ipv4ToLong(ipAddress); + + long aBegin = Ipv4Util.ipv4ToLong("10.0.0.0"); + long aEnd = Ipv4Util.ipv4ToLong("10.255.255.255"); + if (isInclude(ipNum, aBegin, aEnd)) { + return "A"; + } + + long bBegin = Ipv4Util.ipv4ToLong("172.16.0.0"); + long bEnd = Ipv4Util.ipv4ToLong("172.31.255.255"); + if (isInclude(ipNum, bBegin, bEnd)) { + return "B"; + } + + long cBegin = Ipv4Util.ipv4ToLong("192.168.0.0"); + long cEnd = Ipv4Util.ipv4ToLong("192.168.255.255"); + if (isInclude(ipNum, cBegin, cEnd)) { + return "C"; + } + + long pBegin = Ipv4Util.ipv4ToLong("20.0.0.0"); + long pEnd = Ipv4Util.ipv4ToLong("223.255.255.255"); + if (isInclude(ipNum, pBegin, pEnd)) { + return "PUBLIC"; + } + return null; + } + + private static boolean isInclude(long userIp, long begin, long end) { + return (userIp >= begin) && (userIp <= end); + } +} 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 new file mode 100644 index 0000000000..fae58ba7be --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoController.java @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.IDockerConfigPlugin; +import org.dromara.jpom.build.BuildExecuteService; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.build.DockerYmlDsl; +import org.dromara.jpom.build.ResultDirFileAction; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.configuration.BuildExtConfig; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.data.RepositoryModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; +import org.dromara.jpom.service.dblog.RepositoryService; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 构建列表,新版本,数据存放到数据库,不再是文件了 + * 以前的数据会在程序启动时插入到数据库中 + * + * @author Hotstrip + * @since 2021-08-09 + */ +@RestController +@Feature(cls = ClassFeature.BUILD) +public class BuildInfoController extends BaseServerController { + + private final DbBuildHistoryLogService dbBuildHistoryLogService; + private final SshService sshService; + private final BuildInfoService buildInfoService; + private final RepositoryService repositoryService; + private final BuildExecuteService buildExecuteService; + private final DockerInfoService dockerInfoService; + private final ScriptServer scriptServer; + private final BuildExtConfig buildExtConfig; + protected final MachineDockerServer machineDockerServer; + + public BuildInfoController(DbBuildHistoryLogService dbBuildHistoryLogService, + SshService sshService, + BuildInfoService buildInfoService, + RepositoryService repositoryService, + BuildExecuteService buildExecuteService, + DockerInfoService dockerInfoService, + ScriptServer scriptServer, + BuildExtConfig buildExtConfig, + MachineDockerServer machineDockerServer) { + this.dbBuildHistoryLogService = dbBuildHistoryLogService; + this.sshService = sshService; + this.buildInfoService = buildInfoService; + this.repositoryService = repositoryService; + this.buildExecuteService = buildExecuteService; + this.dockerInfoService = dockerInfoService; + this.scriptServer = scriptServer; + this.buildExtConfig = buildExtConfig; + this.machineDockerServer = machineDockerServer; + } + + /** + * load build list with params + * + * @return json + */ + @RequestMapping(value = "/build/list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getBuildList(HttpServletRequest request) { + // load list with page + PageResultDto page = buildInfoService.listPage(request); + page.each(buildInfoModel -> { + // 获取源码目录是否存在 + File source = BuildUtil.getSourceById(buildInfoModel.getId()); + buildInfoModel.setSourceDirExist(FileUtil.exist(source)); + // + File file = BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId(), buildInfoModel.getResultDirFile()); + buildInfoModel.setResultHasFile(FileUtil.exist(file)); + }); + return JsonMessage.success("", page); + } + + /** + * load build list with params + * + * @return json + */ + @GetMapping(value = "/build/get", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getBuildListAll(String id, HttpServletRequest request) { + // load list with page + BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, request); + Assert.notNull(buildInfoModel, I18nMessageUtil.get("i18n.build_not_exist.c2ac")); + // 获取源码目录是否存在 + File source = BuildUtil.getSourceById(buildInfoModel.getId()); + buildInfoModel.setSourceDirExist(FileUtil.exist(source)); + // + File file = BuildUtil.getHistoryPackageFile(buildInfoModel.getId(), buildInfoModel.getBuildId(), buildInfoModel.getResultDirFile()); + buildInfoModel.setResultHasFile(FileUtil.exist(file)); + return JsonMessage.success("", buildInfoModel); + } + + /** + * load build list with params + * + * @return json + */ + @GetMapping(value = "/build/list_group_all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getBuildGroupAll(HttpServletRequest request) { + // load list with page + List group = buildInfoService.listGroup(request); + return JsonMessage.success("", group); + } + + /** + * edit build info + * + * @param id 构建ID + * @param name 构建名称 + * @param repositoryId 仓库ID + * @param resultDirFile 构建产物目录 + * @param script 构建命令 + * @param releaseMethod 发布方法 + * @param branchName 分支名称 + * @param webhook webhook + * @param extraData 构建的其他信息 + * @param autoBuildCron 自动构建表达是 + * @param branchTagName 标签名 + * @return json + */ + @RequestMapping(value = "/build/edit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage updateBuild(String id, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.build_name_not_empty.4154") String name, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.repository_info_cannot_be_empty.67d2") String repositoryId, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.build_product_dir_not_empty.ba06", range = "1:200") String resultDirFile, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.build_command_not_empty.2e37") String script, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.incorrect_publish_method.e095") int releaseMethod, + String branchName, String branchTagName, String webhook, String autoBuildCron, + String extraData, String group, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.build_method_incorrect.5319") int buildMode, + String aliasCode, + @ValidatorItem(value = ValidatorRule.NUMBERS, msg = "i18n.correct_retention_days_required.d542") Integer resultKeepDay, + String buildEnvParameter, + HttpServletRequest request) { + // 根据 repositoryId 查询仓库信息 + RepositoryModel repositoryModel = repositoryService.getByKey(repositoryId, request); + Assert.notNull(repositoryModel, I18nMessageUtil.get("i18n.invalid_repository_info.b4ad")); + // 如果是 GIT 需要检测分支是否存在 + if (RepositoryModel.RepoType.Git.getCode() == repositoryModel.getRepoType()) { + Assert.hasText(branchName, I18nMessageUtil.get("i18n.branch_required.5095")); + } else if (RepositoryModel.RepoType.Svn.getCode() == repositoryModel.getRepoType()) { + // 如果是 SVN + branchName = "trunk"; + } + ResultDirFileAction resultDirFileAction = ResultDirFileAction.parse(resultDirFile); + resultDirFileAction.check(); + // + Assert.state(buildMode == 0 || buildMode == 1, I18nMessageUtil.get("i18n.select_correct_build_method.84c4")); + if (buildMode == 1) { + // 验证 dsl 内容 + this.checkDocker(script, request); + // 容器构建不能使用 ant 模式 + Assert.state(resultDirFileAction.getType() == ResultDirFileAction.Type.ORIGINAL, I18nMessageUtil.get("i18n.container_build_product_path_cannot_use_ant_pattern.ddc7")); + } else { + if (StrUtil.startWith(script, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(script, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKeyAndGlobal(scriptId, request, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + } + } + if (buildExtConfig.isCheckDeleteCommand()) { + // 判断删除命令 + Assert.state(!CommandUtil.checkContainsDel(script), I18nMessageUtil.get("i18n.build_command_no_delete.df52")); + } + // 查询构建信息 + BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, request); + buildInfoModel = ObjectUtil.defaultIfNull(buildInfoModel, new BuildInfoModel()); + // 设置参数 + Opt.ofBlankAble(webhook).ifPresent(s -> Validator.validateMatchRegex(RegexPool.URL_HTTP, s, I18nMessageUtil.get("i18n.invalid_webhooks_address.d836"))); + Opt.ofBlankAble(aliasCode).ifPresent(s -> Validator.validateGeneral(s, I18nMessageUtil.get("i18n.alias_code_validation.8b99"))); + // + buildInfoModel.setAutoBuildCron(this.checkCron(autoBuildCron)); + buildInfoModel.setWebhook(webhook); + buildInfoModel.setRepositoryId(repositoryId); + buildInfoModel.setName(name); + buildInfoModel.setAliasCode(aliasCode); + buildInfoModel.setBranchName(branchName); + buildInfoModel.setBranchTagName(branchTagName); + buildInfoModel.setResultDirFile(resultDirFile); + buildInfoModel.setScript(script); + buildInfoModel.setGroup(group); + buildInfoModel.setResultKeepDay(resultKeepDay); + buildInfoModel.setBuildMode(buildMode); + buildInfoModel.setBuildEnvParameter(buildEnvParameter); + // 发布方式 + BuildReleaseMethod releaseMethod1 = BaseEnum.getEnum(BuildReleaseMethod.class, releaseMethod); + Assert.notNull(releaseMethod1, I18nMessageUtil.get("i18n.incorrect_publish_method.e095")); + buildInfoModel.setReleaseMethod(releaseMethod1.getCode()); + // 把 extraData 信息转换成 JSON 字符串 ,不能直接使用 io.jpom.build.BuildExtraModule + JSONObject jsonObject = JSON.parseObject(extraData); + + // 验证发布方式 和 extraData 信息 + if (releaseMethod1 == BuildReleaseMethod.Project) { + this.formatProject(jsonObject); + } else if (releaseMethod1 == BuildReleaseMethod.Ssh) { + this.formatSsh(jsonObject, request); + } else if (releaseMethod1 == BuildReleaseMethod.Outgiving) { + this.formatOutGiving(jsonObject); + } else if (releaseMethod1 == BuildReleaseMethod.LocalCommand) { + this.formatLocalCommand(jsonObject); + jsonObject.put("releaseMethodDataId", "LocalCommand"); + } else if (releaseMethod1 == BuildReleaseMethod.DockerImage) { + // dockerSwarmId default + String dockerSwarmId = this.formatDocker(jsonObject, request); + jsonObject.put("releaseMethodDataId", dockerSwarmId); + } + // 检查关联数据ID + buildInfoModel.setReleaseMethodDataId(jsonObject.getString("releaseMethodDataId")); + if (buildInfoModel.getReleaseMethod() != BuildReleaseMethod.No.getCode()) { + Assert.hasText(buildInfoModel.getReleaseMethodDataId(), I18nMessageUtil.get("i18n.no_publish_distribution_related_data_id.a077")); + } + // 验证服务端脚本 + String noticeScriptId = jsonObject.getString("noticeScriptId"); + if (StrUtil.isNotEmpty(noticeScriptId)) { + List list = StrUtil.splitTrim(noticeScriptId, StrUtil.COMMA); + for (String noticeScriptIdItem : list) { + ScriptModel scriptModel = scriptServer.getByKey(noticeScriptIdItem, request); + Assert.notNull(scriptModel, I18nMessageUtil.get("i18n.server_script_not_exist.de24")); + } + } + buildInfoModel.setExtraData(jsonObject.toJSONString()); + + // 新增构建信息 + if (StrUtil.isEmpty(id)) { + // set default buildId + buildInfoModel.setBuildId(0); + buildInfoService.insert(buildInfoModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.addition_succeeded.3fda"), buildInfoModel.getId()); + } + + buildInfoService.updateById(buildInfoModel, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be"), buildInfoModel.getId()); + } + + private void checkDocker(String script, HttpServletRequest request) { + String workspaceId = buildInfoService.getCheckUserWorkspace(request); + DockerYmlDsl build = DockerYmlDsl.build(script); + // + IDockerConfigPlugin plugin = (IDockerConfigPlugin) PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + build.check(dockerInfoService, machineDockerServer, workspaceId, plugin); + // + String fromTag = build.getFromTag(); + if (StrUtil.isNotEmpty(fromTag)) { + // + int count = dockerInfoService.countByTag(workspaceId, fromTag); + Assert.state(count > 0, fromTag + I18nMessageUtil.get("i18n.docker_not_found.2a2e")); + } + } + + /** + * 验证构建信息 + * 当发布方式为【SSH】的时候 + * + * @param jsonObject 配置信息 + */ + private void formatSsh(JSONObject jsonObject, HttpServletRequest request) { + // 发布方式 + String releaseMethodDataId = jsonObject.getString("releaseMethodDataId_3"); + Assert.hasText(releaseMethodDataId, I18nMessageUtil.get("i18n.ssh_item_distribution_required.2884")); + + String releasePath = jsonObject.getString("releasePath"); + Assert.hasText(releasePath, I18nMessageUtil.get("i18n.publish_to_ssh_directory_required.56a6")); + releasePath = FileUtil.normalize(releasePath); + String releaseCommand = jsonObject.getString("releaseCommand"); + List strings = StrUtil.splitTrim(releaseMethodDataId, StrUtil.COMMA); + for (String releaseMethodDataIdItem : strings) { + SshModel sshServiceItem = sshService.getByKey(releaseMethodDataIdItem, request); + Assert.notNull(sshServiceItem, I18nMessageUtil.get("i18n.no_corresponding_ssh_item.2deb")); + // + if (releasePath.startsWith(StrUtil.SLASH)) { + // 以根路径开始 + List fileDirs = sshServiceItem.fileDirs(); + Assert.notEmpty(fileDirs, sshServiceItem.getName() + I18nMessageUtil.get("i18n.ssh_unauthorized_directory.df78")); + + boolean find = false; + for (String fileDir : fileDirs) { + if (FileUtil.isSub(new File(fileDir), new File(releasePath))) { + find = true; + } + } + Assert.state(find, sshServiceItem.getName() + I18nMessageUtil.get("i18n.ssh_unauthorized_directory.df78")); + } + // 发布命令 + if (StrUtil.isNotEmpty(releaseCommand)) { + int length = releaseCommand.length(); + Assert.state(length <= 4000, I18nMessageUtil.get("i18n.publish_command_length_limit.66b0")); + //return JsonMessage.getString(405, "请输入发布命令"); + String[] commands = StrUtil.splitToArray(releaseCommand, StrUtil.LF); + + for (String commandItem : commands) { + boolean checkInputItem = SshModel.checkInputItem(sshServiceItem, commandItem); + Assert.state(checkInputItem, sshServiceItem.getName() + I18nMessageUtil.get("i18n.publish_command_contains_forbidden_command.097d")); + } + } + } + jsonObject.put("releaseMethodDataId", releaseMethodDataId); + } + + private String formatDocker(JSONObject jsonObject, HttpServletRequest request) { + // 发布命令 + String dockerfile = jsonObject.getString("dockerfile"); + Assert.hasText(dockerfile, I18nMessageUtil.get("i18n.dockerfile_path_required.69ac")); + String fromTag = jsonObject.getString("fromTag"); + if (StrUtil.isNotEmpty(fromTag)) { + Assert.hasText(fromTag, I18nMessageUtil.get("i18n.docker_label_required.b690")); + String workspaceId = dockerInfoService.getCheckUserWorkspace(request); + int count = dockerInfoService.countByTag(workspaceId, fromTag); + Assert.state(count > 0, I18nMessageUtil.get("i18n.docker_tag_incorrect.8b62")); + } + String dockerTag = jsonObject.getString("dockerTag"); + Assert.hasText(dockerTag, I18nMessageUtil.get("i18n.image_tag_required.92cf")); + // + String dockerSwarmId = jsonObject.getString("dockerSwarmId"); + if (StrUtil.isEmpty(dockerSwarmId)) { + return "DockerImage"; + } + String dockerSwarmServiceName = jsonObject.getString("dockerSwarmServiceName"); + Assert.hasText(dockerSwarmServiceName, I18nMessageUtil.get("i18n.service_name_in_cluster_required.5446")); + return dockerSwarmId; + } + + private void formatLocalCommand(JSONObject jsonObject) { + // 发布命令 + String releaseCommand = jsonObject.getString("releaseCommand"); + if (StrUtil.isNotEmpty(releaseCommand)) { + int length = releaseCommand.length(); + Assert.state(length <= 4000, I18nMessageUtil.get("i18n.publish_command_length_limit.66b0")); + } + } + + private void formatOutGiving(JSONObject jsonObject) { + String releaseMethodDataId = jsonObject.getString("releaseMethodDataId_1"); + Assert.hasText(releaseMethodDataId, I18nMessageUtil.get("i18n.distribution_project_required.2560")); + jsonObject.put("releaseMethodDataId", releaseMethodDataId); + // + this.checkProjectSecondaryDirectory(jsonObject); + } + + /** + * 验证构建信息 + * 当发布方式为【项目】的时候 + * + * @param jsonObject 配置信息 + */ + private void formatProject(JSONObject jsonObject) { + String releaseMethodDataId2Node = jsonObject.getString("releaseMethodDataId_2_node"); + String releaseMethodDataId2Project = jsonObject.getString("releaseMethodDataId_2_project"); + + Assert.state(!StrUtil.hasEmpty(releaseMethodDataId2Node, releaseMethodDataId2Project), I18nMessageUtil.get("i18n.select_node_and_project.6021")); + jsonObject.put("releaseMethodDataId", String.format("%s:%s", releaseMethodDataId2Node, releaseMethodDataId2Project)); + // + String afterOpt = jsonObject.getString("afterOpt"); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_packaging_action_required.bf66")); + // + String clearOld = jsonObject.getString("clearOld"); + jsonObject.put("afterOpt", afterOpt1.getCode()); + jsonObject.put("clearOld", Convert.toBool(clearOld, false)); + // + this.checkProjectSecondaryDirectory(jsonObject); + } + + private void checkProjectSecondaryDirectory(JSONObject jsonObject) { + // + String projectSecondaryDirectory = jsonObject.getString("projectSecondaryDirectory"); + Opt.ofBlankAble(projectSecondaryDirectory).ifPresent(s -> FileUtils.checkSlip(s, e -> new IllegalArgumentException(I18nMessageUtil.get("i18n.second_level_directory_cannot_skip_levels.c9fb") + e.getMessage()))); + } + + /** + * 获取分支信息 + * + * @param repositoryId 仓库id + * @return json + * @throws Exception 异常 + */ + @RequestMapping(value = "/build/branch-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage branchList( + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.repository_id_cannot_be_empty.a42c") String repositoryId) throws Exception { + // 根据 repositoryId 查询仓库信息 + RepositoryModel repositoryModel = repositoryService.getByKey(repositoryId, false); + Assert.notNull(repositoryModel, I18nMessageUtil.get("i18n.invalid_repository_info.b4ad")); + // + Assert.state(repositoryModel.getRepoType() == 0, I18nMessageUtil.get("i18n.only_git_repositories_have_branch_info.d7f7")); + IPlugin plugin = PluginFactory.getPlugin("git-clone"); + Map map = repositoryModel.toMap(); + Tuple branchAndTagList = (Tuple) plugin.execute("branchAndTagList", map); + Assert.notNull(branchAndTagList, I18nMessageUtil.get("i18n.no_any_branch.d042")); + JSONObject jsonObject = new JSONObject(); + List collection = branchAndTagList.toList(); + jsonObject.put("branch", CollUtil.get(collection, 0)); + jsonObject.put("tags", CollUtil.get(collection, 1)); + return JsonMessage.success("", jsonObject); + } + + + /** + * 删除构建信息 + * + * @param id 构建ID + * @return json + */ + @PostMapping(value = "/build/delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_not_found.1b0a") String id, HttpServletRequest request) { + this.delById(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success_with_cleanup.6155")); + } + + + private void delById(String id, HttpServletRequest request) { + // 查询构建信息 + BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, request); + Objects.requireNonNull(buildInfoModel, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + // + String e = buildExecuteService.checkStatus(buildInfoModel); + Assert.isNull(e, () -> e); + // 删除构建历史 + dbBuildHistoryLogService.delByWorkspace(request, entity -> entity.set("buildDataId", buildInfoModel.getId())); + // 删除构建信息文件 + File file = BuildUtil.getBuildDataFile(buildInfoModel.getId()); + // 快速删除 + boolean fastDel = CommandUtil.systemFastDel(file); + // + Assert.state(!fastDel, I18nMessageUtil.get("i18n.cleanup_history_build_failed_retrying.088e")); + // 删除构建信息数据 + 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 = "i18n.data_id_not_found.1b0a") String ids, HttpServletRequest request) { + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String s : list) { + this.delById(s, request); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success_with_cleanup.6155")); + } + + + /** + * 清除构建信息 + * + * @param id 构建ID + * @return json + */ + @PostMapping(value = "/build/clean-source", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage cleanSource(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_not_found.1b0a") String id, HttpServletRequest request) { + // 查询构建信息 + BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, request); + Objects.requireNonNull(buildInfoModel, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + File source = BuildUtil.getSourceById(buildInfoModel.getId()); + // 快速删除 + boolean fastDel = CommandUtil.systemFastDel(source); + // + Assert.state(!fastDel, I18nMessageUtil.get("i18n.delete_file_failure.041f")); + return JsonMessage.success(I18nMessageUtil.get("i18n.cleanup_succeeded.02ea")); + } + + /** + * 排序 + * + * @param id 节点ID + * @param method 方法 + * @param compareId 比较的ID + * @return msg + */ + @GetMapping(value = "/build/sort-item", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage sortItem(@ValidatorItem String id, @ValidatorItem String method, String compareId, HttpServletRequest request) { + if (StrUtil.equalsIgnoreCase(method, "top")) { + buildInfoService.sortToTop(id, request); + } else if (StrUtil.equalsIgnoreCase(method, "up")) { + buildInfoService.sortMoveUp(id, compareId, request); + } else if (StrUtil.equalsIgnoreCase(method, "down")) { + buildInfoService.sortMoveDown(id, compareId, request); + } else { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.unsupported_method.a1de") + method); + } + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + +} 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 new file mode 100644 index 0000000000..e149440204 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoHistoryController.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorConfig; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.log.BuildHistoryLog; +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.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 javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.List; +import java.util.Objects; + +/** + * new version for build info history controller + * + * @author Hotstrip + * @since 2021-08-26 + */ +@RestController +@Feature(cls = ClassFeature.BUILD_LOG) +public class BuildInfoHistoryController extends BaseServerController { + + private final BuildInfoService buildInfoService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; + + public BuildInfoHistoryController(BuildInfoService buildInfoService, + DbBuildHistoryLogService dbBuildHistoryLogService) { + this.buildInfoService = buildInfoService; + this.dbBuildHistoryLogService = dbBuildHistoryLogService; + } + + /** + * 下载构建物 + * + * @param logId 日志id + */ + @RequestMapping(value = "/build/history/download_file", method = RequestMethod.GET) + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadFile(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String logId, HttpServletRequest request, HttpServletResponse response) { + BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, request, false); + this.downloadFile(buildHistoryLog, response); + } + + /** + * 下载构建物 + * + * @param buildId 构建ID + * @param buildNumberId 构建序号ID + */ + @RequestMapping(value = "/build/history/download_file_by_build", method = RequestMethod.GET) + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadFile(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String buildId, + @ValidatorItem(ValidatorRule.NUMBERS) int buildNumberId, + HttpServletResponse response, + HttpServletRequest request) { + String workspaceId = dbBuildHistoryLogService.getCheckUserWorkspace(request); + // + BuildHistoryLog historyLog = new BuildHistoryLog(); + historyLog.setWorkspaceId(workspaceId); + historyLog.setBuildDataId(buildId); + historyLog.setBuildNumberId(buildNumberId); + List buildHistoryLogs = dbBuildHistoryLogService.listByBean(historyLog, false); + BuildHistoryLog first = CollUtil.getFirst(buildHistoryLogs); + this.downloadFile(first, response); + } + + private void downloadFile(BuildHistoryLog buildHistoryLog, HttpServletResponse response) { + if (buildHistoryLog == null) { + ServletUtil.write(response, JsonMessage.getString(404, I18nMessageUtil.get("i18n.build_record_not_exist.8186")), MediaType.APPLICATION_JSON_VALUE); + return; + } + EnvironmentMapBuilder environmentMapBuilder = buildHistoryLog.toEnvironmentMapBuilder(); + boolean tarGz = environmentMapBuilder.getBool(BuildUtil.USE_TAR_GZ, false); + File resultDirFile = BuildUtil.getHistoryPackageFile(buildHistoryLog.getBuildDataId(), buildHistoryLog.getBuildNumberId(), buildHistoryLog.getResultDirFile()); + File dirPackage = BuildUtil.loadDirPackage(buildHistoryLog.getBuildDataId(), buildHistoryLog.getBuildNumberId(), resultDirFile, tarGz, (aBoolean, file) -> file); + ServletUtil.write(response, dirPackage); + } + + + @RequestMapping(value = "/build/history/download_log", method = RequestMethod.GET) + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String logId, HttpServletRequest request, HttpServletResponse response) { + BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, request); + Objects.requireNonNull(buildHistoryLog); + BuildInfoModel item = buildInfoService.getByKey(buildHistoryLog.getBuildDataId()); + Objects.requireNonNull(item); + File logFile = BuildUtil.getLogFile(item.getId(), buildHistoryLog.getBuildNumberId()); + if (!FileUtil.exist(logFile)) { + return; + } + if (logFile.isFile()) { + ServletUtil.write(response, logFile); + } + } + + @RequestMapping(value = "/build/history/history_list.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + 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)); + // + File logFile = BuildUtil.getLogFile(buildHistoryLog.getBuildDataId(), buildHistoryLog.getBuildNumberId()); + buildHistoryLog.setHasLog(FileUtil.exist(logFile)); + }); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), pageResultTemp); + } + + /** + * 删除构建历史,支持批量删除,用逗号分隔 + * + * @param logId id + * @return json + */ + @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 = "i18n.no_data.1ac0")) String logId, HttpServletRequest request) { + List strings = StrUtil.splitTrim(logId, StrUtil.COMMA); + for (String itemId : strings) { + BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(itemId, request); + IJsonMessage jsonMessage = dbBuildHistoryLogService.deleteLogAndFile(buildHistoryLog); + if (!jsonMessage.success()) { + return jsonMessage; + } + } + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.delete_success.0007")); + } +} 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 new file mode 100644 index 0000000000..3beabb1c6c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoManageController.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.build.BuildExecuteManage; +import org.dromara.jpom.build.BuildExecuteService; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.build.ResultDirFileAction; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorConfig; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.log.BuildHistoryLog; +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.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; +import org.dromara.jpom.util.FileUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.Objects; +import java.util.Optional; + +/** + * new build info manage controller + * ` * + * + * @author Hotstrip + * @since 2021-08-23 + */ +@RestController +@Feature(cls = ClassFeature.BUILD) +public class BuildInfoManageController extends BaseServerController { + + private final BuildInfoService buildInfoService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; + private final BuildExecuteService buildExecuteService; + + public BuildInfoManageController(BuildInfoService buildInfoService, + DbBuildHistoryLogService dbBuildHistoryLogService, + BuildExecuteService buildExecuteService) { + this.buildInfoService = buildInfoService; + this.dbBuildHistoryLogService = dbBuildHistoryLogService; + this.buildExecuteService = buildExecuteService; + } + + /** + * 开始构建 + * + * @param id id + * @return json + */ + @RequestMapping(value = "/build/manage/start", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage start(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + String buildRemark, + String resultDirFile, + String branchName, + String branchTagName, + String checkRepositoryDiff, + String projectSecondaryDirectory, + String buildEnvParameter, + String dispatchSelectProject, + HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + // 更新数据 + BuildInfoModel update = new BuildInfoModel(); + Opt.ofBlankAble(resultDirFile).ifPresent(s -> { + ResultDirFileAction parse = ResultDirFileAction.parse(s); + parse.check(); + update.setResultDirFile(s); + }); + Opt.ofBlankAble(branchName).ifPresent(update::setBranchName); + Opt.ofBlankAble(branchTagName).ifPresent(update::setBranchTagName); + Opt.ofBlankAble(projectSecondaryDirectory).ifPresent(s -> { + FileUtils.checkSlip(s, e -> new IllegalArgumentException(I18nMessageUtil.get("i18n.second_level_directory_cannot_skip_levels.c9fb") + e.getMessage())); + // + String extraData = item.getExtraData(); + JSONObject jsonObject = JSONObject.parseObject(extraData); + jsonObject.put("projectSecondaryDirectory", s); + update.setExtraData(jsonObject.toString()); + }); + // 会存在清空的情况 + update.setBuildEnvParameter(Optional.ofNullable(buildEnvParameter).orElse(StrUtil.EMPTY)); + update.setId(id); + buildInfoService.updateById(update); + // userModel + UserModel userModel = getUser(); + Object[] parametersEnv = StrUtil.isNotEmpty(dispatchSelectProject) ? new Object[]{"dispatchSelectProject", dispatchSelectProject} : new Object[]{}; + // 执行构建 + return buildExecuteService.start(item.getId(), userModel, null, 0, buildRemark, checkRepositoryDiff, parametersEnv); + } + + /** + * 取消构建 + * + * @param id id + * @return json + */ + @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 = "i18n.no_data.1ac0")) String id, HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); + Objects.requireNonNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + String checkStatus = buildExecuteService.checkStatus(item); + BuildStatus nowStatus = BaseEnum.getEnum(BuildStatus.class, item.getStatus()); + Objects.requireNonNull(nowStatus); + if (checkStatus == null) { + return JsonMessage.success(I18nMessageUtil.get("i18n.status_not_in_progress.f410") + nowStatus.getDesc()); + } + boolean status = BuildExecuteManage.cancelTaskById(item.getId()); + if (!status) { + // 缓存中可能不存在数据,还是需要执行取消 + buildInfoService.updateStatus(id, BuildStatus.Cancel, I18nMessageUtil.get("i18n.manual_cancel.8464")); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.cancel_success.285f")); + } + + /** + * 重新发布 + * + * @param logId logId + * @return json + */ + @RequestMapping(value = "/build/manage/reRelease", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage reRelease(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String logId, + HttpServletRequest request) { + String workspaceId = dbBuildHistoryLogService.getCheckUserWorkspace(request); + BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, false, entity -> entity.set("workspaceId", workspaceId)); + Objects.requireNonNull(buildHistoryLog, I18nMessageUtil.get("i18n.no_corresponding_build_record.b3b2")); + BuildInfoModel item = buildInfoService.getByKey(buildHistoryLog.getBuildDataId(), request); + Objects.requireNonNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + int buildId = buildExecuteService.rollback(buildHistoryLog, item, getUser()); + return JsonMessage.success(I18nMessageUtil.get("i18n.republishing.131d"), buildId); + } + + /** + * 获取构建的日志 + * + * @param id id + * @param buildId 构建编号 + * @param line 需要获取的行号 + * @return json + */ + @RequestMapping(value = "/build/manage/get-now-log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getNowLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.no_build_id.a0b8") int buildId, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.line_number_error.c65d") int line, + HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + Assert.state(buildId <= item.getBuildId(), I18nMessageUtil.get("i18n.no_build_record_found.76f4")); + + BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); + buildHistoryLog.setBuildDataId(id); + buildHistoryLog.setBuildNumberId(buildId); + BuildHistoryLog queryByBean = dbBuildHistoryLogService.queryByBean(buildHistoryLog); + Assert.notNull(queryByBean, I18nMessageUtil.get("i18n.no_build_history.39f7")); + + File file = BuildUtil.getLogFile(item.getId(), buildId); + Assert.state(FileUtil.isFile(file), I18nMessageUtil.get("i18n.log_file_does_not_exist_or_error.a0e7")); + + if (!file.exists()) { + if (buildId == item.getBuildId()) { + return new JsonMessage<>(201, I18nMessageUtil.get("i18n.no_log_file.bacf")); + } + return new JsonMessage<>(300, I18nMessageUtil.get("i18n.log_file_does_not_exist.f6c6")); + } + JSONObject data = FileUtils.readLogFile(file, line); + // 运行中 + Integer status = queryByBean.getStatus(); + data.put("run", buildExecuteService.checkStatus(item) != null); + data.put("logId", queryByBean.getId()); + data.put("status", status); + data.put("statusMsg", queryByBean.getStatusMsg()); + // 构建中 + //data.put("buildRun", status == BuildStatus.Ing.getCode()); + return JsonMessage.success("", data); + } +} 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 new file mode 100644 index 0000000000..5162f4f921 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoTriggerController.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build; + +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.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.BuildInfoModel; +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.dblog.BuildInfoService; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +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; + +/** + * new trigger controller for build + * + * @author Hotstrip + * @since 2021-08-23 + */ +@RestController +@Feature(cls = ClassFeature.BUILD) +public class BuildInfoTriggerController extends BaseServerController { + + private final BuildInfoService buildInfoService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public BuildInfoTriggerController(BuildInfoService buildInfoService, + TriggerTokenLogServer triggerTokenLogServer) { + this.buildInfoService = buildInfoService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @RequestMapping(value = "/build/trigger/url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + 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)) { + updateInfo = new BuildInfoModel(); + updateInfo.setId(id); + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), buildInfoService.typeName(), + item.getId(), user.getId())); + buildInfoService.updateById(updateInfo); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + 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()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + Map map = new HashMap<>(10); + map.put("triggerBuildUrl", FileUtil.normalize(triggerBuildUrl)); + String batchTriggerBuildUrl = String.format("/%s/%s", contextPath, ServerOpenApi.BUILD_TRIGGER_BUILD_BATCH); + map.put("batchTriggerBuildUrl", FileUtil.normalize(batchTriggerBuildUrl)); + // + String batchBuildStatusUrl = String.format("/%s/%s", contextPath, ServerOpenApi.BUILD_TRIGGER_STATUS); + map.put("batchBuildStatusUrl", FileUtil.normalize(batchBuildStatusUrl)); + String buildLogUrl = String.format("/%s/%s", contextPath, ServerOpenApi.BUILD_TRIGGER_LOG); + map.put("buildLogUrl", FileUtil.normalize(buildLogUrl)); + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } + + +// /** +// * reset new trigger url +// * +// * @param id id +// * @return json +// */ +// @RequestMapping(value = "/build/trigger/rest", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) +// @Feature(method = MethodFeature.EDIT) +// public String triggerRest(String id) { +// BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); +// UserModel user = getUser(); +// BuildInfoModel updateInfo = new BuildInfoModel(); +// updateInfo.setId(id); +// // new trigger url +// updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), buildInfoService.typeName(), +// item.getId(), user.getId())); +// buildInfoService.update(updateInfo); +// Map map = this.getBuildToken(updateInfo); +// return JsonMessage.success( "重置成功", map); +// } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/RepositoryController.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/RepositoryController.java new file mode 100644 index 0000000000..0c9261f539 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/RepositoryController.java @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.BomReader; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.text.csv.*; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.controller.build.repository.ImportRepoUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.RepositoryModel; +import org.dromara.jpom.model.enums.GitProtocolEnum; +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.plugin.PluginFactory; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.RepositoryService; +import org.springframework.http.HttpStatus; +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.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Repository controller + * + * @author Hotstrip + */ +@RestController +@Feature(cls = ClassFeature.BUILD_REPOSITORY) +@Slf4j +public class RepositoryController extends BaseServerController { + + private final RepositoryService repositoryService; + private final BuildInfoService buildInfoService; + + public RepositoryController(RepositoryService repositoryService, + BuildInfoService buildInfoService) { + this.repositoryService = repositoryService; + this.buildInfoService = buildInfoService; + } + + /** + * load repository list + * + * @return json + */ + @PostMapping(value = "/build/repository/list") + @Feature(method = MethodFeature.LIST) + public Object loadRepositoryList(HttpServletRequest request) { + PageResultDto pageResult = repositoryService.listPage(request); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), pageResult); + } + + /** + * load build list with params + * + * @return json + */ + @GetMapping(value = "/build/repository/list-group", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getBuildGroupAll() { + // load list with page + List group = repositoryService.listGroup(); + return JsonMessage.success("", group); + } + + /** + * 下载导入模板 + */ + @GetMapping(value = "/build/repository/import-template", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public void importTemplate(HttpServletResponse response) throws IOException { + String fileName = I18nMessageUtil.get("i18n.repository_import_template.5e2d"); + this.setApplicationHeader(response, fileName); + // + CsvWriter writer = CsvUtil.getWriter(response.getWriter()); + writer.writeLine("name", "address", "type", "protocol", "share", "private rsa", "username", "password", "timeout(s)"); + writer.flush(); + } + + /** + * export repository by csv + */ + @GetMapping(value = "/build/repository/export") + @Feature(method = MethodFeature.DOWNLOAD) + @SystemPermission + public void exportRepositoryList(HttpServletRequest request, HttpServletResponse response) throws IOException { + String prex = I18nMessageUtil.get("i18n.exported_repo_data.bac5"); + String fileName = prex + DateTime.now().toString(DatePattern.NORM_DATE_FORMAT) + ".csv"; + this.setApplicationHeader(response, fileName); + CsvWriter writer = CsvUtil.getWriter(response.getWriter()); + int pageInt = 0; + Map paramMap = ServletUtil.getParamMap(request); + writer.writeLine("name", "group", "address", "type", "protocol", "private rsa", "username", "password", "timeout(s)"); + while (true) { + // 下一页 + paramMap.put("page", String.valueOf(++pageInt)); + PageResultDto listPage = repositoryService.listPage(paramMap, false); + if (listPage.isEmpty()) { + break; + } + listPage.getResult() + .stream() + .map((Function>) repositoryModel -> CollUtil.newArrayList( + repositoryModel.getName(), + repositoryModel.getGroup(), + repositoryModel.getGitUrl(), + EnumUtil.likeValueOf(RepositoryModel.RepoType.class, repositoryModel.getRepoType()), + EnumUtil.likeValueOf(GitProtocolEnum.class, repositoryModel.getProtocol()), + repositoryModel.getRsaPrv(), + repositoryModel.getUserName(), + repositoryModel.getPassword(), + repositoryModel.getTimeout() + )) + .map(objects -> objects.stream().map(StrUtil::toStringOrNull).toArray(String[]::new)) + .forEach(writer::writeLine); + if (ObjectUtil.equal(listPage.getPage(), listPage.getTotalPage())) { + // 最后一页 + break; + } + } + writer.flush(); + } + + /** + * 导入数据 + * + * @return json + */ + @PostMapping(value = "/build/repository/import-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + @SystemPermission + public IJsonMessage importData(MultipartFile file, HttpServletRequest request) throws IOException { + Assert.notNull(file, I18nMessageUtil.get("i18n.no_uploaded_file.07ef")); + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + boolean csv = StrUtil.endWithIgnoreCase(extName, "csv"); + Assert.state(csv, I18nMessageUtil.get("i18n.disallowed_file_format.d6e4")); + BomReader bomReader = IoUtil.getBomReader(file.getInputStream()); + CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.setHeaderLineNo(0); + CsvReader reader = CsvUtil.getReader(bomReader, csvReadConfig); + CsvData csvData; + try { + csvData = reader.read(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_csv_exception.885e"), e); + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.parse_file_exception.374d") + e.getMessage()); + } + List rows = csvData.getRows(); + Assert.notEmpty(rows, I18nMessageUtil.get("i18n.no_data.55a2")); + int addCount = 0, updateCount = 0; + for (int i = 0; i < rows.size(); i++) { + int finalI = i; + CsvRow csvRow = rows.get(i); + String name = csvRow.getByName("name"); + Assert.hasText(name, () -> StrUtil.format(I18nMessageUtil.get("i18n.name_field_required.e0c5"), finalI + 1)); + String group = csvRow.getByName("group"); + String address = csvRow.getByName("address"); + Assert.hasText(address, () -> StrUtil.format(I18nMessageUtil.get("i18n.address_field_required.3bc8"), finalI + 1)); + String type = csvRow.getByName("type"); + Assert.hasText(type, () -> StrUtil.format(I18nMessageUtil.get("i18n.type_field_required.7637"), finalI + 1)); + RepositoryModel.RepoType repoType = null; + if ("Git".equalsIgnoreCase(type)) { + repoType = RepositoryModel.RepoType.Git; + } else if ("Svn".equalsIgnoreCase(type)) { + repoType = RepositoryModel.RepoType.Svn; + } + Assert.notNull(repoType, () -> StrUtil.format(I18nMessageUtil.get("i18n.type_field_value_error.14cf"), finalI + 1)); + String protocol = csvRow.getByName("protocol"); + Assert.hasText(protocol, () -> StrUtil.format(I18nMessageUtil.get("i18n.protocol_field_required.7cc2"), finalI + 1)); + GitProtocolEnum gitProtocolEnum = null; + if ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol)) { + gitProtocolEnum = GitProtocolEnum.HTTP; + } else if ("ssh".equalsIgnoreCase(protocol)) { + gitProtocolEnum = GitProtocolEnum.SSH; + } + Assert.notNull(gitProtocolEnum, () -> StrUtil.format(I18nMessageUtil.get("i18n.protocol_field_value_error.2b41"), finalI + 1)); + String privateRsa = csvRow.getByName("private rsa"); + String username = csvRow.getByName("username"); + String password = csvRow.getByName("password"); + Integer timeout = Convert.toInt(csvRow.getByName("timeout(s)")); + // + String optWorkspaceId = repositoryService.covertGlobalWorkspace(request); + RepositoryModel where = new RepositoryModel(); + where.setProtocol(gitProtocolEnum.getCode()); + where.setGitUrl(address); + // 工作空间 + where.setWorkspaceId(optWorkspaceId); + // 查询是否存在 + RepositoryModel repositoryModel = repositoryService.queryByBean(where); + // + where.setName(name); + where.setGroup(group); + where.setTimeout(timeout); + where.setPassword(password); + where.setRsaPrv(privateRsa); + where.setRepoType(repoType.getCode()); + where.setUserName(username); + // 检查 rsa 私钥 + boolean andUpdateSshKey = this.checkAndUpdateSshKey(where); + Assert.state(andUpdateSshKey, StrUtil.format(I18nMessageUtil.get("i18n.rsa_private_key_file_error.b687"), finalI + 1)); + if (where.getRepoType() == RepositoryModel.RepoType.Git.getCode()) { + // 验证 git 仓库信息 + try { + IPlugin plugin = PluginFactory.getPlugin("git-clone"); + Map map = where.toMap(); + Tuple branchAndTagList = (Tuple) plugin.execute("branchAndTagList", map); + //Tuple tuple = GitUtil.getBranchAndTagList(repositoryModelReq); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.get_repository_branch_failure.37cc"), e); + throw new IllegalStateException(StrUtil.format(I18nMessageUtil.get("i18n.repository_info_error.5b0a"), finalI + 1)); + } + } + if (repositoryModel == null) { + // 添加 + repositoryService.insert(where); + addCount++; + } else { + where.setId(repositoryModel.getId()); + repositoryService.updateById(where); + updateCount++; + } + } + return JsonMessage.success(I18nMessageUtil.get("i18n.import_success_with_count.22b9"), addCount, updateCount); + } + + /** + * load repository list + * + * @return json + */ + @GetMapping(value = "/build/repository/get") + @Feature(method = MethodFeature.LIST) + public IJsonMessage loadRepositoryGet(String id, HttpServletRequest request) { + RepositoryModel repositoryModel = repositoryService.getByKey(id, request); + Assert.notNull(repositoryModel, I18nMessageUtil.get("i18n.no_corresponding_repository.dde9")); + return JsonMessage.success("", repositoryModel); + } + + /** + * 过滤前端多余避免核心字段被更新 + * + * @param repositoryModelReq 仓库对象 + * @return 可以更新的对象 + */ + private RepositoryModel convertRequest(RepositoryModel repositoryModelReq) { + RepositoryModel repositoryModel = new RepositoryModel(); + repositoryModel.setName(repositoryModelReq.getName()); + repositoryModel.setGroup(repositoryModelReq.getGroup()); + repositoryModel.setUserName(repositoryModelReq.getUserName()); + repositoryModel.setId(repositoryModelReq.getId()); + repositoryModel.setProtocol(repositoryModelReq.getProtocol()); + repositoryModel.setTimeout(repositoryModelReq.getTimeout()); + repositoryModel.setGitUrl(repositoryModelReq.getGitUrl()); + repositoryModel.setPassword(repositoryModelReq.getPassword()); + repositoryModel.setRepoType(repositoryModelReq.getRepoType()); + repositoryModel.setSortValue(repositoryModelReq.getSortValue()); + repositoryModel.setRsaPrv(repositoryModelReq.getRsaPrv()); + return repositoryModel; + } + + /** + * edit + * + * @param req 仓库实体 + * @return json + */ + @PostMapping(value = "/build/repository/edit") + @Feature(method = MethodFeature.EDIT) + public IJsonMessage editRepository(RepositoryModel req, HttpServletRequest request) { + RepositoryModel repositoryModelReq = this.convertRequest(req); + repositoryModelReq.setWorkspaceId(repositoryService.covertGlobalWorkspace(request)); + this.checkInfo(repositoryModelReq, request); + // 检查 rsa 私钥 + boolean andUpdateSshKey = this.checkAndUpdateSshKey(repositoryModelReq); + Assert.state(andUpdateSshKey, I18nMessageUtil.get("i18n.rsa_private_key_file_invalid.5f12")); + + if (repositoryModelReq.getRepoType() == RepositoryModel.RepoType.Git.getCode()) { + RepositoryModel repositoryModel = repositoryService.getByKey(repositoryModelReq.getId(), false); + if (repositoryModel != null) { + repositoryModelReq.setRsaPrv(StrUtil.emptyToDefault(repositoryModelReq.getRsaPrv(), repositoryModel.getRsaPrv())); + repositoryModelReq.setPassword(StrUtil.emptyToDefault(repositoryModelReq.getPassword(), repositoryModel.getPassword())); + } + // 验证 git 仓库信息 + try { + IPlugin plugin = PluginFactory.getPlugin("git-clone"); + Map map = repositoryModelReq.toMap(); + Tuple branchAndTagList = (Tuple) plugin.execute("branchAndTagList", map); + //Tuple tuple = GitUtil.getBranchAndTagList(repositoryModelReq); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.get_repository_branch_failure.37cc"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.unable_to_connect_to_repository.52df") + e.getMessage()); + } + } + if (StrUtil.isEmpty(repositoryModelReq.getId())) { + // insert data + repositoryService.insert(repositoryModelReq); + } else { + // update data + repositoryService.getByKeyAndGlobal(repositoryModelReq.getId(), request); + //repositoryModelReq.setWorkspaceId(repositoryService.getCheckUserWorkspace(getRequest())); + repositoryService.updateById(repositoryModelReq); + } + + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * edit + * + * @param id 仓库信息 + * @return json + */ + @PostMapping(value = "/build/repository/rest_hide_field") + @Feature(method = MethodFeature.EDIT) + public IJsonMessage restHideField(@ValidatorItem String id, HttpServletRequest request) { + RepositoryModel byKeyAndGlobal = repositoryService.getByKeyAndGlobal(id, request); + RepositoryModel repositoryModel = new RepositoryModel(); + repositoryModel.setId(byKeyAndGlobal.getId()); + repositoryModel.setPassword(StrUtil.EMPTY); + repositoryModel.setRsaPrv(StrUtil.EMPTY); + repositoryModel.setRsaPub(StrUtil.EMPTY); + repositoryService.updateById(repositoryModel, request); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + @GetMapping(value = "/build/repository/provider_info") + @Feature(method = MethodFeature.LIST) + public IJsonMessage>> providerInfo() { + Map> providerList = ImportRepoUtil.getProviderList(); + return JsonMessage.success(HttpStatus.OK.name(), providerList); + } + + @GetMapping(value = "/build/repository/authorize_repos") + @Feature(method = MethodFeature.LIST) + public IJsonMessage> authorizeRepos(HttpServletRequest request, + @ValidatorItem String token, + String address, + @ValidatorItem String type, + String condition) { + // 获取分页信息 + Map paramMap = ServletUtil.getParamMap(request); + Page page = repositoryService.parsePage(paramMap); + Assert.hasText(token, I18nMessageUtil.get("i18n.please_fill_in_personal_token.970a")); + // 搜索条件 + // 远程仓库 + //ImportRepoUtil.getProviderConfig(type); + + String userName = ImportRepoUtil.getCurrentUserName(type, token, address); + cn.hutool.json.JSONObject repoList = ImportRepoUtil.getRepoList(type, condition, page, token, userName, address); + PageResultDto pageResultDto = new PageResultDto<>(page.getPageNumber(), page.getPageSize(), repoList.getLong("total").intValue()); + List objects = repoList.getJSONArray("data") + .stream() + .map(o -> { + cn.hutool.json.JSONObject obj = (cn.hutool.json.JSONObject) o; + JSONObject jsonObject = new JSONObject(); + jsonObject.putAll(obj); + jsonObject.put("exists", RepositoryController.this.checkRepositoryUrl(obj.getStr("url"), request)); + return jsonObject; + }) + .collect(Collectors.toList()); + pageResultDto.setResult(objects); + return JsonMessage.success(HttpStatus.OK.name(), pageResultDto); + } + + /** + * 检查信息 + * + * @param request 请求信息 + * @param repositoryModelReq 仓库信息 + */ + private void checkInfo(RepositoryModel repositoryModelReq, HttpServletRequest request) { + Assert.notNull(repositoryModelReq, I18nMessageUtil.get("i18n.correct_information_required.5e12")); + Assert.hasText(repositoryModelReq.getName(), I18nMessageUtil.get("i18n.please_fill_in_repository_name.9f0d")); + Integer repoType = repositoryModelReq.getRepoType(); + Assert.state(repoType != null && (repoType == RepositoryModel.RepoType.Git.getCode() || repoType == RepositoryModel.RepoType.Svn.getCode()), I18nMessageUtil.get("i18n.repository_type_required.9414")); + Assert.hasText(repositoryModelReq.getGitUrl(), I18nMessageUtil.get("i18n.please_fill_in_repository_address.0cf8")); + // + Integer protocol = repositoryModelReq.getProtocol(); + Assert.state(protocol != null && (protocol == GitProtocolEnum.HTTP.getCode() || protocol == GitProtocolEnum.SSH.getCode()), I18nMessageUtil.get("i18n.select_pull_code_protocol.fc24")); + // 修正字段 + if (protocol == GitProtocolEnum.HTTP.getCode()) { + // http + repositoryModelReq.setRsaPub(StrUtil.EMPTY); + repositoryModelReq.setRsaPrv(StrUtil.EMPTY); + } else if (protocol == GitProtocolEnum.SSH.getCode()) { + // ssh + repositoryModelReq.setPassword(StrUtil.emptyToDefault(repositoryModelReq.getPassword(), StrUtil.EMPTY)); + } + String workspaceId = repositoryService.getCheckUserWorkspace(request); + // + boolean repositoryUrl = this.checkRepositoryUrl(workspaceId, repositoryModelReq.getId(), repositoryModelReq.getGitUrl()); + Assert.state(!repositoryUrl, I18nMessageUtil.get("i18n.repo_already_exists.38a3")); + } + + /** + * 判断仓库地址是否存在 + * + * @param workspaceId 工作空间ID + * @param id 仓库ID + * @param url 仓库 url + * @return true 在当前工作空间已经存在拉 + */ + private boolean checkRepositoryUrl(String workspaceId, String id, String url) { + // 判断仓库是否重复 + Entity entity = Entity.create(); + if (StrUtil.isNotEmpty(id)) { + Validator.validateGeneral(id, I18nMessageUtil.get("i18n.wrong_id.ab4d")); + entity.set("id", "<> " + id); + } + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + entity.set("gitUrl", url); + return repositoryService.exists(entity); + } + + /** + * 判断仓库地址是否存在 + * + * @param url 仓库 url + * @return true 在当前工作空间已经存在拉 + */ + private boolean checkRepositoryUrl(String url, HttpServletRequest request) { + String workspaceId = repositoryService.getCheckUserWorkspace(request); + return this.checkRepositoryUrl(workspaceId, null, url); + } + + /** + * check and update ssh key + * + * @param repositoryModelReq 仓库 + */ + private boolean checkAndUpdateSshKey(RepositoryModel repositoryModelReq) { + if (repositoryModelReq.getProtocol() == GitProtocolEnum.SSH.getCode()) { + // if rsa key is not empty + if (StrUtil.isNotEmpty(repositoryModelReq.getRsaPrv())) { + /** + * if rsa key is start with "file:" + * copy this file + */ + if (StrUtil.startWith(repositoryModelReq.getRsaPrv(), URLUtil.FILE_URL_PREFIX)) { + String rsaPath = StrUtil.removePrefix(repositoryModelReq.getRsaPrv(), URLUtil.FILE_URL_PREFIX); + if (!FileUtil.exist(rsaPath)) { + log.warn("there is no rsa file... {}", rsaPath); + return false; + } + } else { + //File rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModelReq.getId() + Const.ID_RSA); + // or else put into file + //FileUtil.writeUtf8String(repositoryModelReq.getRsaPrv(), rsaFile); + } + } + } + return true; + } + + /** + * delete + * + * @param id 仓库ID + * @return json + */ + @PostMapping(value = "/build/repository/delete") + @Feature(method = MethodFeature.DEL) + public Object delRepository(@ValidatorItem String id, HttpServletRequest request) { + // 判断仓库是否被关联 + Entity entity = Entity.create(); + entity.set("repositoryId", id); + boolean exists = buildInfoService.exists(entity); + Assert.state(!exists, I18nMessageUtil.get("i18n.current_repository_associated_with_build.4b6e")); + RepositoryModel keyAndGlobal = repositoryService.getByKeyAndGlobal(id, request); + repositoryService.delByKey(keyAndGlobal.getId()); + File rsaFile = BuildUtil.getRepositoryRsaFile(id + ServerConst.ID_RSA); + FileUtil.del(rsaFile); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 排序 + * + * @param id 节点ID + * @param method 方法 + * @param compareId 比较的ID + * @return msg + */ + @GetMapping(value = "/build/repository/sort-item", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage sortItem(@ValidatorItem String id, + @ValidatorItem String method, + String compareId, HttpServletRequest request) { + if (StrUtil.equalsIgnoreCase(method, "top")) { + repositoryService.sortToTop(id, request); + } else if (StrUtil.equalsIgnoreCase(method, "up")) { + repositoryService.sortMoveUp(id, compareId, request); + } else if (StrUtil.equalsIgnoreCase(method, "down")) { + repositoryService.sortMoveDown(id, compareId, request); + } else { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.unsupported_method.a1de") + method); + } + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/repository/ImportRepoProviderConfig.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/repository/ImportRepoProviderConfig.java new file mode 100644 index 0000000000..d82a15f7a2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/repository/ImportRepoProviderConfig.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build.repository; + +import lombok.Data; + +import java.util.Map; + +/** + * 仓库提供商配置 + * + * @author WeiHongBin + */ +@Data +public class ImportRepoProviderConfig { + private String baseUrl; + /** + * 鉴权方式 1:header 2:from 3:body + */ + private Integer authType; + /** + * 鉴权key 例如:Authorization + */ + private String authKey; + /** + * 鉴权值 例如:Bearer ${token} + */ + private String authValue; + /** + * 扩展参数 + */ + private Map extraParams; + /** + * 扩展参数类型 1:header 2:from 3:body + */ + private Integer extraParamsType; + /** + * 获取用户信息的请求方式 + */ + private String currentUserMethod; + /** + * 获取用户信息的请求地址 + */ + private String currentUserUrl; + /** + * 获取用户名 path + */ + private String userNamePath; + /** + * 获取仓库列表的请求方式 + */ + private String repoListMethod; + /** + * 获取仓库列表的请求地址 + */ + private String repoListUrl; + /** + * 获取仓库列表的请求参数 + */ + private Map repoListParam; + /** + * 获取仓库列表数组 path + */ + private String repoListPath; + /** + * 仓库信息 转换 path + */ + private Map repoConvertPath; + /** + * 获取仓库总数 X-Total + */ + private String repoTotalHeader; + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/repository/ImportRepoUtil.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/repository/ImportRepoUtil.java new file mode 100644 index 0000000000..63250c0498 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/repository/ImportRepoUtil.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build.repository; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Page; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.hutool.setting.yaml.YamlUtil; +import lombok.Lombok; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.ExtConfigBean; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * @author WeiHongBin + */ +@Slf4j +@UtilityClass +public class ImportRepoUtil { + + private static final String IMPORT_REPO_PROVIDER_DIR = "/import-repo-provider/"; + + public Map> getProviderList() { + Resource[] configResources = ExtConfigBean.getDefaultConfigResources("import-repo-provider/*.yml"); + Map> map = resourceToMap(configResources); + Resource[] diyConfigResources = ExtConfigBean.getConfigResources("import-repo-provider/*.yml"); + map.putAll(resourceToMap(diyConfigResources)); + return map; + } + + private Map> resourceToMap(Resource[] configResources) { + if (configResources == null) { + return new HashMap<>(1); + } + return Arrays.stream(configResources) + .map(resource -> { + String filename = resource.getFilename(); + String mainName = FileUtil.mainName(filename); + + try (InputStream inputStream = resource.getInputStream()) { + ImportRepoProviderConfig providerConfig = YamlUtil.load(inputStream, ImportRepoProviderConfig.class); + Map map = new HashMap<>(); + map.put("name", mainName); + map.put("baseUrl", providerConfig.getBaseUrl()); + // 是否支持查询 + map.put("query", providerConfig.getRepoListParam().values().stream().anyMatch(s -> s.contains("${query}"))); + return map; + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }) + .collect(Collectors.toMap(map -> (String) map.get("name"), map -> map)); + } + + @SneakyThrows + public ImportRepoProviderConfig getProviderConfig(String platform) { + try (InputStream inputStream = ExtConfigBean.getConfigResourceInputStream(IMPORT_REPO_PROVIDER_DIR + platform + ".yml")) { + return YamlUtil.load(inputStream, ImportRepoProviderConfig.class); + } + } + + private void setCommonParams(String platform, HttpRequest request, String token) { + ImportRepoProviderConfig provider = getProviderConfig(platform); + String callToken = provider.getAuthValue().replace("${token}", token); + if (provider.getAuthType() == 1) { + request.header(provider.getAuthKey(), callToken); + } else if (provider.getAuthType() == 2) { + request.form(provider.getAuthKey(), callToken); + } else if (provider.getAuthType() == 3) { + request.body(JSONUtil.createObj().set(provider.getAuthKey(), callToken).toString()); + } + + Map extraParams = provider.getExtraParams(); + if (CollUtil.isNotEmpty(extraParams)) { + if (provider.getExtraParamsType() == 1) { + extraParams.forEach(request::header); + } else if (provider.getExtraParamsType() == 2) { + extraParams.forEach(request::form); + } else if (provider.getExtraParamsType() == 3) { + request.body(JSONUtil.toJsonStr(extraParams)); + } + } + } + + public JSONObject getRepoList(String platform, String query, Page page, String token, String username, String baseUrl) { + baseUrl = StrUtil.blankToDefault(baseUrl, getProviderConfig(platform).getBaseUrl()); + ImportRepoProviderConfig provider = getProviderConfig(platform); + HttpRequest request = HttpUtil.createRequest(Method.valueOf(provider.getRepoListMethod()), baseUrl + provider.getRepoListUrl()); + setCommonParams(platform, request, token); + query = StrUtil.blankToDefault(query, ""); + String finalQuery = query; + provider.getRepoListParam().forEach((k, v) -> { + if ("${query}".equals(v)) { + v = v.replace("${query}", finalQuery); + } + if ("${page}".equals(v)) { + v = v.replace("${page}", String.valueOf(page.getPageNumber())); + } + if ("${pageSize}".equals(v)) { + v = v.replace("${pageSize}", String.valueOf(page.getPageSize())); + } + request.form(k, v); + }); + String body; + int total; + request.getUrl(); + log.debug(String.format("url: %s headers: %s form: %s", request.getUrl(), request.headers(), request.form())); + try (HttpResponse execute = request.execute()) { + body = execute.body(); + int status = execute.getStatus(); + Map> headers = execute.headers(); + String totalHeader = execute.header(provider.getRepoTotalHeader()); + int totalCount = page.getPageSize() * page.getPageNumber(); + if ("Link".equals(provider.getRepoTotalHeader()) && StrUtil.isNotBlank(totalHeader)) { + // github 特殊处理 + Pattern pattern = PatternPool.get("page=(\\d+)&per_page=(\\d+)>; rel=\"last\""); + Matcher matcher = pattern.matcher(totalHeader); + if (matcher.find()) { + int linkPage = Integer.parseInt(matcher.group(2)); + int linkPerPage = Integer.parseInt(matcher.group(1)); + total = linkPage * linkPerPage; + } else { + total = totalCount; + } + } else { + total = StrUtil.isNotBlank(totalHeader) ? Integer.parseInt(totalHeader) : totalCount; + } + log.debug(String.format("status: %s body: %s headers: %s", status, body, headers)); + Assert.state(execute.isOk(), String.format(I18nMessageUtil.get("i18n.request_failed_message.9c71"), status, body, headers)); + } + JSONArray jsonArray = JSONUtil.parse(body).getByPath(provider.getRepoListPath(), JSONArray.class); + List data = jsonArray.stream().map(o -> { + JSONObject obj = (JSONObject) o; + JSONObject entries = new JSONObject(); + provider.getRepoConvertPath().forEach((k, v) -> { + if (StrUtil.startWith(v, "$ ")) { + String[] expression = v.split(" "); + String value = obj.getStr(expression[1]); + // 对比方式 + String compare = expression[2]; + // 对比值 + String compareValue = expression[3]; + switch (compare) { + case "==": + entries.set(k, value.equals(compareValue)); + break; + case "!=": + entries.set(k, !value.equals(compareValue)); + break; + default: + throw new IllegalStateException(I18nMessageUtil.get("i18n.supported_comparison_operators_message.6d7a")); + } + } else { + entries.set(k, obj.get(v)); + } + }); + entries.set("username", username); + return entries; + }).collect(Collectors.toList()); + return JSONUtil.createObj().set("data", data).set("total", total); + } + + public String getCurrentUserName(String platform, String token, String baseUrl) { + baseUrl = StrUtil.blankToDefault(baseUrl, getProviderConfig(platform).getBaseUrl()); + Assert.state(StrUtil.isNotBlank(baseUrl), String.format(I18nMessageUtil.get("i18n.please_fill_in_address_of.9e02"), platform)); + ImportRepoProviderConfig provider = getProviderConfig(platform); + HttpRequest request = HttpUtil.createRequest(Method.valueOf(provider.getCurrentUserMethod()), baseUrl + provider.getCurrentUserUrl()); + setCommonParams(platform, request, token); + String body; + log.debug("url: {} headers: {} form: {}", request.getUrl(), request.headers(), request.form()); + try (HttpResponse execute = request.execute()) { + body = execute.body(); + int status = execute.getStatus(); + Map> headers = execute.headers(); + log.debug("status: {} body: {} headers: {}", status, body, headers); + Assert.state(execute.isOk(), String.format(I18nMessageUtil.get("i18n.request_failed_message.9c71"), status, body, headers)); + } + return JSONUtil.parse(body).getByPath(provider.getUserNamePath(), String.class); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerContainerController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerContainerController.java new file mode 100644 index 0000000000..21c80dcf77 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerContainerController.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerContainerController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER) +@RequestMapping(value = "/docker/container") +public class DockerContainerController extends BaseDockerContainerController { + + private final DockerInfoService dockerInfoService; + private final MachineDockerServer machineDockerServer; + + public DockerContainerController(DockerInfoService dockerInfoService, + MachineDockerServer machineDockerServer, + ServerConfig serverConfig) { + super(serverConfig); + this.dockerInfoService = dockerInfoService; + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + DockerInfoModel dockerInfoModel = dockerInfoService.getByKey(id); + Assert.notNull(dockerInfoModel, I18nMessageUtil.get("i18n.no_docker_info.d685")); + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(dockerInfoModel.getMachineDockerId()); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerImagesController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerImagesController.java new file mode 100644 index 0000000000..558d650667 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerImagesController.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerImagesController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER) +@RequestMapping(value = "/docker/images") +public class DockerImagesController extends BaseDockerImagesController { + + private final DockerInfoService dockerInfoService; + private final MachineDockerServer machineDockerServer; + + public DockerImagesController(DockerInfoService dockerInfoService, + ServerConfig serverConfig, + MachineDockerServer machineDockerServer) { + super(serverConfig); + this.dockerInfoService = dockerInfoService; + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + DockerInfoModel dockerInfoModel = dockerInfoService.getByKey(id); + Assert.notNull(dockerInfoModel, I18nMessageUtil.get("i18n.no_docker_info.d685")); + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(dockerInfoModel.getMachineDockerId()); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} 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 new file mode 100644 index 0000000000..7da873159b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerInfoController.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.docker.DockerInfoModel; +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.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +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.util.List; + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER) +@RequestMapping(value = "/docker") +@Slf4j +public class DockerInfoController extends BaseServerController { + + private final DockerInfoService dockerInfoService; + private final MachineDockerServer machineDockerServer; + + public DockerInfoController(DockerInfoService dockerInfoService, + MachineDockerServer machineDockerServer) { + this.dockerInfoService = dockerInfoService; + this.machineDockerServer = machineDockerServer; + } + + /** + * @return json + */ + @GetMapping(value = "api-versions", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> apiVersions() throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + List data = (List) plugin.execute("apiVersions"); + return JsonMessage.success("", data); + } + + /** + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + // load list with page + PageResultDto resultDto = dockerInfoService.listPage(request); + resultDto.each(dockerInfoModel -> { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(dockerInfoModel.getMachineDockerId()); + if (machineDockerModel != null) { + machineDockerModel.setRegistryPassword(null); + } + dockerInfoModel.setMachineDocker(machineDockerModel); + }); + return JsonMessage.success("", resultDto); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@ValidatorItem String id, @ValidatorItem String name, String tags, HttpServletRequest request) throws Exception { + DockerInfoModel dockerInfoModel = new DockerInfoModel(); + dockerInfoModel.setId(id); + dockerInfoModel.setName(name); + Assert.state(!StrUtil.contains(tags, StrUtil.COLON), I18nMessageUtil.get("i18n.tag_cannot_contain_colon.f9ae")); + List tagList = StrUtil.splitTrim(tags, StrUtil.COMMA); + String newTags = CollUtil.join(tagList, StrUtil.COLON, StrUtil.COLON, StrUtil.COLON); + dockerInfoModel.setTags(newTags); + dockerInfoService.updateById(dockerInfoModel, request); + // + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, HttpServletRequest request) throws Exception { + dockerInfoService.delByKey(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + + /** + * 同步到指定工作空间 + * + * @param ids 节点ID + * @param toWorkspaceId 分配到到工作空间ID + * @return msg + */ + @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission() + 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(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 查询所有的 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/DockerNetworkController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerNetworkController.java new file mode 100644 index 0000000000..99d6e66dd5 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerNetworkController.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerNetworkController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/15 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER) +@RequestMapping(value = "/docker/networks") +public class DockerNetworkController extends BaseDockerNetworkController { + + private final DockerInfoService dockerInfoService; + private final MachineDockerServer machineDockerServer; + + public DockerNetworkController(DockerInfoService dockerInfoService, + MachineDockerServer machineDockerServer) { + this.dockerInfoService = dockerInfoService; + this.machineDockerServer = machineDockerServer; + } + + + @Override + protected Map toDockerParameter(String id) { + DockerInfoModel dockerInfoModel = dockerInfoService.getByKey(id); + Assert.notNull(dockerInfoModel, I18nMessageUtil.get("i18n.no_docker_info.d685")); + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(dockerInfoModel.getMachineDockerId()); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerSwarmInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerSwarmInfoController.java new file mode 100644 index 0000000000..a738793929 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerSwarmInfoController.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.controller.docker.base.BaseDockerSwarmInfoController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.model.docker.DockerSwarmInfoMode; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +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.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2022/2/13 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER_SWARM) +@RequestMapping(value = "/docker/swarm") +@Slf4j +public class DockerSwarmInfoController extends BaseDockerSwarmInfoController { + + private final DockerSwarmInfoService dockerSwarmInfoService; + private final MachineDockerServer machineDockerServer; + private final DockerInfoService dockerInfoService; + + public DockerSwarmInfoController(DockerSwarmInfoService dockerSwarmInfoService, + MachineDockerServer machineDockerServer, + DockerInfoService dockerInfoService) { + this.dockerSwarmInfoService = dockerSwarmInfoService; + this.machineDockerServer = machineDockerServer; + this.dockerInfoService = dockerInfoService; + } + + /** + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + // load list with page + PageResultDto resultDto = dockerSwarmInfoService.listPage(request); + resultDto.each(dockerSwarmInfoMode -> { + String swarmId = dockerSwarmInfoMode.getSwarmId(); + MachineDockerModel machineDocker = machineDockerServer.tryMachineDockerBySwarmId(swarmId); + dockerSwarmInfoMode.setMachineDocker(machineDocker); + }); + return JsonMessage.success("", resultDto); + } + + /** + * @return json + */ + @GetMapping(value = "list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listAll(HttpServletRequest request) { + // load list with all + List swarmInfoModes = dockerSwarmInfoService.listByWorkspace(request); + return JsonMessage.success("", swarmInfoModes); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@ValidatorItem String id, + @ValidatorItem String name, + @ValidatorItem String tag, + HttpServletRequest request) throws Exception { + String workspaceId = dockerSwarmInfoService.getCheckUserWorkspace(request); + DockerSwarmInfoMode dockerSwarmInfoMode1 = dockerSwarmInfoService.getByKey(id, request); + Assert.notNull(dockerSwarmInfoMode1, I18nMessageUtil.get("i18n.cluster_not_exist.4098")); + // 更新集群信息 + DockerSwarmInfoMode dockerSwarmInfoMode = new DockerSwarmInfoMode(); + dockerSwarmInfoMode.setId(id); + dockerSwarmInfoMode.setName(name); + dockerSwarmInfoMode.setTag(tag); + dockerSwarmInfoService.updateById(dockerSwarmInfoMode); + // 更新集群关联的 docker 工作空间的 tag + MachineDockerModel dockerModel = new MachineDockerModel(); + dockerModel.setSwarmId(dockerSwarmInfoMode1.getSwarmId()); + List machineDockerModels = machineDockerServer.listByBean(dockerModel); + Assert.notEmpty(machineDockerModels, I18nMessageUtil.get("i18n.docker_info_not_found.4f64")); + for (MachineDockerModel machineDockerModel : machineDockerModels) { + DockerInfoModel queryWhere = new DockerInfoModel(); + queryWhere.setMachineDockerId(machineDockerModel.getId()); + queryWhere.setWorkspaceId(workspaceId); + List dockerInfoModels = dockerInfoService.listByBean(queryWhere); + for (DockerInfoModel dockerInfoModel : dockerInfoModels) { + // 处理标签 + Collection allTag = StrUtil.splitTrim(dockerInfoModel.getTags(), StrUtil.COLON); + allTag = ObjectUtil.defaultIfNull(allTag, new ArrayList<>()); + if (!allTag.contains(tag)) { + allTag.add(tag); + } + allTag = allTag.stream().filter(StrUtil::isNotEmpty).collect(Collectors.toSet()); + String newTags = CollUtil.join(allTag, StrUtil.COLON, StrUtil.COLON, StrUtil.COLON); + // + DockerInfoModel update = new DockerInfoModel(); + update.setId(dockerInfoModel.getId()); + update.setTags(newTags); + dockerInfoService.updateById(update); + } + } + // + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, HttpServletRequest request) throws Exception { + dockerSwarmInfoService.delByKey(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + + @Override + protected Map toDockerParameter(String id) { + return machineDockerServer.dockerParameter(id); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerSwarmServiceController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerSwarmServiceController.java new file mode 100644 index 0000000000..ee7034ec60 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerSwarmServiceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.controller.docker.base.BaseDockerSwarmServiceController; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/14 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER_SWARM) +@RequestMapping(value = "/docker/swarm-service") +@Slf4j +public class DockerSwarmServiceController extends BaseDockerSwarmServiceController { + + private final MachineDockerServer machineDockerServer; + + public DockerSwarmServiceController(ServerConfig serverConfig, + MachineDockerServer machineDockerServer) { + super(serverConfig); + this.machineDockerServer = machineDockerServer; + } + + + @Override + protected Map toDockerParameter(String id) { + return machineDockerServer.dockerParameter(id); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerVolumeController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerVolumeController.java new file mode 100644 index 0000000000..37e1447a70 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerVolumeController.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerVolumeController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@RestController +@Feature(cls = ClassFeature.DOCKER) +@RequestMapping(value = "/docker/volumes") +public class DockerVolumeController extends BaseDockerVolumeController { + + private final DockerInfoService dockerInfoService; + private final MachineDockerServer machineDockerServer; + + public DockerVolumeController(DockerInfoService dockerInfoService, + MachineDockerServer machineDockerServer) { + this.dockerInfoService = dockerInfoService; + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + DockerInfoModel dockerInfoModel = dockerInfoService.getByKey(id); + Assert.notNull(dockerInfoModel, I18nMessageUtil.get("i18n.no_docker_info.d685")); + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(dockerInfoModel.getMachineDockerId()); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerContainerController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerContainerController.java new file mode 100644 index 0000000000..514197b6cf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerContainerController.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker.base; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.system.ServerConfig; +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.RequestBody; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@Slf4j +public abstract class BaseDockerContainerController extends BaseDockerController { + protected final ServerConfig serverConfig; + + protected BaseDockerContainerController(ServerConfig serverConfig) { + this.serverConfig = serverConfig; + } + + @GetMapping(value = "info", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage info(@ValidatorItem String id) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + JSONObject info = plugin.execute("info", parameter, JSONObject.class); + return JsonMessage.success("", info); + } + + @PostMapping(value = "prune", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission + public IJsonMessage prune(@ValidatorItem String id, @ValidatorItem String pruneType, String labels, String until, String dangling) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("pruneType", pruneType); + parameter.put("labels", labels); + parameter.put("until", until); + parameter.put("dangling", dangling); + // + Long spaceReclaimed = plugin.execute("prune", parameter, Long.class); + spaceReclaimed = ObjectUtil.defaultIfNull(spaceReclaimed, 0L); + return JsonMessage.success(I18nMessageUtil.get("i18n.trim_completed_with_recovered_space.0463") + FileUtil.readableFileSize(spaceReclaimed)); + } + + /** + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(@ValidatorItem String id) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("name", getParameter("name")); + parameter.put("containerId", getParameter("containerId")); + parameter.put("imageId", getParameter("imageId")); + parameter.put("showAll", getParameter("showAll")); + List listContainer = (List) plugin.execute("listContainer", parameter); + return JsonMessage.success("", listContainer); + } + + /** + * @return json + */ + @PostMapping(value = "list-compose", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listCompose(@ValidatorItem String id) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("name", getParameter("name")); + parameter.put("containerId", getParameter("containerId")); + parameter.put("imageId", getParameter("imageId")); + parameter.put("showAll", getParameter("showAll")); + List listContainer = (List) plugin.execute("listComposeContainer", parameter); + return JsonMessage.success("", listContainer); + } + + /** + * @return json + */ + @GetMapping(value = "remove", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, String containerId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("containerId", containerId); + plugin.execute("removeContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } + + /** + * @return json + */ + @GetMapping(value = "start", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage start(@ValidatorItem String id, String containerId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("containerId", containerId); + plugin.execute("startContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } + + + /** + * @return json + */ + @GetMapping(value = "stop", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage stop(@ValidatorItem String id, String containerId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("containerId", containerId); + plugin.execute("stopContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } + + + /** + * @return json + */ + @GetMapping(value = "restart", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage restart(@ValidatorItem String id, String containerId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("containerId", containerId); + plugin.execute("restartContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } + + /** + * @return json + */ + @GetMapping(value = "stats", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage> stats(@ValidatorItem String id, String containerId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("containerId", containerId); + Map stats = (Map) plugin.execute("stats", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c"), stats); + } + + /** + * @return json + */ + @GetMapping(value = "inspect-container", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage inspectContainer(@ValidatorItem String id, @ValidatorItem String containerId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("containerId", containerId); + JSONObject results = (JSONObject) plugin.execute("inspectContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c"), results); + } + + /** + * 修改容器配置 + * NanoCpus:以 10-9 (十億分之一) 個 CPU 為單位的 CPU 配額。 例如,250000000 nanocpus = 0.25 CPU。 + * + * @return json + */ + @PostMapping(value = "update-container", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage updateContainer(@RequestBody JSONObject jsonObject) throws Exception { + // @ValidatorItem String id, String containerId + String id = jsonObject.getString("id"); + Assert.hasText(id, I18nMessageUtil.get("i18n.id_cannot_be_empty.8f2c")); + jsonObject.remove("id"); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.putAll(jsonObject); + JSONObject results = (JSONObject) plugin.execute("updateContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c"), results); + } + + /** + * drop old container and create new container + * + * @return json + */ + @PostMapping(value = "rebuild-container", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage reBuildContainer(@RequestBody JSONObject jsonObject) throws Exception { + String id = jsonObject.getString("id"); + String containerId = jsonObject.getString("containerId"); + Assert.hasText(id, I18nMessageUtil.get("i18n.id_cannot_be_empty.8f2c")); + String imageId = jsonObject.getString("imageId"); + Assert.hasText(imageId, I18nMessageUtil.get("i18n.image_cannot_be_empty.1600")); + String name = jsonObject.getString("name"); + Assert.hasText(name, I18nMessageUtil.get("i18n.container_name_cannot_be_empty.14b1")); + + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + + // drop old container + if (StrUtil.isNotEmpty(containerId)) { + parameter.put("containerId", containerId); + try { + plugin.execute("removeContainer", parameter); + } catch (com.github.dockerjava.api.exception.NotFoundException notFoundException) { + log.warn(notFoundException.getMessage()); + } + } + + // create new container + parameter.putAll(jsonObject); + plugin.execute("createContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.rebuild_success.5938")); + } + + /** + * 下载拉取的日志 + * + * @param id id + */ + @GetMapping(value = "download-log") + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + HttpServletResponse response) { + File file = FileUtil.file(serverConfig.getUserTempPath(), "docker-log", id + ".log"); + if (!file.exists()) { + ServletUtil.write(response, new JsonMessage<>(201, I18nMessageUtil.get("i18n.no_log_file.bacf")).toString(), MediaType.APPLICATION_JSON_VALUE); + return; + } + ServletUtil.write(response, file); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerController.java new file mode 100644 index 0000000000..c0b7dcfe29 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerController.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker.base; + +import org.dromara.jpom.common.BaseServerController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2023/3/3 + */ +public abstract class BaseDockerController extends BaseServerController { + + + /** + * 根据参数 id 获取 docker 信息 + * + * @param id id + * @return docker 信息 + */ + protected abstract Map toDockerParameter(String id); + +} 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 new file mode 100644 index 0000000000..1ea3dff143 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerImagesController.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.LogRecorder; +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.RequestBody; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@Slf4j +public abstract class BaseDockerImagesController extends BaseDockerController { + + protected final ServerConfig serverConfig; + + public BaseDockerImagesController(ServerConfig serverConfig) { + this.serverConfig = serverConfig; + } + + + /** + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(@ValidatorItem String id) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("name", getParameter("name")); + parameter.put("showAll", getParameter("showAll")); + parameter.put("dangling", getParameter("dangling")); + parameter.put("workspaceId", getWorkspaceId()); + List listContainer = (List) plugin.execute("listImages", parameter); + return JsonMessage.success("", listContainer); + } + + + /** + * @return json + */ + @GetMapping(value = "remove", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, String imageId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("imageId", imageId); + parameter.put("workspaceId", getWorkspaceId()); + plugin.execute("removeImage", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } + + + /** + * @return json + */ + @GetMapping(value = "batchRemove", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage batchRemove(@ValidatorItem String id, String[] imagesIds) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("imagesIds", imagesIds); + parameter.put("workspaceId", getWorkspaceId()); + plugin.execute("batchRemove", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } + + /** + * @return json + */ + @GetMapping(value = "inspect", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage inspect(@ValidatorItem String id, String imageId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("imageId", imageId); + parameter.put("workspaceId", getWorkspaceId()); + JSONObject inspectImage = (JSONObject) plugin.execute("inspectImage", parameter); + return JsonMessage.success("", inspectImage); + } + + /** + * @return json + */ + @GetMapping(value = "pull-image", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage pullImage(@ValidatorItem String id, String repository) { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("repository", repository); + parameter.put("workspaceId", getWorkspaceId()); + // + String uuid = IdUtil.fastSimpleUUID(); + File file = FileUtil.file(serverConfig.getUserTempPath(), "docker-log", uuid + ".log"); + LogRecorder logRecorder = LogRecorder.builder().file(file).build(); + logRecorder.system("start pull {}", repository); + Consumer logConsumer = logRecorder::info; + parameter.put("logConsumer", logConsumer); + I18nThreadUtil.execute(() -> { + try { + plugin.execute("pullImage", parameter); + logRecorder.system("pull end"); + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.pull_exception.b38d"), e); + } finally { + IoUtil.close(logRecorder); + } + + }); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_pulling.57ab"), uuid); + } + + /** + * + */ + @GetMapping(value = "save-image", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public void saveImage(@ValidatorItem String id, String imageId, HttpServletResponse response) { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("imageId", imageId); + // + try { + Tuple saveImage = (Tuple) plugin.execute("saveImage", parameter); + if (saveImage == null) { + ServletUtil.write(response, new JsonMessage<>(405, I18nMessageUtil.get("i18n.image_not_exist.ee17")).toString(), MediaType.APPLICATION_JSON_VALUE); + return; + } + InputStream inputStream = saveImage.get(0); + String name = saveImage.get(1); + ServletUtil.write(response, inputStream, MediaType.APPLICATION_OCTET_STREAM_VALUE, name); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.export_image_exception.cb1c"), e); + ServletUtil.write(response, new JsonMessage<>(500, I18nMessageUtil.get("i18n.export_image_exception.cb1c")).toString(), MediaType.APPLICATION_JSON_VALUE); + } + } + + /** + * + */ + @PostMapping(value = "load-image", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage loadImage(@ValidatorItem String id, + MultipartFile file) throws Exception { + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + boolean expression = StrUtil.equalsIgnoreCase(extName, "tar"); + Assert.state(expression, I18nMessageUtil.get("i18n.only_tar_files_supported.dcc4")); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("stream", file.getInputStream()); + // + plugin.execute("loadImage", parameter); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.import_success.b6d1")); + } + + /** + * 获取拉取的日志 + * + * @param id id + * @param line 需要获取的行号 + * @return json + */ + @GetMapping(value = "pull-image-log", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getNowLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.line_number_error.c65d") int line) { + File file = FileUtil.file(serverConfig.getUserTempPath(), "docker-log", id + ".log"); + if (!file.exists()) { + return new JsonMessage<>(201, I18nMessageUtil.get("i18n.no_log_file.bacf")); + } + JSONObject data = FileUtils.readLogFile(file, line); + return JsonMessage.success("", data); + } + + /** + * @return json + */ + @PostMapping(value = "create-container", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage createContainer(@RequestBody JSONObject jsonObject) throws Exception { + String id = jsonObject.getString("id"); + Assert.hasText(id, I18nMessageUtil.get("i18n.id_cannot_be_empty.8f2c")); + String imageId = jsonObject.getString("imageId"); + Assert.hasText(imageId, I18nMessageUtil.get("i18n.image_cannot_be_empty.1600")); + String name = jsonObject.getString("name"); + Assert.hasText(name, I18nMessageUtil.get("i18n.container_name_cannot_be_empty.14b1")); + + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.putAll(jsonObject); + parameter.put("workspaceId", getWorkspaceId()); + plugin.execute("createContainer", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.create_success.04a6")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerNetworkController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerNetworkController.java new file mode 100644 index 0000000000..4c71763e9d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerNetworkController.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker.base; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/15 + */ +public abstract class BaseDockerNetworkController extends BaseDockerController { + + /** + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(@ValidatorItem String id, String name, String networkId) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("name", name); + parameter.put("id", networkId); + List listContainer = (List) plugin.execute("listNetworks", parameter); + return JsonMessage.success("", listContainer); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmInfoController.java new file mode 100644 index 0000000000..0647899032 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmInfoController.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker.base; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/13 + */ +public abstract class BaseDockerSwarmInfoController extends BaseDockerController { + + @PostMapping(value = "node-list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> nodeList( + @ValidatorItem String id, + String nodeId, String nodeName, String nodeRole) throws Exception { + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map map = this.toDockerParameter(id); + map.put("id", nodeId); + map.put("name", nodeName); + map.put("role", nodeRole); + List listSwarmNodes = (List) plugin.execute("listSwarmNodes", map); + return new JsonMessage<>(200, "", listSwarmNodes); + } + + + /** + * 修改节点信息 + * + * @param id 集群ID + * @return json + */ + @PostMapping(value = "update", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage update(@ValidatorItem String id, + @ValidatorItem String nodeId, + @ValidatorItem String availability, + @ValidatorItem String role) throws Exception { + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map map = this.toDockerParameter(id); + map.put("nodeId", nodeId); + map.put("availability", availability); + map.put("role", role); + plugin.execute("updateSwarmNode", map); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.modify_success.69be")); + } +} 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 new file mode 100644 index 0000000000..008445e00a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmServiceController.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.LogRecorder; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** + * @author bwcx_jzy + * @since 2022/2/14 + */ +@Slf4j +public abstract class BaseDockerSwarmServiceController extends BaseDockerController { + private static final TimedCache> LOG_CACHE = new TimedCache<>(30 * 1000); + protected final ServerConfig serverConfig; + + public BaseDockerSwarmServiceController(ServerConfig serverConfig) { + this.serverConfig = serverConfig; + // 30 秒检查一次 + LOG_CACHE.schedulePrune(30 * 1000); + // 监控过期 + LOG_CACHE.setListener((key, userIds) -> { + try { + log.debug(I18nMessageUtil.get("i18n.async_resource_expired.2ddc"), key, userIds); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map map = MapUtil.of("uuid", key); + plugin.execute("closeAsyncResource", map); + // + for (String userId : userIds) { + File file = FileUtil.file(serverConfig.getUserTempPath(userId), "docker-swarm-log", key + ".log"); + FileUtil.del(file); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.close_resource_failure.dc66"), e); + } + }); + } + + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list( + @ValidatorItem String id, + String serviceId, String serviceName) throws Exception { + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map map = this.toDockerParameter(id); + map.put("id", serviceId); + map.put("name", serviceName); + List listSwarmNodes = (List) plugin.execute("listServices", map); + return new JsonMessage<>(200, "", listSwarmNodes); + } + + @PostMapping(value = "task-list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> taskList( + @ValidatorItem String id, + String serviceId, String taskId, String taskName, String taskNode, String taskState) throws Exception { + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map map = this.toDockerParameter(id); + map.put("id", taskId); + map.put("serviceId", serviceId); + map.put("name", taskName); + map.put("node", taskNode); + map.put("state", taskState); + List listSwarmNodes = (List) plugin.execute("listTasks", map); + return new JsonMessage<>(200, "", listSwarmNodes); + } + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage> del(@ValidatorItem String id, @ValidatorItem String serviceId) throws Exception { + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map map = this.toDockerParameter(id); + map.put("serviceId", serviceId); + plugin.execute("removeService", map); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.delete_service_success.4d73")); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage> edit(@RequestBody JSONObject jsonObject) throws Exception { + // + String id = jsonObject.getString("id"); + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map map = this.toDockerParameter(id); + map.putAll(jsonObject); + plugin.execute("updateService", map); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.modify_service_success.bd75")); + } + + + /** + * @return json + */ + @GetMapping(value = "start-log", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage pullImage(@ValidatorItem String id, + @ValidatorItem String type, + @ValidatorItem String dataId, + Integer tail, + String since, + Boolean timestamps) { + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put(StrUtil.equalsIgnoreCase(type, "service") ? "serviceId" : "taskId", dataId); + // + String uuid = IdUtil.fastSimpleUUID(); + File file = FileUtil.file(serverConfig.getUserTempPath(), "docker-swarm-log", uuid + ".log"); + LogRecorder logRecorder = LogRecorder.builder().file(file).build(); + + logRecorder.system("start pull {}", dataId); + logRecorder.info(""); + Consumer logConsumer = logRecorder::append; + parameter.put("charset", CharsetUtil.CHARSET_UTF_8); + parameter.put("consumer", logConsumer); + // + tail = ObjectUtil.defaultIfNull(tail, 50); + tail = Math.max(tail, 1); + parameter.put("tail", tail); + //parameter.put("since", since); + parameter.put("timestamps", timestamps); + // 操作id + parameter.put("uuid", uuid); + I18nThreadUtil.execute(() -> { + try { + plugin.execute(StrUtil.equalsIgnoreCase(type, "service") ? "logService" : "logTask", parameter); + logRecorder.system("pull end"); + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.pull_log_exception.cc3e"), e); + } finally { + IoUtil.close(logRecorder); + } + }); + // 添加到缓存中 + LOG_CACHE.put(uuid, CollUtil.newHashSet(getUser().getId())); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_pulling.57ab"), uuid); + } + + /** + * 获取拉取的日志 + * + * @param id id + * @param line 需要获取的行号 + * @return json + */ + @GetMapping(value = "pull-log", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getNowLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.line_number_error.c65d") int line) { + File file = FileUtil.file(serverConfig.getUserTempPath(), "docker-swarm-log", id + ".log"); + if (!file.exists()) { + return new JsonMessage<>(201, I18nMessageUtil.get("i18n.no_log_file.bacf")); + } + JSONObject data = FileUtils.readLogFile(file, line); + // 更新缓存,避免超时被清空 + synchronized (BaseDockerSwarmServiceController.class) { + Set userIds = ObjectUtil.defaultIfNull(LOG_CACHE.get(id), new HashSet<>()); + userIds.add(getUser().getId()); + LOG_CACHE.put(id, userIds); + } + return JsonMessage.success("", data); + } + + /** + * 下载拉取的日志 + * + * @param id id + */ + @GetMapping(value = "download-log") + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + HttpServletResponse response) { + File file = FileUtil.file(serverConfig.getUserTempPath(), "docker-swarm-log", id + ".log"); + if (!file.exists()) { + ServletUtil.write(response, new JsonMessage<>(201, I18nMessageUtil.get("i18n.no_log_file.bacf")).toString(), MediaType.APPLICATION_JSON_VALUE); + return; + } + ServletUtil.write(response, file); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerVolumeController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerVolumeController.java new file mode 100644 index 0000000000..950602e709 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerVolumeController.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.docker.base; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +public abstract class BaseDockerVolumeController extends BaseDockerController { + /** + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(@ValidatorItem String id) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("name", getParameter("name")); + parameter.put("dangling", getParameter("dangling")); + List listContainer = (List) plugin.execute("listVolumes", parameter); + return JsonMessage.success("", listContainer); + } + + + /** + * @return json + */ + @GetMapping(value = "remove", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, String volumeName) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map parameter = this.toDockerParameter(id); + parameter.put("volumeName", volumeName); + plugin.execute("removeVolume", parameter); + return JsonMessage.success(I18nMessageUtil.get("i18n.execution_succeeded.f56c")); + } +} 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 new file mode 100644 index 0000000000..142e115a20 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorListController.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.monitor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.MonitorModel; +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.dblog.DbMonitorNotifyLogService; +import org.dromara.jpom.service.monitor.MonitorService; +import org.dromara.jpom.service.node.ProjectInfoCacheService; +import org.dromara.jpom.service.user.UserService; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +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 2019/6/15 + */ +@RestController +@RequestMapping(value = "/monitor") +@Feature(cls = ClassFeature.MONITOR) +public class MonitorListController extends BaseServerController { + + private final MonitorService monitorService; + private final DbMonitorNotifyLogService dbMonitorNotifyLogService; + private final UserService userService; + private final ProjectInfoCacheService projectInfoCacheService; + + public MonitorListController(MonitorService monitorService, + DbMonitorNotifyLogService dbMonitorNotifyLogService, + UserService userService, + ProjectInfoCacheService projectInfoCacheService) { + this.monitorService = monitorService; + this.dbMonitorNotifyLogService = dbMonitorNotifyLogService; + this.userService = userService; + this.projectInfoCacheService = projectInfoCacheService; + } + + /** + * 展示监控列表 + * + * @return json + */ + @RequestMapping(value = "getMonitorList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getMonitorList(HttpServletRequest request) { + PageResultDto pageResultDto = monitorService.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * 删除列表 + * + * @param id id + * @return json + */ + @RequestMapping(value = "deleteMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.delete_failure.acf0") String id, HttpServletRequest request) { + // + + int delByKey = monitorService.delByKey(id, request); + if (delByKey > 0) { + // 删除日志 + dbMonitorNotifyLogService.delByWorkspace(request, entity -> entity.set("monitorId", id)); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + + /** + * 增加或修改监控 + * + * @param id id + * @param name name + * @param notifyUser user + * @return json + */ + @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 = "i18n.monitor_name_cannot_be_empty.514a") String name, + @ValidatorItem(msg = "i18n.configure_monitoring_interval.9741") String execCron, + String notifyUser, String webhook, + HttpServletRequest request) { + String status = getParameter("status"); + String autoRestart = getParameter("autoRestart"); + + JSONArray jsonArray = JSONArray.parseArray(notifyUser); +// List notifyUsers = jsonArray.toJavaList(String.class); + List notifyUserList = jsonArray.toJavaList(String.class); + if (CollUtil.isNotEmpty(notifyUserList)) { + for (String userId : notifyUserList) { + Assert.state(userService.exists(new UserModel(userId)), I18nMessageUtil.get("i18n.no_user_specified.6650") + userId); + } + } + String projects = getParameter("projects"); + JSONArray projectsArray = JSONArray.parseArray(projects); + List nodeProjects = projectsArray.toJavaList(MonitorModel.NodeProject.class); + Assert.notEmpty(nodeProjects, I18nMessageUtil.get("i18n.at_least_one_node_required.a290")); + for (MonitorModel.NodeProject nodeProject : nodeProjects) { + Assert.notEmpty(nodeProject.getProjects(), I18nMessageUtil.get("i18n.at_least_one_project_required.2bbd")); + for (String project : nodeProject.getProjects()) { + boolean exists = projectInfoCacheService.exists(nodeProject.getNode(), project); + Assert.state(exists, I18nMessageUtil.get("i18n.no_project_specified.0076") + project); + } + } + // + // 设置参数 + if (StrUtil.isNotEmpty(webhook)) { + Validator.validateMatchRegex(RegexPool.URL_HTTP, webhook, I18nMessageUtil.get("i18n.invalid_webhooks_address.d836")); + } + Assert.state(CollUtil.isNotEmpty(notifyUserList) || StrUtil.isNotEmpty(webhook), I18nMessageUtil.get("i18n.alarm_contact_or_webhook_required.6c24")); + + boolean start = "on".equalsIgnoreCase(status); + MonitorModel monitorModel = monitorService.getByKey(id); + if (monitorModel == null) { + monitorModel = new MonitorModel(); + } + monitorModel.setAutoRestart("on".equalsIgnoreCase(autoRestart)); + monitorModel.setExecCron(this.checkCron(execCron)); + monitorModel.projects(nodeProjects); + monitorModel.setStatus(start); + monitorModel.setWebhook(webhook); + monitorModel.notifyUser(notifyUserList); + monitorModel.setName(name); + + if (StrUtil.isEmpty(id)) { + //添加监控 + monitorService.insert(monitorModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.addition_succeeded.3fda")); + } + + monitorService.updateById(monitorModel, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } +} 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 new file mode 100644 index 0000000000..21e4988e87 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorLogController.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.monitor; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.log.MonitorNotifyLog; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.dblog.DbMonitorNotifyLogService; +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; + +/** + * 监控列表 + * + * @author bwcx_jzy + * @since 2019/7/16 + */ +@RestController +@RequestMapping(value = "/monitor") +@Feature(cls = ClassFeature.MONITOR_LOG) +public class MonitorLogController extends BaseServerController { + + private final DbMonitorNotifyLogService dbMonitorNotifyLogService; + + public MonitorLogController(DbMonitorNotifyLogService dbMonitorNotifyLogService) { + this.dbMonitorNotifyLogService = dbMonitorNotifyLogService; + } + + /** + * 展示用户列表 + * + * @return json + */ + @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResult = dbMonitorNotifyLogService.listPage(request); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), 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 new file mode 100644 index 0000000000..c47870570d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorUserOptListController.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.monitor; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.MonitorUserOptModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.monitor.MonitorUserOptService; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +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; +import java.util.stream.Collectors; + +/** + * 监控用户操作 + * + * @author bwcx_jzy + * @since 2020/08/06 + */ +@RestController +@RequestMapping(value = "/monitor_user_opt") +@Feature(cls = ClassFeature.OPT_MONITOR) +public class MonitorUserOptListController extends BaseServerController { + + private final MonitorUserOptService monitorUserOptService; + + public MonitorUserOptListController(MonitorUserOptService monitorUserOptService) { + this.monitorUserOptService = monitorUserOptService; + } + + + @RequestMapping(value = "list_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getMonitorList(HttpServletRequest request) { + PageResultDto pageResultDto = monitorUserOptService.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * 操作监控类型列表 + * + * @return json + */ + @RequestMapping(value = "type_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getOperateTypeList() { + JSONObject jsonObject = new JSONObject(); + // + List classFeatureList = Arrays.stream(ClassFeature.values()) + .filter(classFeature -> classFeature != ClassFeature.NULL) + .map(classFeature -> { + JSONObject jsonObject1 = new JSONObject(); + String value = I18nMessageUtil.get(classFeature.getName().get()); + jsonObject1.put("title", value); + jsonObject1.put("value", classFeature.name()); + return jsonObject1; + }) + .collect(Collectors.toList()); + jsonObject.put("classFeature", classFeatureList); + // + List methodFeatureList = Arrays.stream(MethodFeature.values()) + .filter(methodFeature -> methodFeature != MethodFeature.NULL && methodFeature != MethodFeature.LIST) + .map(classFeature -> { + JSONObject jsonObject1 = new JSONObject(); + String value = I18nMessageUtil.get(classFeature.getName().get()); + jsonObject1.put("title", value); + jsonObject1.put("value", classFeature.name()); + return jsonObject1; + }) + .collect(Collectors.toList()); + jsonObject.put("methodFeature", methodFeatureList); + + return JsonMessage.success("", jsonObject); + } + + /** + * 删除列表 + * + * @param id id + * @return json + */ + @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.delete_failure.acf0") String id, HttpServletRequest request) { + // + monitorUserOptService.delByKey(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + + /** + * 增加或修改监控 + * + * @param id id + * @param name name + * @param notifyUser user + * @return json + */ + @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 = "i18n.monitor_name_cannot_be_empty.514a") String name, + String notifyUser, + String monitorUser, + String monitorOpt, + String monitorFeature) { + + String status = getParameter("status"); + + JSONArray jsonArray = JSONArray.parseArray(notifyUser); + List notifyUsers = jsonArray.toJavaList(String.class) + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + Assert.notEmpty(notifyUsers, I18nMessageUtil.get("i18n.select_alarm_contact.d02a")); + + + JSONArray monitorUserArray = JSONArray.parseArray(monitorUser); + List monitorUserArrays = monitorUserArray.toJavaList(String.class) + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + Assert.notEmpty(monitorUserArrays, I18nMessageUtil.get("i18n.select_monitoring_person.0756")); + + + JSONArray monitorOptArray = JSONArray.parseArray(monitorOpt); + List monitorOptArrays = monitorOptArray + .stream() + .map(o -> EnumUtil.fromString(MethodFeature.class, StrUtil.toString(o), null)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + Assert.notEmpty(monitorOptArrays, I18nMessageUtil.get("i18n.select_monitoring_operation.3057")); + + JSONArray monitorFeatureArray = JSONArray.parseArray(monitorFeature); + List monitorFeatureArrays = monitorFeatureArray + .stream() + .map(o -> EnumUtil.fromString(ClassFeature.class, StrUtil.toString(o), null)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + Assert.notEmpty(monitorFeatureArrays, I18nMessageUtil.get("i18n.select_monitoring_function.c6e4")); + + + boolean start = "on".equalsIgnoreCase(status); + MonitorUserOptModel monitorModel = monitorUserOptService.getByKey(id); + if (monitorModel == null) { + monitorModel = new MonitorUserOptModel(); + } + monitorModel.monitorUser(monitorUserArrays); + monitorModel.setStatus(start); + monitorModel.monitorOpt(monitorOptArrays); + monitorModel.monitorFeature(monitorFeatureArrays); + monitorModel.notifyUser(notifyUsers); + monitorModel.setName(name); + + if (StrUtil.isEmpty(id)) { + //添加监控 + monitorUserOptService.insert(monitorModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.addition_succeeded.3fda")); + } + monitorUserOptService.updateById(monitorModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 开启或关闭监控 + * + * @param id id + * @param status 状态 + * @return json + */ + @RequestMapping(value = "changeStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage changeStatus(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_id_cannot_be_empty.86cc") String id, + String status) { + MonitorUserOptModel monitorModel = monitorUserOptService.getByKey(id); + Assert.notNull(monitorModel, I18nMessageUtil.get("i18n.monitoring_item_not_exist.32c8")); + + boolean bStatus = Convert.toBool(status, false); + monitorModel.setStatus(bStatus); + monitorUserOptService.updateById(monitorModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/SystemMailConfigController.java b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/SystemMailConfigController.java new file mode 100644 index 0000000000..d6ffa2cd0c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/SystemMailConfigController.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.monitor; + +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSON; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.MailAccountModel; +import org.dromara.jpom.monitor.EmailUtil; +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.plugin.PluginFactory; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 监控邮箱配置 + * + * @author bwcx_jzy + * @since 2019/7/16 + */ +@RestController +@RequestMapping(value = "system") +@Feature(cls = ClassFeature.SYSTEM_EMAIL) +@SystemPermission +public class SystemMailConfigController extends BaseServerController { + + private final SystemParametersServer systemParametersServer; + + public SystemMailConfigController(SystemParametersServer systemParametersServer) { + this.systemParametersServer = systemParametersServer; + } + + /** + * load mail config data + * 加载邮件配置 + * + * @return json + * @author Hotstrip + */ + @PostMapping(value = "mail-config-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage mailConfigData() { + MailAccountModel item = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); + if (item != null) { + item.setPass(null); + } + return JsonMessage.success("", item); + } + + @PostMapping(value = "mailConfig_save.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage listData(MailAccountModel mailAccountModel) throws Exception { + Assert.notNull(mailAccountModel, I18nMessageUtil.get("i18n.please_fill_in_information_and_check_validity.771a")); + Assert.hasText(mailAccountModel.getHost(), I18nMessageUtil.get("i18n.please_fill_in_host.7922")); + Assert.hasText(mailAccountModel.getUser(), I18nMessageUtil.get("i18n.please_fill_in_user.5f52")); + Assert.hasText(mailAccountModel.getFrom(), I18nMessageUtil.get("i18n.please_fill_in_from.7268")); + // 验证是否正确 + MailAccountModel item = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); + if (item != null) { + mailAccountModel.setPass(StrUtil.emptyToDefault(mailAccountModel.getPass(), item.getPass())); + } else { + Assert.hasText(mailAccountModel.getPass(), I18nMessageUtil.get("i18n.please_fill_in_password.455f")); + } + IPlugin plugin = PluginFactory.getPlugin("email"); + Object json = JSON.toJSON(mailAccountModel); + Map map = new HashMap<>(1); + map.put("data", json); + boolean checkInfo = plugin.execute("checkInfo", map, Boolean.class); + Assert.state(checkInfo, I18nMessageUtil.get("i18n.email_verification_failed.5863")); + systemParametersServer.upsert(MailAccountModel.ID, mailAccountModel, MailAccountModel.ID); + // + EmailUtil.refreshConfig(); + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } +} 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 new file mode 100644 index 0000000000..e241c2dde7 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeEditController.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node; + +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.i18n.I18nMessageUtil; +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.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.dblog.BuildInfoService; +import org.dromara.jpom.service.monitor.MonitorService; +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.outgiving.LogReadServer; +import org.dromara.jpom.service.outgiving.OutGivingServer; +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.util.List; + +/** + * 节点管理 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@RestController +@RequestMapping(value = "/node") +@Feature(cls = ClassFeature.NODE) +public class NodeEditController extends BaseServerController { + + private final OutGivingServer outGivingServer; + private final MonitorService monitorService; + private final BuildInfoService buildService; + private final LogReadServer logReadServer; + private final ProjectInfoCacheService projectInfoCacheService; + private final NodeScriptServer nodeScriptServer; + private final NodeScriptExecuteLogServer nodeScriptExecuteLogServer; + + public NodeEditController(OutGivingServer outGivingServer, + MonitorService monitorService, + BuildInfoService buildService, + LogReadServer logReadServer, + ProjectInfoCacheService projectInfoCacheService, + NodeScriptServer nodeScriptServer, + NodeScriptExecuteLogServer nodeScriptExecuteLogServer) { + this.outGivingServer = outGivingServer; + this.monitorService = monitorService; + this.buildService = buildService; + this.logReadServer = logReadServer; + this.projectInfoCacheService = projectInfoCacheService; + this.nodeScriptServer = nodeScriptServer; + this.nodeScriptExecuteLogServer = nodeScriptExecuteLogServer; + } + + + @PostMapping(value = "list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listJson(HttpServletRequest request) { + PageResultDto nodeModelPageResultDto = nodeService.listPage(request); + nodeModelPageResultDto.each(nodeModel -> nodeModel.setMachineNodeData(machineNodeServer.getByKey(nodeModel.getMachineId()))); + return JsonMessage.success("", nodeModelPageResultDto); + } + + @GetMapping(value = "list_data_all.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listDataAll(HttpServletRequest request) { + List list = nodeService.listByWorkspace(request); + return JsonMessage.success("", list); + } + + /** + * 查询所有的分组 + * + * @return list + */ + @GetMapping(value = "list_group_all.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listGroupAll(HttpServletRequest request) { + List listGroup = nodeService.listGroup(request); + return JsonMessage.success("", listGroup); + } + + @PostMapping(value = "save.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(HttpServletRequest request) { + nodeService.update(request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + + /** + * 删除节点 + * + * @param id 节点id + * @return json + */ + @PostMapping(value = "del.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, HttpServletRequest request) { + this.checkDataBind(id, request, I18nMessageUtil.get("i18n.delete_action.2f4a")); + // + { + ProjectInfoCacheModel projectInfoCacheModel = new ProjectInfoCacheModel(); + projectInfoCacheModel.setNodeId(id); + projectInfoCacheModel.setWorkspaceId(projectInfoCacheService.getCheckUserWorkspace(request)); + boolean exists = projectInfoCacheService.exists(projectInfoCacheModel); + Assert.state(!exists, I18nMessageUtil.get("i18n.project_exists.f4e0")); + } + // + { + NodeScriptCacheModel nodeScriptCacheModel = new NodeScriptCacheModel(); + nodeScriptCacheModel.setNodeId(id); + nodeScriptCacheModel.setWorkspaceId(nodeScriptServer.getCheckUserWorkspace(request)); + boolean exists = nodeScriptServer.exists(nodeScriptCacheModel); + Assert.state(!exists, I18nMessageUtil.get("i18n.script_template_exists.3f86")); + } + // + this.delNodeData(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + private void checkDataBind(String id, HttpServletRequest request, String msg) { + // 判断分发 + boolean checkNode = outGivingServer.checkNode(id, request); + Assert.state(!checkNode, I18nMessageUtil.get("i18n.node_has_distribution_projects_cannot_delete.3987") + msg); + boolean checkLogRead = logReadServer.checkNode(id, request); + Assert.state(!checkLogRead, I18nMessageUtil.get("i18n.node_has_log_search_projects_cannot_delete.a388") + msg); + // 监控 + boolean checkNode1 = monitorService.checkNode(id); + Assert.state(!checkNode1, I18nMessageUtil.get("i18n.node_has_monitoring_items_cannot_delete.0304") + msg); + boolean checkNode2 = buildService.checkNode(id, request); + Assert.state(!checkNode2, I18nMessageUtil.get("i18n.node_has_build_items_cannot_delete.a952") + msg); + } + + private void delNodeData(String id, HttpServletRequest request) { + // + int i = nodeService.delByKey(id, request); + if (i > 0) { + // + nodeScriptExecuteLogServer.delCache(id, request); + } + } + + /** + * 解绑 + * + * @param id 分发id + * @return json + */ + @GetMapping(value = "unbind.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage unbind(String id, HttpServletRequest request) { + this.checkDataBind(id, request, I18nMessageUtil.get("i18n.unbind.6633")); + // + projectInfoCacheService.delCache(id, request); + nodeScriptServer.delCache(id, request); + this.delNodeData(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + + /** + * 同步到指定工作空间 + * + * @param ids 节点ID + * @param toWorkspaceId 分配到到工作空间ID + * @return msg + */ + @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission() + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); + // + nodeService.checkUserWorkspace(toWorkspaceId); + nodeService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 排序 + * + * @param id 节点ID + * @param method 方法 + * @param compareId 比较的ID + * @return msg + */ + @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) { + if (StrUtil.equalsIgnoreCase(method, "top")) { + nodeService.sortToTop(id, request); + } else if (StrUtil.equalsIgnoreCase(method, "up")) { + nodeService.sortMoveUp(id, compareId, request); + } else if (StrUtil.equalsIgnoreCase(method, "down")) { + nodeService.sortMoveDown(id, compareId, request); + } else { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.unsupported_method.a1de") + method); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } +} 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 new file mode 100644 index 0000000000..cdd867b83e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeProjectInfoController.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node; + +import cn.hutool.core.io.FileUtil; +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.BaseServerController; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.UrlRedirectUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.ProjectInfoCacheModel; +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.node.ProjectInfoCacheService; +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; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 节点管理 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@RestController +@RequestMapping(value = "/node") +@Feature(cls = ClassFeature.NODE) +@Slf4j +public class NodeProjectInfoController extends BaseServerController { + + private final ProjectInfoCacheService projectInfoCacheService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public NodeProjectInfoController(ProjectInfoCacheService projectInfoCacheService, + TriggerTokenLogServer triggerTokenLogServer) { + this.projectInfoCacheService = projectInfoCacheService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + + /** + * load node project list + * 加载节点项目列表 + * + * @return json + * @author Hotstrip + */ + @PostMapping(value = "project_list", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> projectList(HttpServletRequest request) { + PageResultDto resultDto = projectInfoCacheService.listPage(request); + return JsonMessage.success("", resultDto); + } + + /** + * load node project list + * 加载节点项目列表 + * + * @return json + * @author Hotstrip + */ + @GetMapping(value = "project_list_all", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> projectListAll(HttpServletRequest request) { + List projectInfoCacheModels = projectInfoCacheService.listByWorkspace(request); + return JsonMessage.success("", projectInfoCacheModels); + } + + /** + * 查询所有的分组 + * + * @return list + */ + @GetMapping(value = "list-project-group-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listGroupAll(HttpServletRequest request) { + List listGroup = projectInfoCacheService.listGroup(request); + return JsonMessage.success("", listGroup); + } + + /** + * 同步节点项目 + * + * @return json + */ + @GetMapping(value = "sync_project", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT, method = MethodFeature.DEL) + public IJsonMessage syncProject(String nodeId, HttpServletRequest request) { + NodeModel nodeModel = nodeService.getByKey(nodeId); + Assert.notNull(nodeModel, I18nMessageUtil.get("i18n.node_not_exist.760e")); + int count = projectInfoCacheService.delCache(nodeId, request); + String msg = projectInfoCacheService.syncExecuteNode(nodeModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.active_clearance_colon.96a6") + count + StrUtil.SPACE + msg); + } + + /** + * 排序 + * + * @param id 节点ID + * @param method 方法 + * @param compareId 比较的ID + * @return msg + */ + @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) { + if (StrUtil.equalsIgnoreCase(method, "top")) { + projectInfoCacheService.sortToTop(id, request); + } else if (StrUtil.equalsIgnoreCase(method, "up")) { + projectInfoCacheService.sortMoveUp(id, compareId, request); + } else if (StrUtil.equalsIgnoreCase(method, "down")) { + projectInfoCacheService.sortMoveDown(id, compareId, request); + } else { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.unsupported_method.a1de") + method); + } + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @RequestMapping(value = "project-trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + 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())); + projectInfoCacheService.updateById(updateItem); + } else { + updateItem = item; + } + Map map = this.getBuildToken(updateItem, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + 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()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + Map map = new HashMap<>(10); + map.put("triggerUrl", FileUtil.normalize(triggerBuildUrl)); + String batchTriggerBuildUrl = String.format("/%s/%s", contextPath, ServerOpenApi.SERVER_PROJECT_TRIGGER_BATCH); + map.put("batchTriggerUrl", FileUtil.normalize(batchTriggerBuildUrl)); + + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } +} 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 new file mode 100644 index 0000000000..6f5b3b2ac3 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeUpdateController.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.*; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.func.openapi.controller.NodeInfoController; +import org.dromara.jpom.model.AgentFileModel; +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.SystemParametersServer; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2021/11/29 + */ +@RestController +@RequestMapping(value = "/node") +@SystemPermission(superUser = true) +@Feature(cls = ClassFeature.UPGRADE_NODE_LIST) +@Slf4j +public class NodeUpdateController extends BaseServerController { + + private final SystemParametersServer systemParametersServer; + private final ServerConfig serverConfig; + + public NodeUpdateController(SystemParametersServer systemParametersServer, + ServerConfig serverConfig) { + this.systemParametersServer = systemParametersServer; + this.serverConfig = serverConfig; + } + + /** + * 远程下载 + * + * @return json + * @see RemoteVersion + */ + @GetMapping(value = "download_remote.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.REMOTE_DOWNLOAD) + public IJsonMessage downloadRemote() throws IOException { + String saveDir = serverConfig.getAgentPath().getAbsolutePath(); + Tuple download = RemoteVersion.download(saveDir, Type.Agent, false); + // 保存文件 + this.saveAgentFile(download); + return JsonMessage.success(I18nMessageUtil.get("i18n.download_success.5094")); + } + + /** + * 检查版本更新 + * + * @return json + * @see RemoteVersion + * @see AgentFileModel + */ + @GetMapping(value = "check_version.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage checkVersion() { + cn.keepbx.jpom.RemoteVersion remoteVersion = RemoteVersion.cacheInfo(); + AgentFileModel agentFileModel = systemParametersServer.getConfig(AgentFileModel.ID, AgentFileModel.class, agentFileModel1 -> { + if (agentFileModel1 == null || !FileUtil.exist(agentFileModel1.getSavePath())) { + return null; + } + return agentFileModel1; + }); + JSONObject jsonObject = new JSONObject(); + if (remoteVersion == null) { + jsonObject.put("upgrade", false); + } else { + String tagName = StrUtil.removePrefixIgnoreCase(remoteVersion.getTagName(), "v"); + jsonObject.put("tagName", tagName); + if (agentFileModel == null) { + jsonObject.put("upgrade", true); + } else { + String version = StrUtil.removePrefixIgnoreCase(agentFileModel.getVersion(), "v"); + jsonObject.put("upgrade", StrUtil.compareVersion(version, tagName) < 0); + jsonObject.put("path", agentFileModel.getSavePath()); + } + } + return JsonMessage.success("", jsonObject); + } + + @RequestMapping(value = "upload-agent-sharding", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @SystemPermission + @Feature(method = MethodFeature.UPLOAD, log = false) + public IJsonMessage uploadAgentSharding(MultipartFile file, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5) throws IOException { + File userTempPath = serverConfig.getUserTempPath(); + this.uploadSharding(file, userTempPath.getAbsolutePath(), sliceId, totalSlice, nowSlice, fileSumMd5, "jar", "zip"); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + @RequestMapping(value = "upload-agent-sharding-merge", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @SystemPermission + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage uploadAgent(String sliceId, + Integer totalSlice, + String fileSumMd5) throws IOException { + File agentPath = serverConfig.getAgentPath(); + + File userTempPath = serverConfig.getUserTempPath(); + File successFile = this.shardingTryMerge(userTempPath.getAbsolutePath(), sliceId, totalSlice, fileSumMd5); + FileUtil.move(successFile, agentPath, true); + // + String path = FileUtil.file(agentPath, successFile.getName()).getAbsolutePath(); + // 解析压缩包 + File file = JpomManifest.zipFileFind(path, Type.Agent, agentPath.getAbsolutePath()); + path = FileUtil.getAbsolutePath(file); + // 基础检查 + JsonMessage error = JpomManifest.checkJpomJar(path, Type.Agent, false); + if (!error.success()) { + FileUtil.del(path); + return new JsonMessage<>(error.getCode(), error.getMsg()); + } + // 保存文件 + this.saveAgentFile(error.getData()); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + private void saveAgentFile(Tuple data) { + File file = data.get(3); + AgentFileModel agentFileModel = new AgentFileModel(); + agentFileModel.setName(file.getName()); + agentFileModel.setSize(file.length()); + agentFileModel.setSavePath(FileUtil.getAbsolutePath(file)); + // + agentFileModel.setVersion(data.get(0)); + agentFileModel.setTimeStamp(data.get(1)); + systemParametersServer.upsert(AgentFileModel.ID, agentFileModel, AgentFileModel.ID); + // 删除历史包 @author jzy 2021-08-03 + String saveDir = serverConfig.getAgentPath().getAbsolutePath(); + List files = FileUtil.loopFiles(saveDir, pathname -> !FileUtil.equals(pathname, file)); + for (File file1 : files) { + FileUtil.del(file1); + } + } + + @GetMapping(value = "fast_install.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage fastInstall(HttpServletRequest request) { + boolean beta = RemoteVersion.betaRelease(); + String language = I18nMessageUtil.tryGetNormalLanguage(); + InputStream inputStream = ResourceUtil.getStream("classpath:/fast-install/" + language + (beta ? "/beta.json" : "/release.json")); + String json = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); + JSONObject jsonObject = new JSONObject(); + JpomManifest instance = JpomManifest.getInstance(); + jsonObject.put("token", instance.randomIdSign()); + jsonObject.put("key", ServerOpenApi.PUSH_NODE_KEY); + // + JSONArray jsonArray = JSONArray.parseArray(json); + jsonObject.put("shUrls", jsonArray); + // + 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); + } + + @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()); + 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) { + JSONObject receiveCache = NodeInfoController.getReceiveCache(id); + Assert.notNull(receiveCache, I18nMessageUtil.get("i18n.no_cache_info.fba1")); + JSONArray jsonArray = receiveCache.getJSONArray("canUseNode"); + Assert.notEmpty(jsonArray, I18nMessageUtil.get("i18n.no_cache_info_with_minus_one.52f2")); + Optional any = jsonArray.stream().map(o -> { + if (o instanceof MachineNodeModel) { + return (MachineNodeModel) o; + } + JSONObject jsonObject = (JSONObject) o; + return jsonObject.toJavaObject(MachineNodeModel.class); + }).filter(nodeModel -> StrUtil.equals(nodeModel.getJpomUrl(), StrUtil.format("{}:{}", ip, port))).findAny(); + Assert.state(any.isPresent(), I18nMessageUtil.get("i18n.incorrect_ip_address.b872")); + MachineNodeModel machineNodeModel = any.get(); + try { + machineNodeServer.testNode(machineNodeModel); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.test_result.8441"), machineNodeModel.getJpomUrl(), e.getMessage()); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.node_connection_failure_message.aacc") + e.getMessage()); + } + String workspaceId = nodeService.getCheckUserWorkspace(request); + + MachineNodeModel existsMachine = machineNodeServer.getByUrl(machineNodeModel.getJpomUrl()); + if (existsMachine == null) { + // 插入 + machineNodeServer.insertAndNode(machineNodeModel, workspaceId); + } else { + boolean exists = nodeService.existsNode2(workspaceId, existsMachine.getId()); + Assert.state(!exists, I18nMessageUtil.get("i18n.i18n_node_already_exists.632d")); + machineNodeServer.insertNode(machineNodeModel, workspaceId); + } + // 更新结果 + receiveCache.put("type", "success"); + return JsonMessage.success(I18nMessageUtil.get("i18n.installation_success.811f"), NodeInfoController.listReceiveCache(null)); + } + + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeWelcomeController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeWelcomeController.java new file mode 100644 index 0000000000..0b1dfd3a46 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeWelcomeController.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +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; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.NodeConfig; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.func.assets.model.MachineNodeStatLogModel; +import org.dromara.jpom.func.assets.server.MachineNodeStatLogServer; +import org.dromara.jpom.model.BaseMachineModel; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.system.ServerConfig; +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.Optional; + +/** + * 节点统计信息 + * + * @author bwcx_jzy + */ +@RestController +@RequestMapping(value = "/node") +public class NodeWelcomeController extends BaseServerController { + + private final MachineNodeStatLogServer machineNodeStatLogServer; + private final NodeConfig nodeConfig; + + public NodeWelcomeController(MachineNodeStatLogServer machineNodeStatLogServer, + ServerConfig serverConfig) { + this.machineNodeStatLogServer = machineNodeStatLogServer; + this.nodeConfig = serverConfig.getNode(); + } + + @PostMapping(value = "node_monitor_data.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> nodeMonitorJson(String machineId) { + NodeModel node = tryGetNode(); + List list = this.getList(node, machineId); + return JsonMessage.success("", list); + } + + private List getList(NodeModel node, String machineId) { + String useMachineId = Optional.ofNullable(node).map(BaseMachineModel::getMachineId).orElse(machineId); + String startDateStr = getParameter("startTime"); + String endDateStr = getParameter("endTime"); + if (StrUtil.hasEmpty(startDateStr, endDateStr)) { + MachineNodeStatLogModel systemMonitorLog = new MachineNodeStatLogModel(); + systemMonitorLog.setMachineId(useMachineId); + return machineNodeStatLogServer.queryList(systemMonitorLog, 500, new Order("monitorTime", Direction.DESC)); + } + // 处理时间 + DateTime startDate = DateUtil.parse(startDateStr); + long startTime = startDate.getTime(); + DateTime endDate = DateUtil.parse(endDateStr); + if (startDate.equals(endDate)) { + // 时间相等 + endDate = DateUtil.endOfDay(endDate); + } + long endTime = endDate.getTime(); + // 开启了节点信息采集 + Page pageObj = new Page(1, 5000); + pageObj.addOrder(new Order("monitorTime", Direction.DESC)); + Entity entity = Entity.create(); + entity.set("machineId", useMachineId); + entity.set(" MONITORTIME", ">= " + startTime); + entity.set("MONITORTIME", "<= " + endTime); + return machineNodeStatLogServer.listPageOnlyResult(entity, pageObj); + } + + @RequestMapping(value = "processList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> getProcessList(HttpServletRequest request, String machineId) { + NodeModel node = tryGetNode(); + if (node != null) { + return NodeForward.request(node, request, NodeUrl.ProcessList); + } + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, NodeUrl.ProcessList); + } + + @RequestMapping(value = "kill.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @SystemPermission + public IJsonMessage kill(HttpServletRequest request, String machineId) { + NodeModel node = tryGetNode(); + if (node != null) { + return NodeForward.request(node, request, NodeUrl.Kill); + } + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, NodeUrl.Kill); + } + + @GetMapping(value = "machine-info", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage machineInfo(String machineId) { + NodeModel nodeModel = tryGetNode(); + String useMachineId = Optional.ofNullable(nodeModel).map(BaseMachineModel::getMachineId).orElse(machineId); + MachineNodeModel model = machineNodeServer.getByKey(useMachineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("data", model); + jsonObject.put("heartSecond", nodeConfig.getHeartSecond()); + return JsonMessage.success("", jsonObject); + } + + @GetMapping(value = "disk-info", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> diskInfo(HttpServletRequest request, String machineId) { + NodeModel node = tryGetNode(); + if (node != null) { + return NodeForward.request(node, request, NodeUrl.DiskInfo); + } + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, NodeUrl.DiskInfo); + } + + @GetMapping(value = "hw-disk-info", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> hwDiskInfo(HttpServletRequest request, String machineId) { + NodeModel node = tryGetNode(); + if (node != null) { + return NodeForward.request(node, request, NodeUrl.HwDiskInfo); + } + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, NodeUrl.HwDiskInfo); + } + + @GetMapping(value = "network-interfaces", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> networkInterfaces(HttpServletRequest request, String machineId) { + NodeModel node = tryGetNode(); + if (node != null) { + return NodeForward.request(node, request, NodeUrl.NetworkInterfaces); + } + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.request(model, request, NodeUrl.NetworkInterfaces); + } +} 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 new file mode 100644 index 0000000000..c9cd2d6504 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/ProjectManageControl.java @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node.manage; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.CharsetDetector; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.text.StrPool; +import cn.hutool.core.text.csv.*; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.BaseNodeModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.RunMode; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.data.MonitorModel; +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.*; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; +import org.dromara.jpom.service.dblog.RepositoryService; +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; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 项目管理 + * + * @author bwcx_jzy + * @since 2018/9/29 + */ +@RestController +@RequestMapping(value = "/node/manage/") +@Feature(cls = ClassFeature.PROJECT) +@NodeDataPermission(cls = ProjectInfoCacheService.class) +@Slf4j +public class ProjectManageControl extends BaseServerController { + + private final OutGivingServer outGivingServer; + private final LogReadServer logReadServer; + private final MonitorService monitorService; + private final BuildInfoService buildService; + private final RepositoryService repositoryService; + private final ProjectInfoCacheService projectInfoCacheService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; + private final ServerConfig serverConfig; + private final WhitelistDirectoryService whitelistDirectoryService; + + public ProjectManageControl(OutGivingServer outGivingServer, + LogReadServer logReadServer, + MonitorService monitorService, + BuildInfoService buildService, + RepositoryService repositoryService, + ProjectInfoCacheService projectInfoCacheService, + DbBuildHistoryLogService dbBuildHistoryLogService, + ServerConfig serverConfig, + WhitelistDirectoryService whitelistDirectoryService) { + this.outGivingServer = outGivingServer; + this.logReadServer = logReadServer; + this.monitorService = monitorService; + this.buildService = buildService; + this.repositoryService = repositoryService; + 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); + if (!exists) { + // 判断如果项目 id 不存在则表示新增 + ProjectInfoCacheModel projectInfoCacheModel = new ProjectInfoCacheModel(); + projectInfoCacheModel.setProjectId(id); + projectInfoCacheModel.setNodeId(node.getId()); + boolean exists1 = projectInfoCacheService.exists(projectInfoCacheModel); + if (!exists1) { + // 新增数据 + return; + } + } + Assert.state(exists, I18nMessageUtil.get("i18n.no_corresponding_data_or_permission.1291")); + } + + @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("", 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; + } + + /** + * 获取正在运行的项目的端口和进程id + * + * @return json + */ + @RequestMapping(value = "getProjectPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getProjectPort(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_GetProjectPort); + } + + + /** + * 查询所有项目 + * + * @return json + */ + @PostMapping(value = "get_project_info", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getProjectInfo(HttpServletRequest request) { + PageResultDto modelPageResultDto = projectInfoCacheService.listPage(request); + return JsonMessage.success("", modelPageResultDto); + } + + /** + * 删除项目 + * + * @param id id + * @return json + */ + @PostMapping(value = "deleteProject", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage deleteProject(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id, + + HttpServletRequest request) { + NodeModel nodeModel = getNode(); + this.checkProjectPermission(id, request, nodeModel); + // 检查节点分发 + outGivingServer.checkNodeProject(nodeModel.getId(), id, request, I18nMessageUtil.get("i18n.project_has_node_distribution_cannot_delete.41b0")); + // 检查日志阅读 + logReadServer.checkNodeProject(nodeModel.getId(), id, request, I18nMessageUtil.get("i18n.project_has_logs_cannot_delete.1d2a")); + // 项目监控 + List monitorModels = monitorService.listByWorkspace(request); + if (monitorModels != null) { + boolean match = monitorModels.stream().anyMatch(monitorModel -> monitorModel.checkNodeProject(nodeModel.getId(), id)); + Assert.state(!match, I18nMessageUtil.get("i18n.project_has_monitoring_items_cannot_delete.c9a3")); + } + // 构建 + boolean releaseMethod = buildService.checkReleaseMethod(nodeModel.getId() + StrUtil.COLON + id, request, BuildReleaseMethod.Project); + Assert.state(!releaseMethod, I18nMessageUtil.get("i18n.project_has_build_items_cannot_delete.c2df")); + + JsonMessage jsonMessage = NodeForward.request(nodeModel, request, NodeUrl.Manage_DeleteProject); + if (jsonMessage.success()) { + // + projectInfoCacheService.syncExecuteNode(nodeModel); + } + return jsonMessage; + } + + /** + * 查看项目关联在线构建的数据 + * + * @param projectData 项目数据 + * @param request 请求 + * @param toWorkspaceId 工作空间ID + * @return list + */ + private List checkBuild(ProjectInfoCacheModel projectData, String toWorkspaceId, HttpServletRequest request) { + // 构建 + String dataId = projectData.getNodeId() + StrUtil.COLON + projectData.getProjectId(); + List buildInfoModels = buildService.listReleaseMethod(dataId, request, BuildReleaseMethod.Project); + if (buildInfoModels != null) { + return buildInfoModels.stream() + .map(buildInfoModel -> { + // 判断共享仓库 + RepositoryModel repositoryModel = repositoryService.getByKey(buildInfoModel.getRepositoryId()); + Assert.notNull(repositoryModel, I18nMessageUtil.get("i18n.repository_does_not_exist.3cdb")); + if (StrUtil.equals(repositoryModel.getWorkspaceId(), toWorkspaceId)) { + // 迁移前后是同一个工作空间 + return null; + } + // 非全局仓库判断仓库关联的构建 + if (!repositoryModel.global()) { + BuildInfoModel buildInfoModel1 = new BuildInfoModel(); + buildInfoModel1.setRepositoryId(buildInfoModel.getRepositoryId()); + buildInfoModel1.setWorkspaceId(projectData.getWorkspaceId()); + List infoModels = buildService.listByBean(buildInfoModel1); + if (CollUtil.size(infoModels) > 1) { + // 判断如果使用通过一个仓库 + long count = infoModels.stream() + .filter(buildInfoModel2 -> { + // 发布方式和数据id 不一样 + return !StrUtil.equals(buildInfoModel2.getReleaseMethodDataId(), dataId); + }) + .count(); + Assert.state(count <= 0, StrUtil.format(I18nMessageUtil.get("i18n.current_project_associated_with_online_build_and_repository.96c5"), repositoryModel.getName(), count)); + } + } + return new Tuple(buildInfoModel, repositoryModel); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + return new ArrayList<>(); + } + + /** + * 迁移项目关联在线构建的数据 + * + * @param list 构建数据 + * @param toWorkspaceId 迁移到哪个工作空间 + * @return list + */ + private String migrateBuild(List list, String toWorkspaceId, String toNodeId, ProjectInfoCacheModel projectData) { + return list.stream() + .map(tuple -> { + BuildInfoModel infoModel = tuple.get(0); + RepositoryModel repository = tuple.get(1); + if (!repository.global()) { + // 非全局仓库才 修改仓库所属工作空间 + String repositoryId = infoModel.getRepositoryId(); + RepositoryModel repositoryModel = new RepositoryModel(); + repositoryModel.setId(repositoryId); + repositoryModel.setWorkspaceId(toWorkspaceId); + repositoryService.updateById(repositoryModel); + } + // + BuildInfoModel buildInfoModel = new BuildInfoModel(); + buildInfoModel.setId(infoModel.getId()); + buildInfoModel.setWorkspaceId(toWorkspaceId); + // 修改发布的关联数据 + buildInfoModel.setReleaseMethodDataId(toNodeId + StrUtil.COLON + projectData.getProjectId()); + buildService.updateById(buildInfoModel); + // 修改构建记录 + dbBuildHistoryLogService.update( + Entity.create().set("workspaceId", toWorkspaceId), + Entity.create().set("buildDataId", infoModel.getId()) + ); + if (!repository.global()) { + return StrUtil.format(I18nMessageUtil.get("i18n.auto_migrate_associated_build_and_repo.0b3f"), infoModel.getName(), repository.getName()); + } + return StrUtil.format(I18nMessageUtil.get("i18n.auto_migrate_associated_build.a060"), infoModel.getName()); + }). + collect(Collectors.joining(" | ")); + } + + /** + * 迁移工作空间 + * + * @param id id + * @return json + */ + @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, + HttpServletRequest request) { + ProjectInfoCacheModel projectData = projectInfoCacheService.getByKey(id, request); + Assert.notNull(projectData, I18nMessageUtil.get("i18n.project_does_not_exist.3029")); + + Assert.state(!StrUtil.equals(toWorkspaceId, projectData.getWorkspaceId()) || !StrUtil.equals(projectData.getNodeId(), toNodeId), I18nMessageUtil.get("i18n.target_workspace_consistency.e04c")); + projectInfoCacheService.checkUserWorkspace(toWorkspaceId); + // + NodeModel nowNode = nodeService.getByKey(projectData.getNodeId()); + Assert.notNull(nowNode, I18nMessageUtil.get("i18n.corresponding_node_does_not_exist.72cb")); + NodeModel toNodeModel = nodeService.getByKey(toNodeId); + Assert.notNull(toNodeModel, I18nMessageUtil.get("i18n.node_not_exist.760e")); + Assert.state(StrUtil.equals(toWorkspaceId, toNodeModel.getWorkspaceId()), I18nMessageUtil.get("i18n.migration_target_workspace_node_mismatch.d9cf")); + // 检查节点分发 + outGivingServer.checkNodeProject(projectData.getNodeId(), projectData.getProjectId(), request, I18nMessageUtil.get("i18n.project_has_node_distribution_cannot_migrate.cc0e")); + // 检查日志阅读 + logReadServer.checkNodeProject(projectData.getNodeId(), projectData.getProjectId(), request, I18nMessageUtil.get("i18n.project_has_logs_cannot_migrate.2e0e")); + // 项目监控 + List monitorModels = monitorService.listByWorkspace(request); + if (monitorModels != null) { + boolean match = monitorModels.stream().anyMatch(monitorModel -> monitorModel.checkNodeProject(projectData.getNodeId(), id)); + Assert.state(!match, I18nMessageUtil.get("i18n.project_has_monitoring_items_cannot_migrate.c7f6")); + } + // 检查构建 + List buildInfoModels = this.checkBuild(projectData, toWorkspaceId, request); + JsonMessage result; + if (StrUtil.equals(nowNode.getMachineId(), toNodeModel.getMachineId())) { + // 相同机器 + JSONObject jsonObject = new JSONObject(); + jsonObject.put("newWorkspaceId", toWorkspaceId); + jsonObject.put("newNodeId", toNodeId); + jsonObject.put("id", projectData.getProjectId()); + JsonMessage jsonMessage = NodeForward.request(nowNode, NodeUrl.Manage_ChangeWorkspaceId, jsonObject); + if (!jsonMessage.success()) { + return new JsonMessage<>(406, nowNode.getName() + I18nMessageUtil.get("i18n.node_migration_project_failure.d5ff") + jsonMessage.getMsg()); + } + result = jsonMessage; + } else { + JSONObject item = projectInfoCacheService.getItem(nowNode, projectData.getProjectId()); + Assert.notNull(item, I18nMessageUtil.get("i18n.project_data_lost.2ae3")); + item = projectInfoCacheService.convertToRequestData(item); + item.put("nodeId", toNodeId); + item.put("workspaceId", toWorkspaceId); + item.put("previewData", true); + // 发起预检查数据 + JsonMessage jsonMessage = NodeForward.request(toNodeModel, NodeUrl.Manage_SaveProject, item); + if (!jsonMessage.success()) { + return new JsonMessage<>(406, toNodeModel.getName() + I18nMessageUtil.get("i18n.node_and_check_project_failed.ac4b") + jsonMessage.getMsg()); + } + item.remove("previewData"); + jsonMessage = NodeForward.request(toNodeModel, NodeUrl.Manage_SaveProject, item); + if (!jsonMessage.success()) { + return new JsonMessage<>(406, toNodeModel.getName() + I18nMessageUtil.get("i18n.node_sync_project_failed.a2a7") + jsonMessage.getMsg()); + } + // 删除之前节点项目 + JSONObject delData = new JSONObject(); + delData.put("id", projectData.getProjectId()); + // 非强制 + delData.put("thorough", ""); + JsonMessage delJsonMeg = NodeForward.request(nowNode, NodeUrl.Manage_DeleteProject, delData); + if (!delJsonMeg.success()) { + return new JsonMessage<>(406, nowNode.getName() + I18nMessageUtil.get("i18n.node_delete_project_failed.534c") + delJsonMeg.getMsg()); + } + result = jsonMessage; + } + // 迁移构建 + String buildMsg = this.migrateBuild(buildInfoModels, toWorkspaceId, toNodeId, projectData); + // 刷新缓存 + projectInfoCacheService.syncExecuteNode(nowNode); + projectInfoCacheService.syncExecuteNode(toNodeModel); + return new JsonMessage<>(200, StrUtil.format(I18nMessageUtil.get("i18n.migration_success_message.e546"), result.getMsg(), buildMsg)); + } + + /** + * 操作项目 + *

+ * nodeId,id + * + * @return json + */ + @RequestMapping(value = "operate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage operate(HttpServletRequest request) { + NodeModel nodeModel = getNode(); + return NodeForward.request(nodeModel, request, NodeUrl.Manage_Operate); + } + + + /** + * 下载导入模板 + */ + @GetMapping(value = "import-template", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public void importTemplate(HttpServletResponse response) throws IOException { + String fileName = I18nMessageUtil.get("i18n.import_project_template_csv.c6f1"); + this.setApplicationHeader(response, fileName); + // + CsvWriter writer = CsvUtil.getWriter(response.getWriter()); + writer.writeLine("id", "name", "groupName", "whitelistDirectory", "path", "logPath", "runMode", + "mainClass", + "jvm", "args", + "javaExtDirsCp", + "dslContent", + "webHooks", + "autoStart"); + writer.flush(); + } + + /** + * 导出数据 + */ + @GetMapping(value = "export-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DOWNLOAD) + public void exportData(HttpServletResponse response, HttpServletRequest request) throws IOException { + String workspace = projectInfoCacheService.getCheckUserWorkspace(request); + String prefix = I18nMessageUtil.get("i18n.exported_project_data.fd1f"); + String fileName = prefix + DateTime.now().toString(DatePattern.NORM_DATE_FORMAT) + ".csv"; + this.setApplicationHeader(response, fileName); + // + CsvWriteConfig csvWriteConfig = CsvWriteConfig.defaultConfig(); + csvWriteConfig.setAlwaysDelimitText(true); + CsvWriter writer = CsvUtil.getWriter(response.getWriter(), csvWriteConfig); + int pageInt = 0; + writer.writeLine("id", "name", "groupName", "whitelistDirectory", "path", "logPath", "runMode", + "mainClass", + "jvm", "args", "javaExtDirsCp", + "dslContent", + "webHooks", + "autoStart", "outGivingProject"); + while (true) { + Map paramMap = ServletUtil.getParamMap(request); + // 下一页 + paramMap.put("page", String.valueOf(++pageInt)); + PageResultDto listPage = projectInfoCacheService.listPage(paramMap, false); + if (listPage.isEmpty()) { + break; + } + listPage.getResult() + .stream() + .map((Function>) projectInfoCacheModel -> CollUtil.newArrayList( + projectInfoCacheModel.getProjectId(), + projectInfoCacheModel.getName(), + projectInfoCacheModel.getGroup(), + projectInfoCacheModel.getWhitelistDirectory(), + projectInfoCacheModel.getLib(), + projectInfoCacheModel.getLogPath(), + projectInfoCacheModel.getRunMode(), + projectInfoCacheModel.getMainClass(), + encodeCsv(projectInfoCacheModel.getJvm()), + encodeCsv(projectInfoCacheModel.getArgs()), + encodeCsv(projectInfoCacheModel.getJavaExtDirsCp()), + encodeCsv(projectInfoCacheModel.getDslContent()), + projectInfoCacheModel.getToken(), + projectInfoCacheModel.getAutoStart(), + projectInfoCacheModel.getOutGivingProject() + )) + .map(objects -> objects.stream().map(StrUtil::toStringOrNull).toArray(String[]::new)) + .forEach(writer::writeLine); + if (ObjectUtil.equal(listPage.getPage(), listPage.getTotalPage())) { + // 最后一页 + break; + } + } + writer.flush(); + } + + + private String encodeCsv(String data) { + return StrUtil.replace(data, StrPool.LF, "\"\n\""); + } + + private String decodeCsv(String data) { + return StrUtil.replace(data, "\"\n\"", StrPool.LF); + } + + /** + * 导入数据 + * + * @return json + */ + @PostMapping(value = "import-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage importData(MultipartFile file, HttpServletRequest request) throws IOException { + Assert.notNull(file, I18nMessageUtil.get("i18n.no_uploaded_file.07ef")); + String workspaceId = projectInfoCacheService.getCheckUserWorkspace(request); + NodeModel node = getNode(); + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + boolean csv = StrUtil.endWithIgnoreCase(extName, "csv"); + Assert.state(csv, I18nMessageUtil.get("i18n.disallowed_file_format.d6e4")); + assert originalFilename != null; + File csvFile = FileUtil.file(serverConfig.getUserTempPath(), originalFilename); + int updateCount = 0, ignoreCount = 0; + Charset fileCharset; + try { + file.transferTo(csvFile); + // + fileCharset = CharsetDetector.detect(csvFile); + Reader bomReader = FileUtil.getReader(csvFile, fileCharset); + CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.setHeaderLineNo(0); + CsvReader reader = CsvUtil.getReader(bomReader, csvReadConfig); + CsvData csvData; + try { + csvData = reader.read(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_project_csv_exception.ece1"), e); + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.parse_file_exception.374d") + e.getMessage()); + } finally { + IoUtil.close(reader); + } + List rows = csvData.getRows(); + Assert.notEmpty(rows, I18nMessageUtil.get("i18n.no_data.55a2")); + + for (int i = 0; i < rows.size(); i++) { + CsvRow csvRow = rows.get(i); + JSONObject jsonObject = this.loadProjectData(csvRow, workspaceId, node); + if (jsonObject == null) { + ignoreCount++; + continue; + } + try { + // + JsonMessage jsonMessage = NodeForward.request(node, NodeUrl.Manage_SaveProject, jsonObject); + if (jsonMessage.success()) { + updateCount++; + continue; + } + throw new IllegalArgumentException(StrUtil.format(I18nMessageUtil.get("i18n.import_save_failure.001a"), i + 2, jsonMessage.getMsg())); + } catch (IllegalArgumentException | IllegalStateException e) { + throw Lombok.sneakyThrow(e); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.import_save_project_exception.cdbe"), e); + throw new IllegalArgumentException(StrUtil.format(I18nMessageUtil.get("i18n.import_exception.04b6"), i + 2, e.getMessage())); + } + } + projectInfoCacheService.syncExecuteNode(node); + } finally { + FileUtil.del(csvFile); + } + String fileCharsetStr = Optional.ofNullable(fileCharset).map(Charset::name).orElse(StrUtil.EMPTY); + return JsonMessage.success(I18nMessageUtil.get("i18n.import_success_message.2df3"), fileCharsetStr, updateCount, ignoreCount); + } + + private JSONObject loadProjectData(CsvRow csvRow, String workspaceId, NodeModel node) { + String id = csvRow.getByName("id"); + String fullId = BaseNodeModel.fullId(workspaceId, node.getId(), id); + + ProjectInfoCacheModel projectInfoCacheModel1 = projectInfoCacheService.getByKey(fullId); + if (projectInfoCacheModel1 != null) { + // 节点分发项目不能在这里导入 + Boolean outGivingProject = projectInfoCacheModel1.getOutGivingProject(); + if (outGivingProject != null && outGivingProject) { + return null; + } + if (StrUtil.isNotEmpty(projectInfoCacheModel1.getJavaCopyItemList())) { + return null; + } + } +// "id", "name", "groupName", "whitelistDirectory", "path", "logPath", "runMode", +// "mainClass", +// "jvm", "args", "javaExtDirsCp", +// "dslContent", +// "webHooks", +// "autoStart", "outGivingProject" + JSONObject data = new JSONObject(); + data.put("id", id); + data.put("name", csvRow.getByName("name")); + data.put("group", csvRow.getByName("groupName")); + String runModeStr = csvRow.getByName("runMode"); + // 运行模式 + RunMode runMode1 = EnumUtil.fromString(RunMode.class, runModeStr, RunMode.ClassPath); + data.put("runMode", runMode1.name()); + if (runMode1 == RunMode.ClassPath || runMode1 == RunMode.JavaExtDirsCp) { + data.put("mainClass", csvRow.getByName("mainClass")); + } + if (runMode1 == RunMode.JavaExtDirsCp) { + data.put("javaExtDirsCp", decodeCsv(csvRow.getByName("javaExtDirsCp"))); + } + if (runMode1 == RunMode.Dsl) { + data.put("dslContent", decodeCsv(csvRow.getByName("dslContent"))); + } + data.put("whitelistDirectory", csvRow.getByName("whitelistDirectory")); + data.put("logPath", csvRow.getByName("logPath")); + data.put("lib", csvRow.getByName("path")); + data.put("autoStart", csvRow.getByName("autoStart")); + data.put("token", csvRow.getByName("webHooks")); + data.put("jvm", decodeCsv(csvRow.getByName("jvm"))); + data.put("args", decodeCsv(csvRow.getByName("args"))); + return data; + } +} 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 new file mode 100644 index 0000000000..84942c2067 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileBackupController.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.NodeDataPermission; +import org.dromara.jpom.service.node.ProjectInfoCacheService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +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 javax.servlet.http.HttpServletResponse; + +/** + * @author bwcx_jzy + * @since 2022/5/11 + */ +@RestController +@RequestMapping(value = "/node/manage/file/") +@Feature(cls = ClassFeature.PROJECT_FILE) +@NodeDataPermission(cls = ProjectInfoCacheService.class) +public class ProjectFileBackupController extends BaseServerController { + + /** + * 查询备份列表 + * + * @param id 项目ID + * @return list + */ + @RequestMapping(value = "list-backup", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage listBackup(String id, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_LIST_BACKUP); + } + + /** + * 获取指定备份的文件列表 + * + * @param id 项目 + * @param path 读取的二级目录 + * @param backupId 备份id + * @return list + */ + @RequestMapping(value = "backup-item-files", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage backupItemFiles(String id, String path, @ValidatorItem String backupId, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_LIST_ITEM_FILES); + } + + /** + * 将执行文件下载到客户端 本地 + * + * @param id 项目id + * @param filename 文件名 + * @param levelName 文件夹名 + * @param backupId 备份id + */ + @GetMapping(value = "backup-download", produces = MediaType.APPLICATION_JSON_VALUE) + public void download(String id, @ValidatorItem String backupId, @ValidatorItem String filename, String levelName, + HttpServletResponse response, + HttpServletRequest request) { + NodeForward.requestDownload(getNode(), request, response, NodeUrl.MANAGE_FILE_BACKUP_DOWNLOAD); + } + + /** + * 删除文件 + * + * @param id 项目ID + * @param backupId 备份ID + * @param filename 文件名 + * @param levelName 层级目录 + * @return msg + */ + @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, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_DELETE); + } + + /** + * 还原项目文件 + * + * @param id 项目ID + * @param backupId 备份ID + * @param type 类型 clear 清空还原 + * @param filename 文件名 + * @param levelName 目录 + * @return msg + */ + @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, 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 new file mode 100644 index 0000000000..dad31424b0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileControl.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node.manage.file; + +import cn.keepbx.jpom.IJsonMessage; +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.i18n.I18nMessageUtil; +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; +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.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 文件管理 + * + * @author bwcx_jzy + */ +@RestController +@RequestMapping(value = "/node/manage/file/") +@Feature(cls = ClassFeature.PROJECT_FILE) +@NodeDataPermission(cls = ProjectInfoCacheService.class) +public class ProjectFileControl extends BaseServerController { + private final OutGivingWhitelistService outGivingWhitelistService; + + public ProjectFileControl(OutGivingWhitelistService outGivingWhitelistService) { + this.outGivingWhitelistService = outGivingWhitelistService; + } + + + /** + * 列出目录下的文件 + * + * @return json + */ + @RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.LIST) + public IJsonMessage getFileList(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_GetFileList); + } + + /** + * 上传文件 + * + * @return json + */ + @RequestMapping(value = "upload-sharding", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.UPLOAD, log = false) + public IJsonMessage uploadSharding(String sliceId) { + Assert.state(BaseServerController.SHARDING_IDS.containsKey(sliceId), I18nMessageUtil.get("i18n.invalid_shard_id.46fd")); + return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.Manage_File_Upload_Sharding); + } + + /** + * 合并分片 + * + * @return json + */ + @RequestMapping(value = "sharding-merge", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.UPLOAD) + public IJsonMessage shardingMerge(String sliceId, HttpServletRequest request) { + Assert.state(BaseServerController.SHARDING_IDS.containsKey(sliceId), I18nMessageUtil.get("i18n.invalid_shard_id.46fd")); + JsonMessage message = NodeForward.request(getNode(), request, NodeUrl.Manage_File_Sharding_Merge); + // 判断-删除分片id + BaseServerController.SHARDING_IDS.remove(sliceId); + return message; + } + + /** + * 下载文件 + */ + @RequestMapping(value = "download", method = RequestMethod.GET) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.DOWNLOAD) + public void download(HttpServletRequest request, HttpServletResponse response) { + NodeForward.requestDownload(getNode(), request, response, NodeUrl.Manage_File_Download); + } + + /** + * 删除文件 + * + * @return json + */ + @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.DEL) + public IJsonMessage deleteFile(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_DeleteFile); + } + + + /** + * 更新配置文件 + * + * @return json + */ + @PostMapping(value = "update_config_file", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) + public IJsonMessage updateConfigFile(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_UpdateConfigFile); + } + + /** + * 删除文件 + * + * @return json + */ + @GetMapping(value = "read_file", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.LIST) + public IJsonMessage readFile(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_ReadFile); + } + + /** + * 下载远程文件 + * + * @return json + */ + @GetMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.REMOTE_DOWNLOAD) + 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); + } + + /** + * 创建文件 + * + * @return json + */ + @GetMapping(value = "new_file_folder", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) + public IJsonMessage newFileFolder(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_NEW_FILE_FOLDER); + } + + + /** + * 修改文件名 + * + * @return json + */ + @GetMapping(value = "rename_file_folder", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) + public IJsonMessage renameFileFolder(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_RENAME_FILE_FOLDER); + } + + /** + * 复制文件 + * + * @return json + */ + @PostMapping(value = "copy", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) + public IJsonMessage copy(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_COPY); + } + + /** + * 压缩文件 + * + * @return json + */ + @PostMapping(value = "compress", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) + public IJsonMessage compress(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_COMPRESS); + } + +} 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 new file mode 100644 index 0000000000..247db93d3d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/log/LogBackController.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node.manage.log; + +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.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.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 控制台日志备份管理 + * + * @author bwcx_jzy + * @since 2019/3/7 + */ +@Controller +@RequestMapping(value = "node/manage/log") +@Feature(cls = ClassFeature.PROJECT_LOG) +@NodeDataPermission(cls = ProjectInfoCacheService.class) +public class LogBackController extends BaseServerController { + + private final ProjectInfoCacheService projectInfoCacheService; + + public LogBackController(ProjectInfoCacheService projectInfoCacheService) { + this.projectInfoCacheService = projectInfoCacheService; + } + + @RequestMapping(value = "export", method = RequestMethod.GET) + @ResponseBody + @Feature(method = MethodFeature.DOWNLOAD) + public void export(HttpServletRequest request, HttpServletResponse response) { + NodeForward.requestDownload(getNode(), request, response, NodeUrl.Manage_Log_export); + } + + /** + * get log back list + * 日志备份列表接口 + * + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "log-back-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @Feature(method = MethodFeature.LIST) + public IJsonMessage logBackList(HttpServletRequest request) { + JSONObject jsonObject = NodeForward.requestData(getNode(), NodeUrl.Manage_Log_logBack, request, JSONObject.class); + return JsonMessage.success("", jsonObject); + } + + @RequestMapping(value = "logBack_download", method = RequestMethod.GET) + @ResponseBody + @Feature(method = MethodFeature.DOWNLOAD) + public void download(HttpServletResponse response, HttpServletRequest request) { + NodeForward.requestDownload(getNode(), request, response, NodeUrl.Manage_Log_logBack_download); + } + + @RequestMapping(value = "logBack_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @Feature(method = MethodFeature.DEL) + 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) + @ResponseBody + public IJsonMessage logSize(String id) { + + return NodeForward.request(getNode(), NodeUrl.Manage_Log_LogSize, "id", id); + } + + /** + * 重置日志 + * + * @return json + */ + @RequestMapping(value = "resetLog", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + @Feature(method = MethodFeature.DEL) + 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 new file mode 100644 index 0000000000..99a06d8250 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptController.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node.script; + +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.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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; +import java.util.HashMap; +import java.util.Map; + +/** + * 脚本管理 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@RestController +@RequestMapping(value = "/node/script") +@Feature(cls = ClassFeature.NODE_SCRIPT) +@NodeDataPermission(cls = NodeScriptServer.class) +public class NodeScriptController extends BaseServerController { + + private final NodeScriptServer nodeScriptServer; + private final NodeScriptExecuteLogServer nodeScriptExecuteLogServer; + private final TriggerTokenLogServer triggerTokenLogServer; + + public NodeScriptController(NodeScriptServer nodeScriptServer, + NodeScriptExecuteLogServer nodeScriptExecuteLogServer, + TriggerTokenLogServer triggerTokenLogServer) { + this.nodeScriptServer = nodeScriptServer; + this.nodeScriptExecuteLogServer = nodeScriptExecuteLogServer; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * load node script list + * 加载节点脚本列表 + * + * @return json + * @author Hotstrip + */ + @PostMapping(value = "list_all", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> listAll(HttpServletRequest request) { + PageResultDto modelPageResultDto = nodeScriptServer.listPage(request); + return JsonMessage.success("", modelPageResultDto); + } + + + 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, I18nMessageUtil.get("i18n.no_corresponding_data_or_permission.1291")); + } + + @GetMapping(value = "item.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage item(HttpServletRequest request, String id) { + NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); + return NodeForward.request(node, request, NodeUrl.Script_Item); + } + + /** + * 保存脚本 + * + * @return json + */ + @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + 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()) { + nodeScriptServer.syncNode(node); + } + return jsonMessage; + } + + @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @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); + // 删除日志 + nodeScriptExecuteLogServer.delCache(id, node.getId(), request); + } + return requestData; + } + + /** + * 同步脚本模版 + * + * @return json + */ + @GetMapping(value = "sync", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage syncProject(HttpServletRequest request) { + // + NodeModel node = getNode(); + int cache = nodeScriptServer.delCache(node.getId(), request); + String msg = nodeScriptServer.syncExecuteNode(node); + return JsonMessage.success(I18nMessageUtil.get("i18n.active_clearance.5870") + cache + StrUtil.SPACE + msg); + } + + /** + * 释放脚本关联的节点 + * + * @param id 脚本ID + * @return json + */ + @RequestMapping(value = "unbind.json", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission + public IJsonMessage unbind(@ValidatorItem String id, HttpServletRequest request) { + nodeScriptServer.delByKey(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.unbind_success.1c43")); + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @RequestMapping(value = "trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage> getTriggerUrl(String id, String rest, HttpServletRequest request) { + NodeScriptCacheModel item = nodeScriptServer.getByKeyAndGlobal(id, request); + UserModel user = getUser(); + NodeScriptCacheModel updateInfo; + if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { + updateInfo = new NodeScriptCacheModel(); + updateInfo.setId(id); + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), nodeScriptServer.typeName(), + item.getId(), user.getId())); + nodeScriptServer.updateById(updateInfo); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + 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()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + Map map = new HashMap<>(10); + map.put("triggerUrl", FileUtil.normalize(triggerBuildUrl)); + String batchTriggerBuildUrl = String.format("/%s/%s", contextPath, ServerOpenApi.NODE_SCRIPT_TRIGGER_BATCH); + map.put("batchTriggerUrl", FileUtil.normalize(batchTriggerBuildUrl)); + + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } +} 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 new file mode 100644 index 0000000000..9d684471ae --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptLogController.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node.script; + + +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.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.NodeScriptExecuteLogCacheModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.node.script.NodeScriptExecuteLogServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +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; + +/** + * @author bwcx_jzy + * @since 2021/12/24 + */ +@RestController +@RequestMapping(value = "/node/script_log") +@Feature(cls = ClassFeature.NODE_SCRIPT_LOG) +public class NodeScriptLogController extends BaseServerController { + + private final NodeScriptExecuteLogServer nodeScriptExecuteLogServer; + + public NodeScriptLogController(NodeScriptExecuteLogServer nodeScriptExecuteLogServer) { + this.nodeScriptExecuteLogServer = nodeScriptExecuteLogServer; + } + + /** + * get script log list + * + * @return json + */ + @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> scriptList(HttpServletRequest request) { + PageResultDto pageResultDto = nodeScriptExecuteLogServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * 查日志 + * + * @return json + */ + @RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage log(HttpServletRequest request) { + NodeModel node = getNode(); + return NodeForward.request(node, request, NodeUrl.SCRIPT_LOG); + } + + /** + * 删除日志 + * + * @param id 模版ID + * @param executeId 日志ID + * @return json + */ + @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, String executeId, HttpServletRequest request) { + NodeModel node = getNode(); + NodeScriptExecuteLogCacheModel executeLogModel = nodeScriptExecuteLogServer.getByKey(executeId, request); + Assert.notNull(executeLogModel, I18nMessageUtil.get("i18n.no_corresponding_execution_log.9545")); + Assert.state(StrUtil.equals(id, executeLogModel.getScriptId()), I18nMessageUtil.get("i18n.data_associated_id_inconsistent.59f7")); +// NodeScriptExecuteLogCacheModel nodeScriptExecuteLogCacheModel = new NodeScriptExecuteLogCacheModel(); +// nodeScriptExecuteLogCacheModel.setId(executeId); +// nodeScriptExecuteLogCacheModel.setScriptId(id); +// nodeScriptExecuteLogCacheModel.setNodeId(node.getId()); +// NodeScriptExecuteLogCacheModel executeLogModel = nodeScriptExecuteLogServer.queryByBean(nodeScriptExecuteLogCacheModel); + + JsonMessage jsonMessage = NodeForward.request(node, request, NodeUrl.SCRIPT_DEL_LOG); + if (jsonMessage.success()) { + nodeScriptExecuteLogServer.delByKey(executeId); + } + return jsonMessage; + } +} 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 new file mode 100644 index 0000000000..9e6d8b1868 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/system/WhitelistDirectoryController.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.node.system; + +import cn.hutool.core.util.ReflectUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +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; +import org.springframework.util.Assert; +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.lang.reflect.Field; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * 授权目录 + * + * @author bwcx_jzy + * @since 2019/2/28 + */ +@RestController +@RequestMapping(value = "/node/system") +@Feature(cls = ClassFeature.NODE_CONFIG_WHITELIST) +public class WhitelistDirectoryController extends BaseServerController { + + private final WhitelistDirectoryService whitelistDirectoryService; + + public WhitelistDirectoryController(WhitelistDirectoryService whitelistDirectoryService) { + this.whitelistDirectoryService = whitelistDirectoryService; + } + + + /** + * get whiteList data + * 授权数据接口 + * + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> whiteList(String machineId) { + NodeModel nodeModel = tryGetNode(); + AgentWhitelist agentWhitelist; + if (nodeModel != null) { + agentWhitelist = whitelistDirectoryService.getData(nodeModel); + } else { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(machineId); + agentWhitelist = whitelistDirectoryService.getData(machineNodeModel); + } + Map map = new HashMap<>(8); + if (agentWhitelist != null) { + /** + * put key and value into map + * 赋值给 map 对象返回 + */ + Field[] fields = ReflectUtil.getFields(AgentWhitelist.class, field -> Collection.class.isAssignableFrom(field.getType()) || String.class.isAssignableFrom(field.getType())); + for (Field field : fields) { + Object fieldValue = ReflectUtil.getFieldValue(agentWhitelist, field); + if (fieldValue instanceof Collection) { + Collection collection = (Collection) fieldValue; + map.put(field.getName(), AgentWhitelist.convertToLine(collection)); + } else if (fieldValue instanceof String) { + map.put(field.getName(), (String) fieldValue); + } + } + } + return JsonMessage.success("", map); + } + + + /** + * 保存接口 + * + * @return json + */ + @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.tryRequestNode(machineId, request, NodeUrl.WhitelistDirectory_Submit); + Assert.notNull(objectJsonMessage, I18nMessageUtil.get("i18n.select_node.f8a6")); + 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 new file mode 100644 index 0000000000..cb10a19693 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/LogReadController.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.outgiving.LogReadModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.outgiving.LogReadServer; +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.stream.Collectors; + +/** + * 日志阅读 + * + * @author bwcx_jzy + * @since 2022/5/15 + */ +@RestController +@RequestMapping(value = "/log-read") +@Feature(cls = ClassFeature.LOG_READ) +public class LogReadController extends BaseServerController { + + private final LogReadServer logReadServer; + + public LogReadController(LogReadServer logReadServer) { + this.logReadServer = logReadServer; + } + + /** + * 日志阅读列表 + * + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto pageResultDto = logReadServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * 删除日志阅读信息 + * + * @param id 分发id + * @return json + */ + @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, HttpServletRequest request) { + int byKey = logReadServer.delByKey(id, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 编辑日志阅读信息 + *

+ * {"projectList":[{"nodeId":"localhost","projectId":"test-jar"}],"name":"11"} + * + * @param jsonObject 参数 + * @return msg + */ + @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(@RequestBody JSONObject jsonObject, HttpServletRequest request) { + Assert.notNull(jsonObject, I18nMessageUtil.get("i18n.please_pass_parameter.3182")); + String id = jsonObject.getString("id"); + String name = jsonObject.getString("name"); + Assert.hasText(name, I18nMessageUtil.get("i18n.name_required.856d")); + JSONArray projectListArray = jsonObject.getJSONArray("projectList"); + Assert.notEmpty(projectListArray, I18nMessageUtil.get("i18n.select_node_and_project.6021")); + List projectList = projectListArray.toJavaList(LogReadModel.Item.class); + projectList = projectList.stream() + .filter(item -> StrUtil.isAllNotEmpty(item.getNodeId(), item.getProjectId())) + .collect(Collectors.toList()); + Assert.notEmpty(projectList, I18nMessageUtil.get("i18n.select_node_and_project.6021")); + LogReadModel logReadModel = new LogReadModel(); + logReadModel.setId(id); + logReadModel.setName(name); + logReadModel.setNodeProject(JSONArray.toJSONString(projectList)); + // + if (StrUtil.isEmpty(id)) { + logReadServer.insert(logReadModel); + } else { + logReadServer.updateById(logReadModel, request); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 更新缓存 + *

+ * {"op":"showlog","projectId":"python", + * "search":true,"useProjectId":"python", + * "useNodeId":"localhost", + * "beforeCount":0,"afterCount":10, + * "head":0,"tail":100,"first":"false", + * "logFile":"/run.log"} + * + * @param jsonObject 参数 + * @return msg + */ + @RequestMapping(value = "update-cache.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage updateCache(@RequestBody JSONObject jsonObject, HttpServletRequest request) { + Assert.notNull(jsonObject, I18nMessageUtil.get("i18n.please_pass_parameter.3182")); + String id = jsonObject.getString("id"); + Assert.hasText(id, I18nMessageUtil.get("i18n.please_pass_parameter.3182")); + LogReadModel.CacheDta cacheDta = jsonObject.toJavaObject(LogReadModel.CacheDta.class); + + LogReadModel logReadModel = new LogReadModel(); + logReadModel.setId(id); + logReadModel.setCacheData(JSONArray.toJSONString(cacheDta)); + logReadServer.updateById(logReadModel, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingController.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingController.java new file mode 100644 index 0000000000..6d698641be --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingController.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +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.Const; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +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.node.ProjectInfoCacheService; +import org.dromara.jpom.service.outgiving.DbOutGivingLogService; +import org.dromara.jpom.service.outgiving.OutGivingServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 分发控制 + * + * @author bwcx_jzy + * @since 2019/4/20 + */ +@RestController +@RequestMapping(value = "/outgiving") +@Feature(cls = ClassFeature.OUTGIVING) +public class OutGivingController extends BaseServerController { + + private final OutGivingServer outGivingServer; + private final BuildInfoService buildService; + private final DbOutGivingLogService dbOutGivingLogService; + private final ProjectInfoCacheService projectInfoCacheService; + + public OutGivingController(OutGivingServer outGivingServer, + BuildInfoService buildService, + DbOutGivingLogService dbOutGivingLogService, + ProjectInfoCacheService projectInfoCacheService) { + this.outGivingServer = outGivingServer; + this.buildService = buildService; + this.dbOutGivingLogService = dbOutGivingLogService; + this.projectInfoCacheService = projectInfoCacheService; + } + + /** + * load dispatch list + * 加载分发列表 + * + * @return json + * @author Hotstrip + */ + @PostMapping(value = "dispatch-list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> dispatchList(HttpServletRequest request) { + PageResultDto pageResultDto = outGivingServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * load dispatch list + * 加载分发列表 + * + * @return json + * @author Hotstrip + */ + @GetMapping(value = "dispatch-list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> dispatchListAll(HttpServletRequest request) { + List outGivingModels = outGivingServer.listByWorkspace(request); + return JsonMessage.success("", outGivingModels); + } + + + @RequestMapping(value = "save", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(String type, @ValidatorItem String id, HttpServletRequest request) throws IOException { + if ("add".equalsIgnoreCase(type)) { + // + String checkId = StrUtil.replace(id, StrUtil.DASHED, StrUtil.UNDERLINE); + Validator.validateGeneral(checkId, 2, Const.ID_MAX_LEN, I18nMessageUtil.get("i18n.distribute_id_requirements.9c63")); + //boolean general = StringUtil.isGeneral(id, 2, 20); + //Assert.state(general, ); + return addOutGiving(id, request); + } else { + return updateGiving(id, request); + } + } + + private IJsonMessage addOutGiving(String id, HttpServletRequest request) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id); + Assert.isNull(outGivingModel, I18nMessageUtil.get("i18n.distribute_id_already_exists_globally.6478")); + // + outGivingModel = new OutGivingModel(); + outGivingModel.setId(id); + this.doData(outGivingModel, request); + // + outGivingServer.insert(outGivingModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.addition_succeeded.3fda")); + } + + private IJsonMessage updateGiving(String id, HttpServletRequest request) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingModel, I18nMessageUtil.get("i18n.no_distribution_id_found.8df2")); + doData(outGivingModel, request); + + outGivingServer.updateById(outGivingModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + private void doData(OutGivingModel outGivingModel, HttpServletRequest request) { + outGivingModel.setName(getParameter("name")); + outGivingModel.setGroup(getParameter("group")); + Assert.hasText(outGivingModel.getName(), I18nMessageUtil.get("i18n.distribute_name_cannot_be_empty.0637")); + List outGivingModels = outGivingServer.list(); + // + Map paramMap = ServletUtil.getParamMap(request); + List outGivingNodeProjects = paramMap.entrySet() + .stream() + .filter(stringStringEntry -> StrUtil.startWith(stringStringEntry.getKey(), "node_")) + .map(stringStringEntry -> { + int lastIndexOf = StrUtil.lastIndexOfIgnoreCase(stringStringEntry.getKey(), StrUtil.UNDERLINE); + int indexOf = StrUtil.indexOfIgnoreCase(stringStringEntry.getKey(), StrUtil.UNDERLINE) + 1; + String nodeId = StrUtil.sub(stringStringEntry.getKey(), indexOf, lastIndexOf); + // + String nodeIdProject = stringStringEntry.getValue(); + NodeModel nodeModel = nodeService.getByKey(nodeId); + Assert.notNull(nodeModel, I18nMessageUtil.get("i18n.node_not_exist.0027")); + // + boolean exists = projectInfoCacheService.exists(nodeModel.getWorkspaceId(), nodeModel.getId(), nodeIdProject); + Assert.state(exists, I18nMessageUtil.get("i18n.no_project_id_found.0f21") + nodeIdProject); + // + OutGivingNodeProject outGivingNodeProject = outGivingModel.getNodeProject(nodeModel.getId(), nodeIdProject); + if (outGivingNodeProject == null) { + outGivingNodeProject = new OutGivingNodeProject(); + } + outGivingNodeProject.setNodeId(nodeModel.getId()); + outGivingNodeProject.setProjectId(nodeIdProject); + return outGivingNodeProject; + }) + .peek(outGivingNodeProject -> { + // 判断项目是否已经被使用过啦 + if (outGivingModels != null) { + for (OutGivingModel outGivingModel1 : outGivingModels) { + if (outGivingModel1.getId().equalsIgnoreCase(outGivingModel.getId())) { + continue; + } + boolean checkContains = outGivingModel1.checkContains(outGivingNodeProject.getNodeId(), outGivingNodeProject.getProjectId()); + Assert.state(!checkContains, I18nMessageUtil.get("i18n.same_distribution_project_exists.ff41") + outGivingNodeProject.getProjectId()); + } + } + }).collect(Collectors.toList()); + + Assert.state(CollUtil.size(outGivingNodeProjects) >= 1, I18nMessageUtil.get("i18n.select_at_least_one_node_project.637c")); + + outGivingModel.outGivingNodeProjectList(outGivingNodeProjects); + // + String afterOpt = getParameter("afterOpt"); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + outGivingModel.setAfterOpt(afterOpt1.getCode()); + // + int intervalTime = getParameterInt("intervalTime", 10); + outGivingModel.setIntervalTime(intervalTime); + // + outGivingModel.setClearOld(Convert.toBool(getParameter("clearOld"), false)); + // + String secondaryDirectory = getParameter("secondaryDirectory"); + outGivingModel.setSecondaryDirectory(secondaryDirectory); + outGivingModel.setUploadCloseFirst(Convert.toBool(getParameter("uploadCloseFirst"), false)); + // + String webhook = getParameter("webhook"); + webhook = Opt.ofBlankAble(webhook) + .map(s -> { + Validator.validateMatchRegex(RegexPool.URL_HTTP, s, I18nMessageUtil.get("i18n.invalid_webhooks_address.d836")); + return s; + }) + .orElse(StrUtil.EMPTY); + outGivingModel.setWebhook(webhook); + } + + /** + * 删除分发信息 + * + * @param id 分发id + * @return json + */ + @RequestMapping(value = "release_del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage releaseDel(String id, HttpServletRequest request) { + // 判断构建 + boolean releaseMethod = buildService.checkReleaseMethod(id, request, BuildReleaseMethod.Outgiving); + Assert.state(!releaseMethod, I18nMessageUtil.get("i18n.distribution_with_build_items_message.45f5")); + + OutGivingModel outGivingServerItem = outGivingServer.getByKey(id, request); + + // 解除项目分发独立分发属性 + List outGivingNodeProjectList = outGivingServerItem.outGivingNodeProjectList(); + if (outGivingNodeProjectList != null) { + outGivingNodeProjectList.forEach(outGivingNodeProject -> { + NodeModel item = nodeService.getByKey(outGivingNodeProject.getNodeId()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", outGivingNodeProject.getProjectId()); + JsonMessage message = NodeForward.request(item, NodeUrl.Manage_ReleaseOutGiving, jsonObject); + Assert.state(message.success(), I18nMessageUtil.get("i18n.release_node_project_failed.764e") + message.getMsg()); + }); + } + + int byKey = outGivingServer.delByKey(id, request); + if (byKey > 0) { + // 删除日志 + Entity where = new Entity(); + where.set("outGivingId", id); + dbOutGivingLogService.del(where); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 解绑 + * + * @param id 分发id + * @return json + */ + @GetMapping(value = "unbind.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage unbind(String id, HttpServletRequest request) { + OutGivingModel outGivingServerItem = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingServerItem, I18nMessageUtil.get("i18n.distribution_not_exist.cf8a")); + // 判断构建 + boolean releaseMethod = buildService.checkReleaseMethod(id, request, BuildReleaseMethod.Outgiving); + Assert.state(!releaseMethod, I18nMessageUtil.get("i18n.current_distribution_has_build_items_cannot_unbind.a8e9")); + + int byKey = outGivingServer.delByKey(id, request); + if (byKey > 0) { + // 删除日志 + Entity where = new Entity(); + where.set("outGivingId", id); + dbOutGivingLogService.del(where); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } +} 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 new file mode 100644 index 0000000000..c6bb60fb86 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingLogController.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.log.OutGivingLog; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.outgiving.DbOutGivingLogService; +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; + +/** + * 分发日志 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@RestController +@RequestMapping(value = "/outgiving") +@Feature(cls = ClassFeature.OUTGIVING_LOG) +public class OutGivingLogController extends BaseServerController { + + private final DbOutGivingLogService dbOutGivingLogService; + + public OutGivingLogController(DbOutGivingLogService dbOutGivingLogService) { + this.dbOutGivingLogService = dbOutGivingLogService; + } + + + @RequestMapping(value = "log_list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResult = dbOutGivingLogService.listPage(request); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), 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 new file mode 100644 index 0000000000..af72a5b301 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectController.java @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.BaseIdModel; +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.i18n.I18nMessageUtil; +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; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +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; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.StringUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +/** + * 分发文件管理 + * + * @author bwcx_jzy + * @since 2019/4/21 + */ +@RestController +@RequestMapping(value = "/outgiving") +@Feature(cls = ClassFeature.OUTGIVING) +@Slf4j +public class OutGivingProjectController extends BaseServerController { + + private final OutGivingServer outGivingServer; + private final OutGivingWhitelistService outGivingWhitelistService; + 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, + 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) + public IJsonMessage getItemData(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_id_error.58ce") String id, + HttpServletRequest request) { + String workspaceId = outGivingServer.getCheckUserWorkspace(request); + OutGivingModel outGivingServerItem = outGivingServer.getByKey(id, request); + Objects.requireNonNull(outGivingServerItem, I18nMessageUtil.get("i18n.no_data.1ac0")); + List outGivingNodeProjectList = outGivingServerItem.outGivingNodeProjectList(); + // + Set nodeIds = outGivingNodeProjectList.stream().map(BaseNodeProject::getNodeId).collect(Collectors.toSet()); + List nodeModels = nodeService.getByKey(nodeIds); + Map nodeMap = CollStreamUtil.toMap(nodeModels, BaseIdModel::getId, nodeModel -> nodeModel); + // + Set projectIds = outGivingNodeProjectList.stream().map(nodeProject -> BaseNodeModel.fullId(workspaceId, nodeProject.getNodeId(), nodeProject.getProjectId())).collect(Collectors.toSet()); + List projectInfoCacheModels = projectInfoCacheService.getByKey(projectIds); + Map projectMap = CollStreamUtil.toMap(projectInfoCacheModels, BaseIdModel::getId, data -> data); + + + List collect = outGivingNodeProjectList + .stream() + .map(outGivingNodeProject -> { + NodeModel nodeModel = nodeMap.get(outGivingNodeProject.getNodeId()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("sortValue", outGivingNodeProject.getSortValue()); + jsonObject.put("disabled", outGivingNodeProject.getDisabled()); + jsonObject.put("nodeId", outGivingNodeProject.getNodeId()); + jsonObject.put("projectId", outGivingNodeProject.getProjectId()); + jsonObject.put("nodeName", nodeModel.getName()); + String fullId = BaseNodeModel.fullId(workspaceId, outGivingNodeProject.getNodeId(), outGivingNodeProject.getProjectId()); + jsonObject.put("id", fullId); + ProjectInfoCacheModel projectInfoCacheModel = projectMap.get(fullId); + if (projectInfoCacheModel != null) { + jsonObject.put("cacheProjectName", projectInfoCacheModel.getName()); + } + + OutGivingLog outGivingLog = dbOutGivingLogService.getByProject(id, outGivingNodeProject); + if (outGivingLog != null) { + jsonObject.put("outGivingStatus", outGivingLog.getStatus()); + jsonObject.put("outGivingResult", outGivingLog.getResult()); + jsonObject.put("lastTime", outGivingLog.getCreateTimeMillis()); + jsonObject.put("fileSize", outGivingLog.getFileSize()); + jsonObject.put("progressSize", outGivingLog.getProgressSize()); + } + return jsonObject; + }) + .collect(Collectors.toList()); + JSONObject data = new JSONObject(); + data.put("data", outGivingServerItem); + data.put("projectList", collect); + return JsonMessage.success("", data); + } + + private File checkZip(File path, boolean unzip) { + if (unzip) { + boolean zip = false; + for (String i : StringUtil.PACKAGE_EXT) { + if (FileUtil.pathEndsWith(path, i)) { + zip = true; + break; + } + } + Assert.state(zip, I18nMessageUtil.get("i18n.file_type_not_supported.ae5d") + path.getName()); + } + return path; + } + + /** + * 节点分发文件 + * + * @param id 分发id + * @return json + * @throws IOException IO + */ + @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, + HttpServletRequest request) throws IOException { + // 状态判断 + this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, I18nMessageUtil.get("i18n.distribution_in_progress.c3ae")), request); + File userTempPath = serverConfig.getUserTempPath(); + // 保存文件 + this.uploadSharding(file, userTempPath.getAbsolutePath(), sliceId, totalSlice, nowSlice, fileSumMd5); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + /** + * 节点分发文件 + * + * @param id 分发id + * @param afterOpt 之后的操作 + * @param autoUnzip 是否自动解压 + * @param clearOld 清空发布 + * @return json + * @throws IOException IO + */ + @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, HttpServletRequest request) throws IOException { + this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, I18nMessageUtil.get("i18n.distribution_in_progress.c3ae")), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + // + boolean unzip = Convert.toBool(autoUnzip, false); + File file = FileUtil.file(JpomApplication.getInstance().getDataPath(), ServerConst.OUTGIVING_FILE, id); + FileUtil.mkdir(file); + // + File userTempPath = serverConfig.getUserTempPath(); + File successFile = this.shardingTryMerge(userTempPath.getAbsolutePath(), sliceId, totalSlice, fileSumMd5); + FileUtil.move(successFile, file, true); + // + File dest = FileUtil.file(file, successFile.getName()); + dest = this.checkZip(dest, unzip); + // + OutGivingModel outGivingModel = new OutGivingModel(); + outGivingModel.setId(id); + 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); + // 开启 + OutGivingRun.OutGivingRunBuilder outGivingRunBuilder = OutGivingRun.builder() + .id(outGivingModel.getId()) + .file(dest) + .userModel(getUser()) + .unzip(unzip) + .mode(outGivingModel.getMode()) + .modeData(outGivingModel.getModeData()) + .stripComponents(stripComponentsValue); + outGivingRunBuilder.build().startRun(selectProject); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success_and_distribute.f446")); + } + + private OutGivingModel check(String id, BiConsumer consumer, HttpServletRequest request) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingModel, I18nMessageUtil.get("i18n.upload_failed_no_matching_project.b219")); + // 检查状态 + Integer statusCode = outGivingModel.getStatus(); + OutGivingModel.Status status = BaseEnum.getEnum(OutGivingModel.Status.class, statusCode, OutGivingModel.Status.NO); + consumer.accept(status, outGivingModel); + return outGivingModel; + } + + + /** + * 远程下载节点分发文件 + * + * @param id 分发id + * @param afterOpt 之后的操作 + * @return json + */ + @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) { + Assert.hasText(url, I18nMessageUtil.get("i18n.fill_download_address.763c")); + Assert.state(StrUtil.length(url) <= 200, I18nMessageUtil.get("i18n.url_length_exceeded.ca1c")); + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, I18nMessageUtil.get("i18n.distribution_in_progress.c3ae")), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + // 验证远程 地址 + ServerWhitelist whitelist = outGivingWhitelistService.getServerWhitelistData(request); + whitelist.checkAllowRemoteDownloadHost(url); + + //outGivingModel = outGivingServer.getItem(id); + 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, true); + return JsonMessage.success(I18nMessageUtil.get("i18n.download_success_and_distribute.ae94")); + } + + /** + * 通过构建历史分发 + * + * @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, I18nMessageUtil.get("i18n.distribution_in_progress.c3ae")), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + + BuildInfoModel infoModel = buildInfoService.getByKey(buildId, request); + Assert.notNull(infoModel, I18nMessageUtil.get("i18n.no_build.d163")); + 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, I18nMessageUtil.get("i18n.no_build_record.66a2")); + 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(I18nMessageUtil.get("i18n.start_distribution_exclamation.9fc2")); + } + + /** + * 文件中心分发文件 + * + * @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, I18nMessageUtil.get("i18n.distribution_in_progress.c3ae")), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + FileStorageModel storageModel = fileStorageService.getByKey(fileId, request); + Assert.notNull(storageModel, I18nMessageUtil.get("i18n.file_not_exist.5091")); + // + 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, false); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_distribution_exclamation.9fc2")); + } + + /** + * 静态文件分发文件 + * + * @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, I18nMessageUtil.get("i18n.distribution_in_progress.c3ae")), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + 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, false); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_distribution_exclamation.9fc2")); + } + + /** + * 开始发布任务 + * + * @param outGivingModel 分发对象 + * @param file 文件 + * @param autoUnzip 是否解压 + * @param stripComponents 剔除目录 + * @param selectProject 选择指定项目 + */ + private void startTask(OutGivingModel outGivingModel, File file, String autoUnzip, + String stripComponents, + String selectProject, + boolean deleteFile) { + Assert.state(FileUtil.isFile(file), I18nMessageUtil.get("i18n.file_missing_cannot_publish.3818")); + // + boolean unzip = BooleanUtil.toBoolean(autoUnzip); + // + this.checkZip(file, unzip); + int stripComponentsValue = Convert.toInt(stripComponents, 0); + // 开启 + OutGivingRun.OutGivingRunBuilder outGivingRunBuilder = OutGivingRun.builder() + .id(outGivingModel.getId()) + .file(file) + .userModel(getUser()) + .unzip(unzip) + .mode(outGivingModel.getMode()) + .modeData(outGivingModel.getModeData()) + // 是否删除 + .doneDeleteFile(deleteFile) + // 可以不再设置-会查询最新的 + // .projectSecondaryDirectory(secondaryDirectory) + .stripComponents(stripComponentsValue); + outGivingRunBuilder.build().startRun(selectProject); + } + + @PostMapping(value = "cancel", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage cancel(@ValidatorItem String id, HttpServletRequest request) { + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status == OutGivingModel.Status.ING, I18nMessageUtil.get("i18n.status_not_distributing.6298")), request); + OutGivingRun.cancel(outGivingModel.getId(), getUser()); + // + return JsonMessage.success(I18nMessageUtil.get("i18n.cancel_success.285f")); + } + + @PostMapping(value = "config-project", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage configProject(@RequestBody JSONObject jsonObject, HttpServletRequest request) { + Assert.notNull(jsonObject, I18nMessageUtil.get("i18n.no_info.e59e")); + String id = jsonObject.getString("id"); + List list = jsonObject.getList("data", OutGivingNodeProject.class); + Assert.notEmpty(list, I18nMessageUtil.get("i18n.no_projects_configured.e873")); + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingModel, I18nMessageUtil.get("i18n.no_distribution_project_found.90b0")); + // 更新信息 + List outGivingNodeProjects = outGivingModel.outGivingNodeProjectList(); + Assert.notEmpty(outGivingNodeProjects, I18nMessageUtil.get("i18n.distribute_info_error_no_projects.e75f")); + for (OutGivingNodeProject outGivingNodeProject : list) { + OutGivingNodeProject nodeProject = OutGivingModel.getNodeProject(outGivingNodeProjects, outGivingNodeProject.getNodeId(), outGivingNodeProject.getProjectId()); + Assert.notNull(nodeProject, I18nMessageUtil.get("i18n.no_project_info_found.725a")); + nodeProject.setDisabled(outGivingNodeProject.getDisabled()); + nodeProject.setSortValue(outGivingNodeProject.getSortValue()); + } + // 更新 + OutGivingModel update = new OutGivingModel(); + update.setId(outGivingModel.getId()); + update.outGivingNodeProjectList(outGivingNodeProjects); + outGivingServer.updateById(update); + return JsonMessage.success(I18nMessageUtil.get("i18n.update_success.55aa")); + } + + @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) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingModel, I18nMessageUtil.get("i18n.no_distribution_project_found.90b0")); + List outGivingNodeProjects = outGivingModel.outGivingNodeProjectList(); + Assert.notEmpty(outGivingNodeProjects, I18nMessageUtil.get("i18n.distribute_info_error_no_projects.e75f")); + // + Assert.state(outGivingNodeProjects.size() > 1, I18nMessageUtil.get("i18n.current_distribution_has_only_one_project.cd59")); + outGivingNodeProjects = outGivingNodeProjects.stream() + .filter(nodeProject -> !StrUtil.equals(nodeProject.getProjectId(), projectId) || !StrUtil.equals(nodeProject.getNodeId(), nodeId)) + .collect(Collectors.toList()); + // 更新 + OutGivingModel update = new OutGivingModel(); + update.setId(outGivingModel.getId()); + update.outGivingNodeProjectList(outGivingNodeProjects); + outGivingServer.updateById(update); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } +} 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 new file mode 100644 index 0000000000..55158261d0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectEditController.java @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +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.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.RunMode; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.data.ServerWhitelist; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +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.node.ProjectInfoCacheService; +import org.dromara.jpom.service.outgiving.DbOutGivingLogService; +import org.dromara.jpom.service.outgiving.OutGivingServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +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.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * 节点分发编辑项目 + * + * @author bwcx_jzy + * @since 2019/4/22 + */ +@RestController +@RequestMapping(value = "/outgiving") +@Feature(cls = ClassFeature.OUTGIVING) +@Slf4j +public class OutGivingProjectEditController extends BaseServerController { + + private final OutGivingWhitelistService outGivingWhitelistService; + private final OutGivingServer outGivingServer; + private final ProjectInfoCacheService projectInfoCacheService; + private final BuildInfoService buildService; + private final DbOutGivingLogService dbOutGivingLogService; + + public OutGivingProjectEditController(OutGivingWhitelistService outGivingWhitelistService, + OutGivingServer outGivingServer, + ProjectInfoCacheService projectInfoCacheService, + BuildInfoService buildService, + DbOutGivingLogService dbOutGivingLogService) { + this.outGivingWhitelistService = outGivingWhitelistService; + this.outGivingServer = outGivingServer; + this.projectInfoCacheService = projectInfoCacheService; + this.buildService = buildService; + this.dbOutGivingLogService = dbOutGivingLogService; + } + + /** + * 保存节点分发项目 + * + * @param id id + * @param type 类型 + * @return json + */ + @RequestMapping(value = "save_project", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + 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, I18nMessageUtil.get("i18n.distribute_id_requirements.9c63")); + return addOutGiving(id, request); + } else { + return updateGiving(id, request); + } + } + + /** + * 删除分发项目 + * + * @param id 项目id + * @return json + */ + @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) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingModel, I18nMessageUtil.get("i18n.no_corresponding_distribution_project.6dcd")); + + // 判断构建 + boolean releaseMethod = buildService.checkReleaseMethod(id, request, BuildReleaseMethod.Outgiving); + Assert.state(!releaseMethod, I18nMessageUtil.get("i18n.distribution_with_build_items_message.45f5")); + // + Assert.state(outGivingModel.outGivingProject(), I18nMessageUtil.get("i18n.project_is_not_node_distribution_project_cannot_delete.2a5a")); + + List deleteNodeProject = outGivingModel.outGivingNodeProjectList(); + if (deleteNodeProject != null) { + // 删除实际的项目 + for (OutGivingNodeProject outGivingNodeProject1 : deleteNodeProject) { + NodeModel nodeModel = nodeService.getByKey(outGivingNodeProject1.getNodeId()); + JsonMessage jsonMessage = this.deleteNodeProject(nodeModel, outGivingNodeProject1.getProjectId(), thorough); + if (!jsonMessage.success()) { + return new JsonMessage<>(406, nodeModel.getName() + I18nMessageUtil.get("i18n.node_failed.20d5") + jsonMessage.getMsg()); + } + } + } + + int byKey = outGivingServer.delByKey(id, request); + // 删除日志 + if (byKey > 0) { + Entity where = new Entity(); + where.set("outGivingId", id); + dbOutGivingLogService.del(where); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + private IJsonMessage addOutGiving(String id, HttpServletRequest request) { + // 全局判断 id + OutGivingModel outGivingModel = outGivingServer.getByKey(id); + Assert.isNull(outGivingModel, I18nMessageUtil.get("i18n.distribute_id_already_exists.2168")); + + outGivingModel = new OutGivingModel(); + outGivingModel.setOutGivingProject(true); + outGivingModel.setId(id); + // + List tuples = doData(outGivingModel, false, request); + + outGivingServer.insert(outGivingModel); + IJsonMessage error = saveNodeData(outGivingModel, tuples, false); + return Optional.ofNullable(error).orElseGet(() -> JsonMessage.success(I18nMessageUtil.get("i18n.addition_succeeded.3fda"))); + } + + + private IJsonMessage updateGiving(String id, HttpServletRequest request) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); + Assert.notNull(outGivingModel, I18nMessageUtil.get("i18n.no_distribution_id_found.8df2")); + List tuples = doData(outGivingModel, true, request); + + outGivingServer.updateById(outGivingModel); + IJsonMessage error = saveNodeData(outGivingModel, tuples, true); + return Optional.ofNullable(error).orElseGet(() -> JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be"))); + } + + /** + * 保存节点项目数据 + * + * @param outGivingModel 节点分发项目 + * @param edit 是否为编辑模式 + * @return 错误信息 + */ + private JsonMessage saveNodeData(OutGivingModel outGivingModel, List tuples, boolean edit) { + +// if () { +// if (!edit) { +// outGivingServer.delByKey(outGivingModel.getId()); +// } +// return JsonMessage.getString(405, "数据异常,请重新操作"); +// } + List success = new ArrayList<>(); + boolean fail = false; + try { + for (Tuple tuple : tuples) { + NodeModel nodeModel = tuple.get(0); + JSONObject data = tuple.get(1); + // + JsonMessage jsonMessage = this.sendData(nodeModel, data, true); + if (!jsonMessage.success()) { + if (!edit) { + fail = true; + outGivingServer.delByKey(outGivingModel.getId()); + } + return new JsonMessage<>(406, nodeModel.getName() + I18nMessageUtil.get("i18n.node_failed.20d5") + jsonMessage.getMsg()); + } + success.add(tuple); + // 同步项目信息 + projectInfoCacheService.syncNode(nodeModel, outGivingModel.getId()); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.save_distribution_project_failed.ceec"), e); + if (!edit) { + fail = true; + outGivingServer.delByKey(outGivingModel.getId()); + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.save_node_data_failed.f314") + e.getMessage()); + } finally { + if (fail) { + try { + for (Tuple entry : success) { + deleteNodeProject(entry.get(0), outGivingModel.getId(), null); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.restore_project_failed.7f7c"), e); + } + } + } + return null; + } + + /** + * 删除项目 + * + * @param nodeModel 节点 + * @param project 判断id + * @param thorough 是否彻底删除 + * @return json + */ + private JsonMessage deleteNodeProject(NodeModel nodeModel, String project, String thorough) { + JSONObject data = new JSONObject(); + data.put("id", project); + data.put("thorough", thorough); + JsonMessage request = NodeForward.request(nodeModel, NodeUrl.Manage_DeleteProject, data); + if (request.success()) { + // 同步项目信息 + projectInfoCacheService.syncNode(nodeModel, project); + } + return request; +// // 发起预检查数据 +// String url = nodeModel.getRealUrl(NodeUrl.Manage_DeleteProject); +// HttpRequest request = HttpUtil.createPost(url); +// // 授权信息 +// NodeForward.addUser(request, nodeModel, userModel); + +// request.form(data); +// // +// String body = request.execute() +// .body(); +// return NodeForward.toJsonMessage(body); + } + + /** + * 创建项目管理的默认数据 + * + * @param outGivingModel 分发实体 + * @param edit 是否为编辑模式 + * @return String为有异常 + */ + private JSONObject getDefData(OutGivingModel outGivingModel, boolean edit, HttpServletRequest request) { + JSONObject defData = new JSONObject(); + defData.put("id", outGivingModel.getId()); + defData.put("name", outGivingModel.getName()); + defData.put("group", outGivingModel.getGroup()); + // + // 运行模式 + String runMode = getParameter("runMode"); + RunMode runMode1 = EnumUtil.fromString(RunMode.class, runMode, RunMode.ClassPath); + defData.put("runMode", runMode1.name()); + if (runMode1 == RunMode.ClassPath || runMode1 == RunMode.JavaExtDirsCp) { + String mainClass = getParameter("mainClass"); + defData.put("mainClass", mainClass); + } + if (runMode1 == RunMode.JavaExtDirsCp) { + defData.put("javaExtDirsCp", getParameter("javaExtDirsCp")); + } + if (runMode1 == RunMode.Dsl) { + defData.put("dslContent", getParameter("dslContent")); + } + String whitelistDirectory = getParameter("whitelistDirectory"); + ServerWhitelist configDeNewInstance = outGivingWhitelistService.getServerWhitelistData(request); + List whitelistServerOutGiving = configDeNewInstance.getOutGiving(); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, whitelistDirectory), I18nMessageUtil.get("i18n.select_correct_project_path_or_no_auth_configured.366a")); + + defData.put("whitelistDirectory", whitelistDirectory); + String logPath = getParameter("logPath"); + if (StrUtil.isNotEmpty(logPath)) { + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, logPath), I18nMessageUtil.get("i18n.select_correct_log_path_or_no_auth_configured.9a9b")); + defData.put("logPath", logPath); + } + String lib = getParameter("lib"); + defData.put("lib", lib); + if (edit) { + // 编辑模式 + defData.put("edit", "on"); + } + defData.put("previewData", true); + return defData; + } + + /** + * 处理页面数据 + * + * @param outGivingModel 分发实体 + * @param edit 是否为编辑模式 + */ + private List doData(OutGivingModel outGivingModel, boolean edit, HttpServletRequest request) { + outGivingModel.setName(getParameter("name")); + outGivingModel.setGroup(getParameter("group")); + Assert.hasText(outGivingModel.getName(), I18nMessageUtil.get("i18n.distribute_name_cannot_be_empty.0637")); + // + int intervalTime = getParameterInt("intervalTime", 10); + outGivingModel.setIntervalTime(intervalTime); + outGivingModel.setClearOld(Convert.toBool(getParameter("clearOld"), false)); + // + String nodeIdsStr = getParameter("nodeIds"); + List nodeIds = StrUtil.splitTrim(nodeIdsStr, StrUtil.COMMA); + //List nodeModelList = nodeService.listByWorkspace(request); + Assert.notEmpty(nodeIds, I18nMessageUtil.get("i18n.no_node_info.6366")); + + // + String afterOpt = getParameter("afterOpt"); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, I18nMessageUtil.get("i18n.post_distribution_action_required.8cc8")); + outGivingModel.setAfterOpt(afterOpt1.getCode()); + JSONObject defData = getDefData(outGivingModel, edit, request); + + // + List outGivingModels = outGivingServer.list(); + List outGivingNodeProjects = new ArrayList<>(); + OutGivingNodeProject outGivingNodeProject; + + List tuples = new ArrayList<>(); + + for (String nodeId : nodeIds) { + NodeModel nodeModel = nodeService.getByKey(nodeId); + Assert.notNull(nodeModel, I18nMessageUtil.get("i18n.node_not_exist.760e")); + //String add = getParameter("add_" + nodeModel.getId()); +// if (!nodeModel.getId().equals(add)) { +// iterator.remove(); +// continue; +// } + // 判断项目是否已经被使用过啦 + if (outGivingModels != null) { + for (OutGivingModel outGivingModel1 : outGivingModels) { + if (outGivingModel1.getId().equalsIgnoreCase(outGivingModel.getId())) { + continue; + } + Assert.state(!outGivingModel1.checkContains(nodeModel.getId(), outGivingModel.getId()), I18nMessageUtil.get("i18n.same_distribution_project_exists.ff41") + outGivingModel.getId()); + + } + } + outGivingNodeProject = outGivingModel.getNodeProject(nodeModel.getId(), outGivingModel.getId()); + if (outGivingNodeProject == null) { + outGivingNodeProject = new OutGivingNodeProject(); + } + outGivingNodeProject.setNodeId(nodeModel.getId()); + // 分发id为项目id + outGivingNodeProject.setProjectId(outGivingModel.getId()); + outGivingNodeProjects.add(outGivingNodeProject); + // 检查数据 + JSONObject allData = defData.clone(); + String token = getParameter(StrUtil.format("{}_token", nodeModel.getId())); + allData.put("token", token); + String jvm = getParameter(StrUtil.format("{}_jvm", nodeModel.getId())); + allData.put("jvm", jvm); + String args = getParameter(StrUtil.format("{}_args", nodeModel.getId())); + allData.put("args", args); + String autoStart = getParameter(StrUtil.format("{}_autoStart", nodeModel.getId())); + String disableScanDir = getParameter(StrUtil.format("{}_disableScanDir", nodeModel.getId())); + allData.put("autoStart", Convert.toBool(autoStart, false)); + allData.put("disableScanDir", Convert.toBool(disableScanDir, false)); + allData.put("dslEnv", getParameter(StrUtil.format("{}_dslEnv", nodeModel.getId()))); + allData.put("nodeId", nodeModel.getId()); + JsonMessage jsonMessage = this.sendData(nodeModel, allData, false); + Assert.state(jsonMessage.success(), nodeModel.getName() + I18nMessageUtil.get("i18n.node_failed.20d5") + jsonMessage.getMsg()); + tuples.add(new Tuple(nodeModel, allData)); + } + // 删除已经删除的项目 + deleteProject(outGivingModel, outGivingNodeProjects); + + outGivingModel.outGivingNodeProjectList(outGivingNodeProjects); + // + String secondaryDirectory = getParameter("secondaryDirectory"); + outGivingModel.setSecondaryDirectory(secondaryDirectory); + outGivingModel.setUploadCloseFirst(Convert.toBool(getParameter("uploadCloseFirst"), false)); + // + String webhook = getParameter("webhook"); + webhook = Opt.ofBlankAble(webhook) + .map(s -> { + Validator.validateMatchRegex(RegexPool.URL_HTTP, s, I18nMessageUtil.get("i18n.invalid_webhooks_address.d836")); + return s; + }) + .orElse(StrUtil.EMPTY); + outGivingModel.setWebhook(webhook); + return tuples; + } + + /** + * 删除已经删除过的项目 + * + * @param outGivingModel 分发项目 + * @param outGivingNodeProjects 新的节点项目 + */ + private void deleteProject(OutGivingModel outGivingModel, List outGivingNodeProjects) { + Assert.state(CollUtil.size(outGivingNodeProjects) >= 1, I18nMessageUtil.get("i18n.selected_node_required.d65a")); + // 删除 + List deleteNodeProject = outGivingModel.getDelete(outGivingNodeProjects); + if (deleteNodeProject != null) { + JsonMessage jsonMessage; + // 删除实际的项目 + for (OutGivingNodeProject outGivingNodeProject1 : deleteNodeProject) { + NodeModel nodeModel = nodeService.getByKey(outGivingNodeProject1.getNodeId()); + //outGivingNodeProject1.getNodeData(true); + // 调用彻底删除 + jsonMessage = this.deleteNodeProject(nodeModel, outGivingNodeProject1.getProjectId(), "thorough"); + Assert.state(jsonMessage.success(), nodeModel.getName() + I18nMessageUtil.get("i18n.node_failed.20d5") + jsonMessage.getMsg()); + } + } + } + + private JsonMessage sendData(NodeModel nodeModel, JSONObject data, boolean save) { + if (save) { + data.remove("previewData"); + } + data.put("outGivingProject", true); + // 发起预检查数据 + return NodeForward.request(nodeModel, NodeUrl.Manage_SaveProject, data); + } +} 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 new file mode 100644 index 0000000000..677822915d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistController.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.RegexPool; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +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; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +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.lang.reflect.Field; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 节点授权 + * + * @author bwcx_jzy + * @since 2019/4/22 + */ +@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, + StaticFileStorageService staticFileStorageService) { + this.systemParametersServer = systemParametersServer; + this.outGivingWhitelistService = outGivingWhitelistService; + this.staticFileStorageService = staticFileStorageService; + } + + + /** + * get whiteList data + * 授权数据接口 + * + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> whiteList(HttpServletRequest request) { + ServerWhitelist serverWhitelist = outGivingWhitelistService.getServerWhitelistData(request); + Field[] fields = ReflectUtil.getFields(ServerWhitelist.class); + Map map = new HashMap<>(8); + for (Field field : fields) { + Object value = ReflectUtil.getFieldValue(serverWhitelist, field); + if (value instanceof Collection) { + Collection fieldValue = (Collection) value; + map.put(field.getName(), AgentWhitelist.convertToLine(fieldValue)); + map.put(field.getName() + "Array", fieldValue); + } + } + return JsonMessage.success("", map); + } + + + /** + * 保存节点授权 + * + * @param outGiving 数据 + * @return json + */ + @RequestMapping(value = "whitelistDirectory_submit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @SystemPermission + @Feature(method = MethodFeature.EDIT) + public IJsonMessage whitelistDirectorySubmit(String outGiving, + String allowRemoteDownloadHost, + String staticDir, + HttpServletRequest request) { + String workspaceId = nodeService.getCheckUserWorkspace(request); + return this.whitelistDirectorySubmit(outGiving, staticDir, allowRemoteDownloadHost, workspaceId); + } + + + private IJsonMessage whitelistDirectorySubmit(String outGiving, + String staticDir, + String allowRemoteDownloadHost, + String workspaceId) { + List list = AgentWhitelist.parseToList(outGiving, true, I18nMessageUtil.get("i18n.auth_directory_cannot_be_empty.21ba")); + list = AgentWhitelist.covertToArray(list, I18nMessageUtil.get("i18n.auth_directory_cannot_be_under_jpom.bb67")); + String error = AgentWhitelist.findStartsWith(list); + Assert.isNull(error, I18nMessageUtil.get("i18n.auth_directory_cannot_contain_hierarchy.d6ca") + error); + // + List staticDirList = AgentWhitelist.parseToList(staticDir, false, I18nMessageUtil.get("i18n.static_directory_auth_cannot_be_empty.2cb2")); + staticDirList = AgentWhitelist.covertToArray(staticDirList, 100, I18nMessageUtil.get("i18n.static_directory_auth_cannot_be_under_jpom.8879")); + error = AgentWhitelist.findStartsWith(staticDirList); + Assert.isNull(error, I18nMessageUtil.get("i18n.static_directory_cannot_contain_relation.1a90") + error); + + ServerWhitelist serverWhitelist = outGivingWhitelistService.getServerWhitelistData(workspaceId); + serverWhitelist.setOutGiving(list); + serverWhitelist.setStaticDir(staticDirList); + // + List allowRemoteDownloadHostList = AgentWhitelist.parseToList(allowRemoteDownloadHost, I18nMessageUtil.get("i18n.remote_download_host_cannot_be_empty.cdf5")); + // + if (CollUtil.isNotEmpty(allowRemoteDownloadHostList)) { + for (String s : allowRemoteDownloadHostList) { + Assert.state(ReUtil.isMatch(RegexPool.URL_HTTP, s), I18nMessageUtil.get("i18n.invalid_remote_address_format.7f32") + s); + } + } + serverWhitelist.setAllowRemoteDownloadHost(allowRemoteDownloadHostList == null ? null : CollUtil.newHashSet(allowRemoteDownloadHostList)); + // + + String id = ServerWhitelist.workspaceId(workspaceId); + systemParametersServer.upsert(id, serverWhitelist, id); + + String resultData = AgentWhitelist.convertToLine(list); + // 重新检查静态目录任务状态 + I18nThreadUtil.execute(() -> { + try { + staticFileStorageService.startLoad(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.static_file_task_load_failure.b995"), e); + } + }); + + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10"), resultData); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistService.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistService.java new file mode 100644 index 0000000000..e53bb9ff36 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistService.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.outgiving; + +import org.dromara.jpom.model.data.ServerWhitelist; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author bwcx_jzy + * @since 2022/1/23 + */ +@Service +public class OutGivingWhitelistService { + + private final SystemParametersServer systemParametersServer; + private final NodeService nodeService; + + public OutGivingWhitelistService(SystemParametersServer systemParametersServer, + NodeService nodeService) { + this.systemParametersServer = systemParametersServer; + this.nodeService = nodeService; + } + + + public ServerWhitelist getServerWhitelistData(HttpServletRequest request) { + String workspaceId = nodeService.getCheckUserWorkspace(request); + return this.getServerWhitelistData(workspaceId); + } + + public ServerWhitelist getServerWhitelistData(String workspaceId) { + String id = ServerWhitelist.workspaceId(workspaceId); + ServerWhitelist serverWhitelist = systemParametersServer.getConfigDefNewInstance(id, ServerWhitelist.class); + if (serverWhitelist == null) { + // 兼容旧数据 + serverWhitelist = systemParametersServer.getConfigDefNewInstance(ServerWhitelist.ID, ServerWhitelist.class); + } + return serverWhitelist; + } +} 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 new file mode 100644 index 0000000000..6ac339d659 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptController.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.script; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +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.ServerConst; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.UrlRedirectUtil; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.data.WorkspaceModel; +import org.dromara.jpom.model.script.ScriptModel; +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.script.CommandParam; +import org.dromara.jpom.service.node.script.NodeScriptServer; +import org.dromara.jpom.service.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.service.system.WorkspaceService; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 服务端脚本 + * + * @author bwcx_jzy + * @since 2022/1/19 + */ +@RestController +@RequestMapping(value = "/script") +@Feature(cls = ClassFeature.SCRIPT) +public class ScriptController extends BaseServerController { + + private final ScriptServer scriptServer; + private final NodeScriptServer nodeScriptServer; + private final ScriptExecuteLogServer scriptExecuteLogServer; + private final TriggerTokenLogServer triggerTokenLogServer; + private final WorkspaceService workspaceService; + + public ScriptController(ScriptServer scriptServer, + NodeScriptServer nodeScriptServer, + ScriptExecuteLogServer scriptExecuteLogServer, + TriggerTokenLogServer triggerTokenLogServer, + WorkspaceService workspaceService) { + this.scriptServer = scriptServer; + this.nodeScriptServer = nodeScriptServer; + this.scriptExecuteLogServer = scriptExecuteLogServer; + this.triggerTokenLogServer = triggerTokenLogServer; + this.workspaceService = workspaceService; + } + + /** + * get script list + * + * @return json + */ + @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> scriptList(HttpServletRequest request) { + PageResultDto pageResultDto = scriptServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * get script list + * + * @return json + */ + @GetMapping(value = "list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> scriptListAll(HttpServletRequest request) { + List pageResultDto = scriptServer.listByWorkspace(request); + return JsonMessage.success("", pageResultDto); + } + + @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) { + ScriptModel scriptModel = new ScriptModel(); + scriptModel.setId(id); + scriptModel.setContext(context); + scriptModel.setName(name); + scriptModel.setNodeIds(nodeIds); + scriptModel.setDescription(description); + scriptModel.setDefArgs(CommandParam.checkStr(defArgs)); + scriptModel.setWorkspaceId(scriptServer.covertGlobalWorkspace(request)); + + Assert.hasText(scriptModel.getContext(), I18nMessageUtil.get("i18n.content_is_empty.3122")); + // + scriptModel.setAutoExecCron(this.checkCron(autoExecCron)); + // + String oldNodeIds = null; + if (StrUtil.isEmpty(id)) { + scriptServer.insert(scriptModel); + } else { + ScriptModel byKey = scriptServer.getByKeyAndGlobal(id, request); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.no_corresponding_data.4703")); + oldNodeIds = byKey.getNodeIds(); + scriptServer.updateById(scriptModel, request); + } + this.syncNodeScript(scriptModel, oldNodeIds, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + private void syncDelNodeScript(ScriptModel scriptModel, Collection delNode) { + for (String nodeId : delNode) { + NodeModel byKey = nodeService.getByKey(nodeId); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", scriptModel.getId()); + JsonMessage request = NodeForward.request(byKey, NodeUrl.Script_Del, jsonObject); + Assert.state(request.getCode() == 200, StrUtil.format(I18nMessageUtil.get("i18n.handle_node_deletion_script_failure_duplicate.821e"), byKey.getName(), request.getMsg())); + nodeScriptServer.syncNode(byKey); + } + } + + private void syncNodeScript(ScriptModel scriptModel, String oldNode, HttpServletRequest request) { + List oldNodeIds = StrUtil.splitTrim(oldNode, StrUtil.COMMA); + List newNodeIds = StrUtil.splitTrim(scriptModel.getNodeIds(), StrUtil.COMMA); + Collection delNode = CollUtil.subtract(oldNodeIds, newNodeIds); + // 删除 + this.syncDelNodeScript(scriptModel, delNode); + // 更新 + for (String newNodeId : newNodeIds) { + NodeModel byKey = nodeService.getByKey(newNodeId); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.no_node_found.6f85")); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", scriptModel.getId()); + jsonObject.put("type", "sync"); + jsonObject.put("context", scriptModel.getContext()); + jsonObject.put("autoExecCron", scriptModel.getAutoExecCron()); + jsonObject.put("defArgs", scriptModel.getDefArgs()); + jsonObject.put("description", scriptModel.getDescription()); + jsonObject.put("name", scriptModel.getName()); + jsonObject.put("workspaceId", byKey.getWorkspaceId()); + jsonObject.put("global", scriptModel.global()); + jsonObject.put("nodeId", byKey.getId()); + JsonMessage jsonMessage = NodeForward.request(byKey, NodeUrl.Script_Save, jsonObject); + Assert.state(jsonMessage.success(), StrUtil.format(I18nMessageUtil.get("i18n.handle_node_sync_script_failure.e99f"), byKey.getName(), jsonMessage.getMsg())); + nodeScriptServer.syncNode(byKey); + } + } + + @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, HttpServletRequest request) { + ScriptModel server = scriptServer.getByKeyAndGlobal(id, request); + if (server != null) { + File file = server.scriptPath(); + boolean del = FileUtil.del(file); + Assert.state(del, I18nMessageUtil.get("i18n.clear_script_file_failed.f595")); + // 删除节点中的脚本 + String nodeIds = server.getNodeIds(); + List delNode = StrUtil.splitTrim(nodeIds, StrUtil.COMMA); + this.syncDelNodeScript(server, delNode); + scriptServer.delByKey(id, request); + // + scriptExecuteLogServer.delByWorkspace(request, entity -> entity.set("scriptId", id)); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + @GetMapping(value = "get", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage get(String id, HttpServletRequest request) { + String workspaceId = scriptServer.getCheckUserWorkspace(request); + ScriptModel server = scriptServer.getByKeyAndGlobal(id, request); + Assert.notNull(server, I18nMessageUtil.get("i18n.no_script.93c4")); + String nodeIds = server.getNodeIds(); + List newNodeIds = StrUtil.splitTrim(nodeIds, StrUtil.COMMA); + List nodeList = newNodeIds.stream() + .map(s -> { + JSONObject jsonObject = new JSONObject(); + NodeModel nodeModel = nodeService.getByKey(s); + if (nodeModel == null) { + jsonObject.put("nodeName", I18nMessageUtil.get("i18n.unknown_data_loss.5a24")); + } else { + jsonObject.put("nodeName", nodeModel.getName()); + jsonObject.put("nodeId", nodeModel.getId()); + jsonObject.put("workspaceId", nodeModel.getWorkspaceId()); + WorkspaceModel workspaceModel = workspaceService.getByKey(nodeModel.getWorkspaceId()); + jsonObject.put("workspaceName", Optional.ofNullable(workspaceModel).map(WorkspaceModel::getName).orElse(I18nMessageUtil.get("i18n.unknown_data_loss.5a24"))); + } + return jsonObject; + }) + .collect(Collectors.toList()); + // 判断是否可以编辑节点 + 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); + jsonObject.put("prohibitSync", prohibitSync); + return JsonMessage.success("", jsonObject); + } + + /** + * 释放脚本关联的节点 + * + * @param id 脚本ID + * @return json + */ + @RequestMapping(value = "unbind.json", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission + public IJsonMessage unbind(@ValidatorItem String id, HttpServletRequest request) { + ScriptModel update = new ScriptModel(); + update.setId(id); + update.setNodeIds(StrUtil.EMPTY); + scriptServer.updateById(update, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.unbind_success.1c43")); + } + + /** + * 同步到指定工作空间 + * + * @param ids 节点ID + * @param toWorkspaceId 分配到到工作空间ID + * @return msg + */ + @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission() + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); + // + scriptServer.checkUserWorkspace(toWorkspaceId); + scriptServer.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @RequestMapping(value = "trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage> getTriggerUrl(String id, String rest, HttpServletRequest request) { + ScriptModel item = scriptServer.getByKey(id, request); + UserModel user = getUser(); + ScriptModel updateInfo; + if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { + updateInfo = new ScriptModel(); + updateInfo.setId(id); + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), scriptServer.typeName(), + item.getId(), user.getId())); + scriptServer.updateById(updateInfo); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + 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()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + Map map = new HashMap<>(10); + map.put("triggerUrl", FileUtil.normalize(triggerBuildUrl)); + String batchTriggerBuildUrl = String.format("/%s/%s", contextPath, ServerOpenApi.SERVER_SCRIPT_TRIGGER_BATCH); + map.put("batchTriggerUrl", FileUtil.normalize(batchTriggerBuildUrl)); + + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptLogController.java b/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptLogController.java new file mode 100644 index 0000000000..08a4e6c8c4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptLogController.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.script; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.script.ScriptExecuteLogModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.socket.ServerScriptProcessBuilder; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2022/1/20 + */ + +@RestController +@RequestMapping(value = "/script_log") +@Feature(cls = ClassFeature.SCRIPT_LOG) +public class ScriptLogController extends BaseServerController { + + private final ScriptExecuteLogServer scriptExecuteLogServer; + private final ScriptServer scriptServer; + + public ScriptLogController(ScriptExecuteLogServer scriptExecuteLogServer, + ScriptServer scriptServer) { + this.scriptExecuteLogServer = scriptExecuteLogServer; + this.scriptServer = scriptServer; + } + + /** + * get script log list + * + * @return json + */ + @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> scriptList(HttpServletRequest request) { + PageResultDto pageResultDto = scriptExecuteLogServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + /** + * 删除日志 + * + * @param id id + * @param executeId 执行ID + * @return json + */ + @RequestMapping(value = "del_log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delLog(@ValidatorItem() String id, + @ValidatorItem() String executeId, + HttpServletRequest request) { + ScriptModel item = null; + try { + item = scriptServer.getByKeyAndGlobal(id, request, "ignore"); + } catch (IllegalArgumentException | IllegalStateException e) { + if (!StrUtil.equals("ignore", e.getMessage())) { + throw e; + } + } + File logFile = item == null ? ScriptModel.logFile(id, executeId) : item.logFile(executeId); + boolean fastDel = CommandUtil.systemFastDel(logFile); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.delete_log_file_failure.bf0b")); + scriptExecuteLogServer.delByKey(executeId); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 获取的日志 + * + * @param id id + * @param executeId 执行ID + * @param line 需要获取的行号 + * @return json + */ + @RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getNowLog(@ValidatorItem() String id, + @ValidatorItem() String executeId, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.line_number_error.c65d") int line, + HttpServletRequest request) { + ScriptModel item = scriptServer.getByKey(id, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + File logFile = item.logFile(executeId); + Assert.state(FileUtil.isFile(logFile), I18nMessageUtil.get("i18n.log_file_error.473b")); + JSONObject data = FileUtils.readLogFile(logFile, line); + // 运行中 + data.put("run", ServerScriptProcessBuilder.isRun(executeId)); + return JsonMessage.success("", data); + } +} 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 new file mode 100644 index 0000000000..a514741b86 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandInfoController.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.ssh; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +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.ServerConst; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.UrlRedirectUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.model.data.CommandModel; +import org.dromara.jpom.model.data.SshModel; +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.script.CommandParam; +import org.dromara.jpom.service.node.ssh.CommandExecLogService; +import org.dromara.jpom.service.node.ssh.SshCommandService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.dromara.jpom.util.CommandUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 命令管理 + * + * @author : Arno + * @since : 2021/12/6 21:42 + */ +@RestController +@RequestMapping(value = "/node/ssh_command") +@Feature(cls = ClassFeature.SSH_COMMAND) +public class CommandInfoController extends BaseServerController { + + private final SshCommandService sshCommandService; + private final CommandExecLogService commandExecLogService; + private final TriggerTokenLogServer triggerTokenLogServer; + private final SshService sshService; + + public CommandInfoController(SshCommandService sshCommandService, + CommandExecLogService commandExecLogService, + TriggerTokenLogServer triggerTokenLogServer, + SshService sshService) { + this.sshCommandService = sshCommandService; + this.commandExecLogService = commandExecLogService; + this.triggerTokenLogServer = triggerTokenLogServer; + this.sshService = sshService; + } + + /** + * 分页获取命令信息 + * + * @return result + */ + @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> page(HttpServletRequest request) { + PageResultDto page = sshCommandService.listPage(request); + return JsonMessage.success("", page); + } + + /** + * 新建/编辑命令 + * + * @param data 命令信息 + * @return result + * @api {POST} node/ssh_command/edit 新建/编辑命令 + * @apiGroup node/ssh_command + * @apiUse defResultJson + * @apiBody {String} name 命令名称 + * @apiBody {String} command 命令内容 + * @apiBody {String} [desc] 命令描述 + * @apiBody {String} defParams 默认参数 + * @apiBody {String} autoExecCron 定时构建表达式 + * @apiBody {String} id 命令主键 ID + * @apiBody {String} [sshIds] SSH 节点 + */ + @RequestMapping(value = "edit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@RequestBody JSONObject data, HttpServletRequest request) { + String name = data.getString("name"); + String command = data.getString("command"); + String desc = data.getString("desc"); + String defParams = data.getString("defParams"); + Assert.hasText(name, I18nMessageUtil.get("i18n.command_name_required.49fa")); + Assert.hasText(command, I18nMessageUtil.get("i18n.command_content_required.6005")); + String autoExecCron = this.checkCron(data.getString("autoExecCron")); + String id = data.getString("id"); + // + CommandModel commandModel = new CommandModel(); + commandModel.setName(name); + commandModel.setCommand(command); + commandModel.setDesc(desc); + String sshIds = data.getString("sshIds"); + List sshIdList = StrUtil.split(sshIds, StrUtil.COMMA, true, true); + if (CollUtil.isNotEmpty(sshIdList)) { + List commandModels = sshService.getByKey(sshIdList, request); + Assert.state(CollUtil.size(sshIdList) == CollUtil.size(commandModels), I18nMessageUtil.get("i18n.associated_ssh_node_contains_nonexistent_node.c7f5")); + } + commandModel.setSshIds(sshIds); + commandModel.setAutoExecCron(autoExecCron); + // + commandModel.setDefParams(CommandParam.checkStr(defParams)); + + if (StrUtil.isEmpty(id)) { + sshCommandService.insert(commandModel); + } else { + commandModel.setId(id); + sshCommandService.updateById(commandModel, request); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 删除命令 + * + * @param id id + * @return result + * @api {DELETE} node/ssh_command/del 删除命令 + * @apiGroup node/ssh_command + * @apiUse defResultJson + * @apiParam {String} id 日志 id + */ + @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, HttpServletRequest request) { + File logFileDir = CommandExecLogModel.logFileDir(id); + boolean fastDel = CommandUtil.systemFastDel(logFileDir); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.log_file_cleanup_failed.3a3b")); + // + + sshCommandService.delByKey(id, request); + commandExecLogService.delByWorkspace(request, entity -> entity.set("commandId", id)); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 批量执行命令 + * + * @return result + * @api {POST} node/ssh_command/batch 批量执行命令 + * @apiGroup node/ssh_command + * @apiUse defResultJson + * @apiParam {String} id 命令 id + * @apiParam {String} [params] 参数 + * @apiParam {String} nodes ssh节点 + * @apiSuccess {String} data batchId + */ + @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 = "i18n.running_node_cannot_be_empty.ffc6") String nodes) throws IOException { + Assert.hasText(id, I18nMessageUtil.get("i18n.execution_command_required.1cf3")); + Assert.hasText(nodes, I18nMessageUtil.get("i18n.execution_node_required.d747")); + String batchId = sshCommandService.executeBatch(id, params, nodes); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313"), batchId); + } + + /** + * 同步到指定工作空间 + * + * @param ids 节点ID + * @param toWorkspaceId 分配到到工作空间ID + * @return msg + */ + @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission() + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); + // + sshCommandService.checkUserWorkspace(toWorkspaceId); + sshCommandService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @RequestMapping(value = "trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + 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(), sshCommandService.typeName(), + item.getId(), user.getId())); + sshCommandService.updateById(updateInfo); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + 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()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + Map map = new HashMap<>(10); + map.put("triggerUrl", FileUtil.normalize(triggerBuildUrl)); + String batchTriggerBuildUrl = String.format("/%s/%s", contextPath, ServerOpenApi.SSH_COMMAND_TRIGGER_BATCH); + map.put("batchTriggerUrl", FileUtil.normalize(batchTriggerBuildUrl)); + + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } +} 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 new file mode 100644 index 0000000000..e2aedab1d2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandLogController.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.ssh; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.servlet.ServletUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.node.ssh.CommandExecLogService; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.FileUtils; +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; + +/** + * 命令执行日志 + * + * @author bwcx_jzy + * @since 2021/12/23 + */ +@RestController +@RequestMapping(value = "/node/ssh_command_log") +@Feature(cls = ClassFeature.SSH_COMMAND_LOG) +public class CommandLogController extends BaseServerController { + + private final CommandExecLogService commandExecLogService; + + public CommandLogController(CommandExecLogService commandExecLogService) { + this.commandExecLogService = commandExecLogService; + } + + /** + * 分页获取命令信息 + * + * @return result + */ + @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> page(HttpServletRequest request) { + PageResultDto page = commandExecLogService.listPage(request); + return JsonMessage.success("", page); + } + + /** + * 删除日志记录 + * + * @param id id + * @return result + * @api {POST} node/ssh_command_log/del 删除日志记录 + * @apiGroup node/ssh_command_log + * @apiUse defResultJson + * @apiParam {String} id 记录 id + */ + @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, HttpServletRequest request) { + CommandExecLogModel execLogModel = commandExecLogService.getByKey(id, request); + Assert.notNull(execLogModel, I18nMessageUtil.get("i18n.no_record.ff41")); + File logFile = execLogModel.logFile(); + boolean fastDel = CommandUtil.systemFastDel(logFile); + Assert.state(!fastDel, I18nMessageUtil.get("i18n.log_file_cleanup_failed.3a3b")); + // + commandExecLogService.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 命令执行记录 + * + * @param commandId 命令ID + * @param batchId 批次ID + * @return result + * @api {GET} node/ssh_command_log/batch_list 命令执行记录 + * @apiGroup node/ssh_command_log + * @apiUse defResultJson + * @apiParam {String} commandId 命令ID + * @apiParam {String} batchId 批次ID + * @apiSuccess {Object} commandExecLogModels 命令执行记录 + * @apiSuccess {String} commandExecLogModels.commandId 命令ID + * @apiSuccess {String} commandExecLogModels.batchId 批次ID + * @apiSuccess {String} commandExecLogModels.sshId ssh Id + * @apiSuccess {Number} commandExecLogModels.status Status + * @apiSuccess {String} commandExecLogModels.commandName 命令名称 + * @apiSuccess {String} commandExecLogModels.sshName ssh 名称 + * @apiSuccess {String} commandExecLogModels.params 参数 + * @apiSuccess {Number} commandExecLogModels.triggerExecType 触发类型 {0,手动,1 自动触发} + * @apiSuccess {Boolean} commandExecLogModels.hasLog 日志文件是否存在 + */ + @GetMapping(value = "batch_list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + 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); + + return JsonMessage.success("", commandExecLogModels); + } + + /** + * 获取日志 + * + * @param id id + * @param line 需要获取的行号 + * @return json + * @api {POST} node/ssh_command_log/log 获取日志 + * @apiGroup node/ssh_command_log + * @apiUse defResultJson + * @apiParam {String} id 日志 id + * @apiParam {Number} line 需要获取的行号 + * @apiSuccess {Boolean} run 运行状态 + */ + @RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage log(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.line_number_error.c65d") int line, HttpServletRequest request) { + CommandExecLogModel item = commandExecLogService.getByKey(id, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + + File file = item.logFile(); + if (!FileUtil.exist(file)) { + return JsonMessage.success(I18nMessageUtil.get("i18n.no_log_info.d551")); + } + Assert.state(FileUtil.isFile(file), I18nMessageUtil.get("i18n.log_file_error.473b")); + + JSONObject data = FileUtils.readLogFile(file, line); + // 运行中 + Integer status = item.getStatus(); + data.put("run", status != null && status == CommandExecLogModel.Status.ING.getCode()); + + return JsonMessage.success("", data); + } + + /** + * 下载日志 + * + * @param logId 日志 id + * @api {GET} node/ssh_command_log/download_log 下载日志 + * @apiGroup node/ssh_command_log + * @apiUse defResultJson + * @apiParam {String} logId 日志 id + * @apiSuccess {File} file 日志文件 + */ + @RequestMapping(value = "download_log", method = RequestMethod.GET) + @ResponseBody + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String logId, HttpServletRequest request, HttpServletResponse response) { + CommandExecLogModel item = commandExecLogService.getByKey(logId, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + File logFile = item.logFile(); + if (!FileUtil.exist(logFile)) { + return; + } + if (logFile.isFile()) { + ServletUtil.write(response, logFile); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/ssh/SshController.java b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/SshController.java new file mode 100644 index 0000000000..d6eeb699d2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/SshController.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.ssh; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNode; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.log.SshTerminalExecuteLog; +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.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.SshTerminalExecuteLogService; +import org.dromara.jpom.service.node.ssh.SshService; +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.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2019/8/9 + */ +@RestController +@RequestMapping(value = "node/ssh") +@Feature(cls = ClassFeature.SSH) +@Slf4j +public class SshController extends BaseServerController { + + private final SshService sshService; + private final SshTerminalExecuteLogService sshTerminalExecuteLogService; + private final BuildInfoService buildInfoService; + private final MachineSshServer machineSshServer; + + public SshController(SshService sshService, + SshTerminalExecuteLogService sshTerminalExecuteLogService, + BuildInfoService buildInfoService, + MachineSshServer machineSshServer) { + this.sshService = sshService; + this.sshTerminalExecuteLogService = sshTerminalExecuteLogService; + this.buildInfoService = buildInfoService; + this.machineSshServer = machineSshServer; + } + + + @PostMapping(value = "list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResultDto = sshService.listPage(request); + pageResultDto.each(sshModel -> { + sshModel.setMachineSsh(machineSshServer.getByKey(sshModel.getMachineSshId())); + List nodeBySshId = nodeService.getNodeBySshId(sshModel.getId()); + sshModel.setLinkNode(CollUtil.getFirst(nodeBySshId)); + }); + return new JsonMessage<>(200, "", pageResultDto); + } + + @GetMapping(value = "list_data_all.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listDataAll(HttpServletRequest request) { + List list = sshService.listByWorkspace(request); + return new JsonMessage<>(200, "", list); + } + + @GetMapping(value = "list-tree", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listTree(HttpServletRequest request) { + List list = sshService.listByWorkspace(request); + Map> groupNode = new HashMap<>(4); + List> treeNodes = list.stream() + .map(sshModel -> { + String group = sshModel.getGroup(); + String groupId = SecureUtil.sha1(StrUtil.emptyToDefault(group, StrUtil.EMPTY)); + String groupId2 = StrUtil.format("g_{}", groupId); + groupNode.computeIfAbsent(groupId, s -> new TreeNode<>(groupId2, StrUtil.SLASH, group, sshModel.getName())); + // + TreeNode treeNode = new TreeNode<>(sshModel.getId(), groupId2, sshModel.getName(), sshModel.getName()); + Map extra = new HashMap<>(); + extra.put("fileDirs", sshModel.getFileDirs()); + extra.put("isLeaf", true); + treeNode.setExtra(extra); + return treeNode; + }) + .collect(Collectors.toList()); + // + treeNodes.addAll(groupNode.values()); + Tree tree = TreeUtil.buildSingle(treeNodes, StrUtil.SLASH); + return new JsonMessage<>(200, "", tree); + } + + @GetMapping(value = "get-item.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getItem(@ValidatorItem String id, HttpServletRequest request) { + SshModel byKey = sshService.getByKey(id, request); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.ssh_does_not_exist_with_message.de6c")); + return new JsonMessage<>(200, "", byKey); + } + + /** + * 查询所有的分组 + * + * @return list + */ + @GetMapping(value = "list-group-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listGroupAll(HttpServletRequest request) { + List listGroup = sshService.listGroup(request); + return JsonMessage.success("", listGroup); + } + + /** + * 编辑 + * + * @param name 名称 + * @param group 分组名 + * @param request 请求对象 + * @param id ID + * @return json + */ + @PostMapping(value = "save.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_ssh_name_cannot_be_empty.ff4f") String name, + String id, + String group, + HttpServletRequest request) { + SshModel sshModel = new SshModel(); + sshModel.setName(name); + sshModel.setGroup(group); + sshModel.setId(id); + sshService.updateById(sshModel, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + + @PostMapping(value = "del.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id, HttpServletRequest request) { + boolean checkSsh = buildInfoService.checkReleaseMethodByLike(id, request, BuildReleaseMethod.Ssh); + Assert.state(!checkSsh, I18nMessageUtil.get("i18n.ssh_with_build_items_message.0f6d")); + // 判断是否绑定节点 + List nodeBySshId = nodeService.getNodeBySshId(id); + Assert.state(CollUtil.isEmpty(nodeBySshId), I18nMessageUtil.get("i18n.ssh_bound_to_node_message.7b64")); + + sshService.delByKey(id, request); + // + int logCount = sshTerminalExecuteLogService.delByWorkspace(request, entity -> entity.set("sshId", id)); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + @PostMapping(value = "del-fore", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission + public IJsonMessage delFore(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id) { + boolean checkSsh = buildInfoService.checkReleaseMethodByLike(id, BuildReleaseMethod.Ssh); + Assert.state(!checkSsh, I18nMessageUtil.get("i18n.ssh_with_build_items_message.0f6d")); + // 判断是否绑定节点 + List nodeBySshId = nodeService.getNodeBySshId(id); + Assert.state(CollUtil.isEmpty(nodeBySshId), I18nMessageUtil.get("i18n.ssh_bound_to_node_message.7b64")); + + sshService.delByKey(id); + // + int logCount = sshTerminalExecuteLogService.delByKey(null, entity -> entity.set("sshId", id)); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 执行记录 + * + * @return json + */ + @PostMapping(value = "log_list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.SSH_TERMINAL_LOG, method = MethodFeature.LIST) + public IJsonMessage> logListData(HttpServletRequest request) { + PageResultDto pageResult = sshTerminalExecuteLogService.listPage(request); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), pageResult); + } + + /** + * 同步到指定工作空间 + * + * @param ids 节点ID + * @param toWorkspaceId 分配到到工作空间ID + * @return msg + */ + @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission() + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, + @ValidatorItem String toWorkspaceId, + HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); + // + sshService.checkUserWorkspace(toWorkspaceId); + sshService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/ssh/SshFileController.java b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/SshFileController.java new file mode 100644 index 0000000000..e198bff4a5 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/SshFileController.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.ssh; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.controller.BaseSshFileController; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.util.FileUtils; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.function.BiFunction; + +/** + * ssh 文件管理 + * + * @author bwcx_jzy + * @since 2019/8/10 + */ +@RestController +@RequestMapping("node/ssh") +@Feature(cls = ClassFeature.SSH_FILE) +@Slf4j +public class SshFileController extends BaseSshFileController { + + + @Override + protected T checkConfigPath(String id, BiFunction function) { + SshModel sshModel = sshService.getByKey(id); + Assert.notNull(sshModel, I18nMessageUtil.get("i18n.no_corresponding_ssh.aa68")); + MachineSshModel machineSshModel = machineSshServer.getByKey(sshModel.getMachineSshId(), false); + return function.apply(machineSshModel, sshModel); + } + + @Override + protected T checkConfigPathChildren(String id, String path, String children, BiFunction function) { + FileUtils.checkSlip(path); + Opt.ofBlankAble(children).ifPresent(FileUtils::checkSlip); + + SshModel sshModel = sshService.getByKey(id); + Assert.notNull(sshModel, I18nMessageUtil.get("i18n.no_corresponding_ssh.aa68")); + List fileDirs = sshModel.fileDirs(); + String normalize = FileUtil.normalize(StrUtil.SLASH + path + StrUtil.SLASH); + // + Assert.state(CollUtil.contains(fileDirs, normalize), I18nMessageUtil.get("i18n.cannot_operate_current_directory.aa3d")); + MachineSshModel machineSshModel = machineSshServer.getByKey(sshModel.getMachineSshId(), false); + return function.apply(machineSshModel, sshModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/BackupInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/BackupInfoController.java new file mode 100644 index 0000000000..88ef5631c1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/BackupInfoController.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.text.UnicodeUtil; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.ContentType; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.StorageServiceFactory; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.BackupInfoModel; +import org.dromara.jpom.model.enums.BackupStatusEnum; +import org.dromara.jpom.model.enums.BackupTypeEnum; +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.dblog.BackupInfoService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 数据库备份 controller + * + * @author Hotstrip + * @since 2021-11-18 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_BACKUP) +@SystemPermission +@ConditionalOnProperty(prefix = "jpom.db", name = "mode", havingValue = "H2", matchIfMissing = true) +@Slf4j +public class BackupInfoController extends BaseServerController { + + + private final BackupInfoService backupInfoService; + + public BackupInfoController(BackupInfoService backupInfoService) { + this.backupInfoService = backupInfoService; + } + + /** + * 分页加载备份列表数据 + * + * @return json + */ + @PostMapping(value = "/system/backup/list") + @Feature(method = MethodFeature.LIST) + public Object loadBackupList(HttpServletRequest request) { + // 查询数据库 + PageResultDto pageResult = backupInfoService.listPage(request); + pageResult.each(backupInfoModel -> backupInfoModel.setFileExist(FileUtil.exist(backupInfoModel.getFilePath()))); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), pageResult); + } + + /** + * 删除备份数据 + * + * @param id 备份 ID + * @return json + */ + @PostMapping(value = "/system/backup/delete") + @Feature(method = MethodFeature.DEL) + @SystemPermission(superUser = true) + public IJsonMessage deleteBackup(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id) { + // 删除备份信息 + backupInfoService.delByKey(id); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 还原备份数据 + * 还原的时候不能异步了,只能等待备份还原成功或者失败 + * + * @param id 备份 ID + * @return json + */ + @PostMapping(value = "/system/backup/restore") + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage restoreBackup(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id) { + // 根据 id 查询备份信息 + BackupInfoModel backupInfoModel = backupInfoService.getByKey(id); + Objects.requireNonNull(backupInfoModel, I18nMessageUtil.get("i18n.backup_data_not_exist.f88c")); + + // 检查备份文件是否存在 + String filePath = backupInfoModel.getFilePath(); + File file = new File(filePath); + if (!FileUtil.exist(file)) { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.backup_file_not_exist.9628")); + } + // 清空 sql 加载记录 + StorageServiceFactory.clearExecuteSqlLog(); + // 还原备份文件 + boolean flag = backupInfoService.restoreWithSql(filePath); + if (flag) { + // 还原备份数据成功之后需要修改当前备份信息的状态(因为备份的时候该备份信息状态是备份中) + this.fuzzyUpdate(SecureUtil.sha1(file)); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.restore_backup_data_success.253a")); + } + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.restore_backup_data_failed.58af")); + } + + /** + * 模糊更新 + * + * @param sha1 文件签名 + */ + private void fuzzyUpdate(String sha1) { + BackupInfoModel where = new BackupInfoModel(); + where.setStatus(BackupStatusEnum.DEFAULT.getCode()); + List list = backupInfoService.listByBean(where); + Optional.ofNullable(list).ifPresent(backupInfoModels -> { + for (BackupInfoModel backupInfoModel : backupInfoModels) { + String filePath = backupInfoModel.getFilePath(); + if (!FileUtil.exist(filePath)) { + continue; + } + File file = FileUtil.file(filePath); + if (StrUtil.equals(SecureUtil.sha1(file), sha1)) { + // 是同一个文件 + BackupInfoModel update = new BackupInfoModel(); + update.setId(backupInfoModel.getId()); + update.setFileSize(FileUtil.size(file)); + update.setStatus(BackupStatusEnum.SUCCESS.getCode()); + update.setSha1Sum(sha1); + int updateCount = backupInfoService.updateById(update); + log.debug(I18nMessageUtil.get("i18n.update_restore_data.1b0b"), updateCount); + } + } + }); + } + + /** + * 创建备份任务 + * + * @param map 参数 map.tableNameList 选中备份的表名称 + * @return json + */ + @PostMapping(value = "/system/backup/create") + @Feature(method = MethodFeature.EDIT) + public IJsonMessage backup(@RequestBody Map map) { + List tableNameList = JSON.parseArray(JSON.toJSONString(map.get("tableNameList")), String.class); + backupInfoService.backupToSql(tableNameList); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded_refresh_backup.54a9")); + } + + /** + * 导入备份数据 + * + * @return json + */ + @PostMapping(value = "/system/backup/upload") + @Feature(method = MethodFeature.UPLOAD) + @SystemPermission(superUser = true) + public IJsonMessage uploadBackupFile(MultipartFile file) throws IOException { + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + Assert.state(StrUtil.containsAnyIgnoreCase(extName, "sql"), I18nMessageUtil.get("i18n.file_type_not_supported2.d497") + extName); + String saveFileName = UnicodeUtil.toUnicode(originalFilename); + saveFileName = saveFileName.replace(StrUtil.BACKSLASH, "_"); + // 存储目录 + File directory = FileUtil.file(StorageServiceFactory.dbLocalPath(), DbExtConfig.BACKUP_DIRECTORY_NAME); + // 生成唯一id + String format = String.format("%s_%s", IdUtil.fastSimpleUUID(), saveFileName); + format = StrUtil.maxLength(format, 40); + File backupSqlFile = FileUtil.file(directory, format + "." + extName); + FileUtil.mkParentDirs(backupSqlFile); + file.transferTo(backupSqlFile); + // 记录到数据库 + String sha1Sum = SecureUtil.sha1(backupSqlFile); + BackupInfoModel backupInfoModel = new BackupInfoModel(); + backupInfoModel.setSha1Sum(sha1Sum); + boolean exists = backupInfoService.exists(backupInfoModel); + if (exists) { + FileUtil.del(backupSqlFile); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.data_already_exists.0397")); + } + + backupInfoModel.setName(backupSqlFile.getName()); + backupInfoModel.setBackupType(BackupTypeEnum.IMPORT.getCode()); + backupInfoModel.setStatus(BackupStatusEnum.SUCCESS.getCode()); + backupInfoModel.setFileSize(FileUtil.size(backupSqlFile)); + + backupInfoModel.setSha1Sum(sha1Sum); + backupInfoModel.setFilePath(FileUtil.getAbsolutePath(backupSqlFile)); + backupInfoService.insert(backupInfoModel); + + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.import_success.b6d1")); + } + + /** + * 下载备份数据 + * + * @param id 备份 ID + */ + @GetMapping(value = "/system/backup/download") + @Feature(method = MethodFeature.DOWNLOAD) + public void downloadBackup(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id, HttpServletResponse response) { + // 根据 id 查询备份信息 + BackupInfoModel backupInfoModel = backupInfoService.getByKey(id); + Objects.requireNonNull(backupInfoModel, I18nMessageUtil.get("i18n.backup_data_not_exist.f88c")); + + // 检查备份文件是否存在 + File file = new File(backupInfoModel.getFilePath()); + if (!FileUtil.exist(file)) { + //log.error("文件不存在,无法下载...backupId: {}", id); + ServletUtil.write(response, JsonMessage.getString(404, I18nMessageUtil.get("i18n.file_does_not_exist_for_download.8dd6")), ContentType.JSON.toString()); + return; + } + + // 下载文件 + ServletUtil.write(response, file); + } + + /** + * 读取数据库表名称列表 + * + * @return json + */ + @PostMapping(value = "/system/backup/table-name-list") + @Feature(method = MethodFeature.LIST) + public IJsonMessage> loadTableNameList() { + // 从数据库加载表名称列表 + List tableNameList = backupInfoService.h2TableNameList(); + // 扫描程序,拿到表名称和别名 + + Set> classes = ClassUtil.scanPackageByAnnotation("org.dromara.jpom", TableName.class); + Map TABLE_NAME_MAP = CollStreamUtil.toMap(classes, aClass -> { + TableName tableName = aClass.getAnnotation(TableName.class); + return tableName.value(); + }, aClass -> { + TableName tableName = aClass.getAnnotation(TableName.class); + return I18nMessageUtil.get(tableName.nameKey()); + }); + + List list = tableNameList.stream().map(s -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("tableName", s); + jsonObject.put("tableDesc", StrUtil.emptyToDefault(TABLE_NAME_MAP.get(s), s)); + return jsonObject; + }).collect(Collectors.toList()); + return new JsonMessage<>(200, "", list); + } + +} 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 new file mode 100644 index 0000000000..0b9e4e9602 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/LogManageController.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +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.socket.ServiceFileTailWatcher; +import org.dromara.jpom.system.LogbackConfig; +import org.dromara.jpom.util.DirTreeUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +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 javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * 系统日志管理 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@RestController +@RequestMapping(value = "system") +@Feature(cls = ClassFeature.SYSTEM_LOG) +@SystemPermission +public class LogManageController extends BaseServerController { + + + @RequestMapping(value = "log_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> logData(String machineId, HttpServletRequest request) { + IJsonMessage> message = this.tryRequestMachine(machineId, request, NodeUrl.SystemLog); + return Optional.ofNullable(message) + .orElseGet(() -> { + List data = DirTreeUtil.getTreeData(LogbackConfig.getPath()); + return JsonMessage.success("", data); + }); + } + + /** + * 删除 需要验证是否最后修改时间 + * + * @param path 路径 + * @return json + */ + @RequestMapping(value = "log_del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage logData(String machineId, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_path_error.f482") String path, + HttpServletRequest request) { + JsonMessage jsonMessage = this.tryRequestMachine(machineId, request, NodeUrl.DelSystemLog); + return Optional.ofNullable(jsonMessage).orElseGet(() -> { + File file = FileUtil.file(LogbackConfig.getPath(), path); + // 判断修改时间 + long modified = file.lastModified(); + Assert.state(System.currentTimeMillis() - modified > TimeUnit.DAYS.toMillis(1), I18nMessageUtil.get("i18n.cannot_delete_recent_logs.ee19")); + // 离线上一个日志 + ServiceFileTailWatcher.offlineFile(file); + if (FileUtil.del(file)) { + FileUtil.cleanEmpty(file.getParentFile()); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.delete_success.0007")); + } + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.delete_failure.acf0")); + }); + } + + + @RequestMapping(value = "log_download", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DOWNLOAD) + public void logDownload(String machineId, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_path_error.f482") String path, + HttpServletResponse response, + HttpServletRequest request) { + if (StrUtil.isNotEmpty(machineId)) { + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + NodeForward.requestDownload(model, request, response, NodeUrl.DownloadSystemLog); + return; + } + File file = FileUtil.file(LogbackConfig.getPath(), path); + if (file.isFile()) { + FileUtil.cleanEmpty(file.getParentFile()); + ServletUtil.write(response, file); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/OauthConfigController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/OauthConfigController.java new file mode 100644 index 0000000000..6739936391 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/OauthConfigController.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.lang.Tuple; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.dromara.jpom.oauth2.Oauth2Factory; +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.SystemParametersServer; +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; + +/** + * @author bwcx_jzy + * @since 2023/3/26 + */ +@RestController +@RequestMapping(value = "system/oauth-config") +@Feature(cls = ClassFeature.OAUTH_CONFIG) +@SystemPermission +public class OauthConfigController { + + private final SystemParametersServer systemParametersServer; + + public OauthConfigController(SystemParametersServer systemParametersServer) { + this.systemParametersServer = systemParametersServer; + } + + @GetMapping(value = "oauth2", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage oauth2(String provide) { + Tuple tuple = BaseOauth2Config.getDbKey(provide); + Assert.notNull(tuple, I18nMessageUtil.get("i18n.no_type.9153")); + BaseOauth2Config configDefNewInstance = systemParametersServer.getConfigDefNewInstance(tuple.get(0), tuple.get(1)); + return JsonMessage.success("", configDefNewInstance); + } + + @PostMapping(value = "oauth2-save", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage saveOauth2(HttpServletRequest request, String provide) { + Tuple tuple = BaseOauth2Config.getDbKey(provide); + Assert.notNull(tuple, I18nMessageUtil.get("i18n.no_type.9153")); + Class oauth2ConfigClass = tuple.get(1); + BaseOauth2Config oauth2Config = ServletUtil.toBean(request, oauth2ConfigClass, true); + Assert.notNull(tuple, I18nMessageUtil.get("i18n.no_type.9153")); + if (oauth2Config.enabled()) { + oauth2Config.check(); + } + systemParametersServer.upsert(tuple.get(0), oauth2Config, oauth2Config.provide()); + // + Oauth2Factory.put(oauth2Config); + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } +} 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 new file mode 100644 index 0000000000..8061b527e8 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.net.Ipv4Util; +import cn.hutool.core.net.MaskBit; +import cn.hutool.core.text.CharPool; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.StorageServiceFactory; +import org.dromara.jpom.model.data.SystemIpConfigModel; +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.SystemParametersServer; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.system.init.ProxySelectorConfig; +import org.springframework.beans.factory.config.YamlMapFactoryBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 系统配置 + * + * @author bwcx_jzy + * @since 2019/08/08 + */ +@RestController +@RequestMapping(value = "system") +@Feature(cls = ClassFeature.SYSTEM_CONFIG) +@SystemPermission +@Slf4j +public class SystemConfigController extends BaseServerController { + + private final SystemParametersServer systemParametersServer; + private final ProxySelectorConfig proxySelectorConfig; + private final DbExtConfig dbExtConfig; + + public SystemConfigController(SystemParametersServer systemParametersServer, + ProxySelectorConfig proxySelectorConfig, + DbExtConfig dbExtConfig) { + this.systemParametersServer = systemParametersServer; + this.proxySelectorConfig = proxySelectorConfig; + this.dbExtConfig = dbExtConfig; + } + + /** + * get server's config or node's config + * 加载服务端或者节点端配置 + * + * @param machineId 机器ID + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "config-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage configData(String machineId, HttpServletRequest request) { + IJsonMessage message = this.tryRequestMachine(machineId, request, NodeUrl.SystemGetConfig); + return Optional.ofNullable(message).orElseGet(() -> { + JSONObject jsonObject = new JSONObject(); + Resource resource = ExtConfigBean.getResource(); + try { + String content = IoUtil.read(resource.getInputStream(), CharsetUtil.CHARSET_UTF_8); + jsonObject.put("content", content); + jsonObject.put("file", FileUtil.getAbsolutePath(resource.getFile())); + return JsonMessage.success("", jsonObject); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + }); + } + + @PostMapping(value = "save_config.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission(superUser = true) + public IJsonMessage saveConfig(String machineId, String content, String restart, HttpServletRequest request) throws SQLException, IOException { + JsonMessage jsonMessage = this.tryRequestMachine(machineId, request, NodeUrl.SystemSaveConfig); + if (jsonMessage != null) { + return jsonMessage; + } + Assert.hasText(content, I18nMessageUtil.get("i18n.content_cannot_be_empty.9f0d")); + + ByteArrayResource byteArrayResource; + try { + YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); + // @author hjk 前端编辑器允许使用tab键,并设定为2个空格,再转换为yml时要把tab键换成2个空格 + byteArrayResource = new ByteArrayResource(content.replace("\t", " ").getBytes(StandardCharsets.UTF_8)); + yamlPropertySourceLoader.load("test", byteArrayResource); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.content_format_error.ce15"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.content_format_error_with_detail.c846") + e.getMessage()); + } + boolean restartBool = Convert.toBool(restart, false); + // 修改数据库密码 + YamlMapFactoryBean yamlMapFactoryBean = new YamlMapFactoryBean(); + yamlMapFactoryBean.setResources(byteArrayResource); + + Map yamlMap = yamlMapFactoryBean.getObject(); + ConfigurationProperties configurationProperties = DbExtConfig.class.getAnnotation(ConfigurationProperties.class); + Assert.notNull(configurationProperties, I18nMessageUtil.get("i18n.no_database_config_header_found.9ee3")); + Map dbYamlMap = BeanUtil.getProperty(yamlMap, configurationProperties.prefix()); + Assert.notNull(dbYamlMap, I18nMessageUtil.get("i18n.config_file_database_config_not_parsed.47b2")); + // 解析字段密码 + DbExtConfig dbExtConfig2 = BeanUtil.toBean(dbYamlMap, DbExtConfig.class, CopyOptions.create() + .setIgnoreError(true) + .setFieldNameEditor(s -> { + String camelCase = StrUtil.toCamelCase(s); + return StrUtil.toCamelCase(camelCase, CharPool.DASHED); + })); + Assert.hasText(dbExtConfig2.getUserName(), I18nMessageUtil.get("i18n.database_username_not_configured.a048")); + if (dbExtConfig2.getMode() == DbExtConfig.Mode.H2) { + String newDbExtConfigUserName = dbExtConfig2.userName(); + String newDbExtConfigUserPwd = dbExtConfig2.userPwd(); + String oldDbExtConfigUserName = dbExtConfig.userName(); + String oldDbExtConfigUserPwd = dbExtConfig.userPwd(); + if (!StrUtil.equals(oldDbExtConfigUserName, newDbExtConfigUserName) || !StrUtil.equals(oldDbExtConfigUserPwd, newDbExtConfigUserPwd)) { + // 执行修改数据库账号密码 + Assert.state(restartBool, I18nMessageUtil.get("i18n.modify_db_password_must_restart.d08d")); + StorageServiceFactory.get().alterUser(oldDbExtConfigUserName, newDbExtConfigUserName, newDbExtConfigUserPwd); + } + } + Resource resource = ExtConfigBean.getResource(); + Assert.state(resource.isFile(), I18nMessageUtil.get("i18n.configuration_modification_not_supported.5872")); + FileUtil.writeString(content, resource.getFile(), CharsetUtil.CHARSET_UTF_8); + + if (restartBool) { + // 重启 + JpomApplication.restart(); + return JsonMessage.success(Const.UPGRADE_MSG.get()); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + + /** + * 加载服务端的 ip 授权配置 + * + * @return json + */ + @RequestMapping(value = "ip-config-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.SYSTEM_CONFIG_IP, method = MethodFeature.LIST) + public IJsonMessage ipConfigData() { + SystemIpConfigModel config = systemParametersServer.getConfig(SystemIpConfigModel.ID, SystemIpConfigModel.class); + JSONObject jsonObject = new JSONObject(); + if (config != null) { + jsonObject.put("allowed", config.getAllowed()); + jsonObject.put("prohibited", config.getProhibited()); + } + //jsonObject.put("path", FileUtil.getAbsolutePath(systemIpConfigService.filePath())); + jsonObject.put("ip", getIp()); + return JsonMessage.success(I18nMessageUtil.get("i18n.load_success.154e"), jsonObject); + } + + @RequestMapping(value = "save_ip_config.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.SYSTEM_CONFIG_IP, method = MethodFeature.EDIT) + public IJsonMessage saveIpConfig(String allowed, String prohibited) { + SystemIpConfigModel systemIpConfigModel = new SystemIpConfigModel(); + String allowed1 = StrUtil.emptyToDefault(allowed, StrUtil.EMPTY); + this.checkIpV4(allowed1); + systemIpConfigModel.setAllowed(allowed1); + // + String prohibited1 = StrUtil.emptyToDefault(prohibited, StrUtil.EMPTY); + systemIpConfigModel.setProhibited(prohibited1); + this.checkIpV4(prohibited1); + systemParametersServer.upsert(SystemIpConfigModel.ID, systemIpConfigModel, SystemIpConfigModel.ID); + // + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 检查是否为 ipv4 + * + * @param ips ip + */ + private void checkIpV4(String ips) { + if (StrUtil.isEmpty(ips)) { + return; + } + String[] split = StrUtil.splitToArray(ips, StrUtil.LF); + for (String itemIp : split) { + itemIp = itemIp.trim(); + if (itemIp.startsWith("#")) { + continue; + } + if (StrUtil.equals(itemIp, "0.0.0.0")) { + // 开放所有 + continue; + } + if (StrUtil.contains(itemIp, Ipv4Util.IP_MASK_SPLIT_MARK)) { + String[] param = StrUtil.splitToArray(itemIp, Ipv4Util.IP_MASK_SPLIT_MARK); + Assert.state(Validator.isIpv4(param[0]), I18nMessageUtil.get("i18n.please_fill_in_ipv4_address.d23a") + itemIp); + int count1 = StrUtil.count(param[0], StrUtil.DOT); + int count2 = StrUtil.count(param[1], StrUtil.DOT); + if (count1 == 3 && count2 == 3) { + //192.168.1.0/192.168.1.200 + Assert.state(Validator.isIpv4(param[1]), I18nMessageUtil.get("i18n.please_fill_in_ipv4_address.d23a") + itemIp); + continue; + } + if (count1 == 3 && count2 == 0) { + //192.168.1.0/24 + int maskBit = Convert.toInt(param[1], 0); + String s = MaskBit.get(maskBit); + Assert.hasText(s, I18nMessageUtil.get("i18n.subnet_mask_incorrect.6c27") + itemIp); + continue; + } + } + boolean ipv4 = Validator.isIpv4(itemIp); + Assert.state(ipv4, I18nMessageUtil.get("i18n.please_fill_in_ipv4_address.d23a") + itemIp); + } + } + + + /** + * 加载代理配置 + * + * @return json + */ + @GetMapping(value = "get_proxy_config", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getPoxyConfig() { + JSONArray array = systemParametersServer.getConfigDefNewInstance(ProxySelectorConfig.KEY, JSONArray.class); + return JsonMessage.success("", array); + } + + /** + * 保存代理 + * + * @param proxys 参数 + * @return json + */ + @PostMapping(value = "save_proxy_config", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage saveProxyConfig(@RequestBody List proxys) { + proxys = ObjectUtil.defaultIfNull(proxys, Collections.emptyList()); + for (ProxySelectorConfig.ProxyConfigItem proxy : proxys) { + if (StrUtil.isNotEmpty(proxy.getProxyAddress())) { + machineNodeServer.testHttpProxy(proxy.getProxyAddress()); + } + } + systemParametersServer.upsert(ProxySelectorConfig.KEY, proxys, ProxySelectorConfig.KEY); + proxySelectorConfig.refreshCache(); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemExtConfigController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemExtConfigController.java new file mode 100644 index 0000000000..0262482c15 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemExtConfigController.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNode; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +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.system.ExtConfigBean; +import org.dromara.jpom.util.StringUtil; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +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 java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 配置文件管理 + * + * @author bwcx_jzy + * @since 2023/1/04 + */ +@RestController +@RequestMapping(value = "system/ext-conf") +@Feature(cls = ClassFeature.SYSTEM_EXT_CONFIG) +@SystemPermission +@Slf4j +public class SystemExtConfigController extends BaseServerController { + + /** + * 获取外部配置文件的 数据 + * + * @param parentMap 父级 map + * @return map + */ + private Map> listDir(Map> parentMap) { + File configResourceDir = ExtConfigBean.getConfigResourceDir(); + if (configResourceDir == null) { + return MapUtil.newHashMap(); + } + List files = FileUtil.loopFiles(configResourceDir); + return files.stream() + .filter(FileUtil::isFile) + .map(file -> { + String path = StringUtil.delStartPath(file, configResourceDir.getAbsolutePath(), true); + return this.buildItemTreeNode(path); + }) + .peek(node -> { + String id = node.getId(); + this.buildParent(parentMap, id); + // + Map extra = node.getExtra(); + extra.put("defaultConfig", false); + }).collect(Collectors.toMap(TreeNode::getId, node -> node)); + } + + /** + * 插件单个 node 对象 + * + * @param path 路径 + * @return tree node + */ + private TreeNode buildItemTreeNode(String path) { + + List list = StrUtil.splitTrim(path, StrUtil.SLASH); + int size = list.size(); + String parentId = size > 1 ? CollUtil.join(CollUtil.sub(list, 0, size - 1), StrUtil.SLASH) : StrUtil.SLASH; + + return new TreeNode<>(path, parentId, CollUtil.getLast(list), 0).setExtra(MapUtil.of("isLeaf", true)); + } + + @GetMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list() throws Exception { + Map> parentMap = new LinkedHashMap<>(10); + // root 节点 + parentMap.put(StrUtil.SLASH, new TreeNode<>(StrUtil.SLASH, null, I18nMessageUtil.get("i18n.root_path.1396"), 0)); + Map> listDir = this.listDir(parentMap); + // + PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = pathMatchingResourcePatternResolver.getResources("classpath*:/config_default/**"); + List> classPathList = Arrays.stream(resources) + .filter(resource -> { + if (resource.isFile()) { + // 本地运行可能出现文件夹的 item + try { + if (resource.getFile().isDirectory()) { + return false; + } + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + } + return true; + }) + .map(resource -> { + try { + String path = resource.getURL().getPath(); + if (StrUtil.endWith(path, StrUtil.SLASH)) { + // 目录 + return null; + } + String itemPath = StrUtil.subAfter(path, "/config_default/", false); + //log.debug("测试:{} {}", path, itemPath); + return this.buildItemTreeNode(itemPath); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + }) + .filter(Objects::nonNull) + .peek(node -> { + String id = node.getId(); + this.buildParent(parentMap, id); + // + node.setName(StrUtil.format(I18nMessageUtil.get("i18n.default_value.1aa9"), node.getName())); + Map extra = node.getExtra(); + extra.put("defaultConfig", true); + extra.put("hasDefault", true); + }) + // 过滤 dir 已经存在的 + .filter(node -> { + TreeNode treeNode = listDir.get(node.getId()); + if (treeNode != null) { + Map extra = treeNode.getExtra(); + extra.put("hasDefault", true); + return false; + } + return true; + }) + .collect(Collectors.toList()); + + List> allList = new ArrayList<>(); + allList.addAll(parentMap.values()); + allList.addAll(classPathList); + allList.addAll(listDir.values()); + // 过滤主配置文件 + allList = allList.stream().peek(node -> { + Map extra = node.getExtra(); + if (extra == null) { + return; + } + extra.put("disabled", StrUtil.equals(node.getId(), Const.FILE_NAME)); + }).collect(Collectors.toList()); + Tree stringTree = TreeUtil.buildSingle(allList, StrUtil.SLASH); + stringTree.setName(StrUtil.SLASH); + + return JsonMessage.success("", stringTree); + } + + @GetMapping(value = "get-item", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getItem(@ValidatorItem String name) { + InputStream resourceInputStream = ExtConfigBean.getConfigResourceInputStream(name); + String s = IoUtil.readUtf8(resourceInputStream); + return JsonMessage.success("", s); + } + + @PostMapping(value = "save-item", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage saveItem(@ValidatorItem String name, @ValidatorItem String content) { + File configResourceFile = ExtConfigBean.getConfigResourceFile(name); + Assert.notNull(configResourceFile, I18nMessageUtil.get("i18n.cannot_edit_corresponding_config_file.8d10")); + FileUtil.writeUtf8String(content, configResourceFile); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + @GetMapping(value = "get-default-item", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getDefaultItem(@ValidatorItem String name) { + InputStream resourceInputStream = ExtConfigBean.getDefaultConfigResourceInputStream(name); + String s = IoUtil.readUtf8(resourceInputStream); + return JsonMessage.success("", s); + } + + @GetMapping(value = "add-item", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage addItem(@ValidatorItem String name) { + boolean existConfigResource = ExtConfigBean.existConfigResource(name); + Assert.state(!existConfigResource, I18nMessageUtil.get("i18n.config_file_already_exists.c5fe")); + File resourceFile = ExtConfigBean.getConfigResourceFile(name); + Assert.notNull(resourceFile, I18nMessageUtil.get("i18n.cannot_create_config_file_in_environment.55bb")); + FileUtil.touch(resourceFile); + return JsonMessage.success(I18nMessageUtil.get("i18n.create_success.04a6")); + } + + private void buildParent(Map> parentMap, String path) { + List list = StrUtil.splitTrim(path, StrUtil.SLASH); + for (int i = 0; i < list.size() - 1; i++) { + String name = list.get(i); + String pathId = CollUtil.join(CollUtil.sub(list, 0, i + 1), StrUtil.SLASH); + String parentId = i > 0 ? CollUtil.join(CollUtil.sub(list, 0, i), StrUtil.SLASH) : StrUtil.SLASH; + parentMap.computeIfAbsent(pathId, s -> new TreeNode<>(s, parentId, name, 0)); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java new file mode 100644 index 0000000000..cf3c93fb28 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.*; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +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.dblog.BackupInfoService; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.context.ApplicationContext; +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 org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; +import java.util.Optional; + +/** + * 在线升级 + * + * @author bwcx_jzy + * @since 2019/7/22 + */ +@RestController +@RequestMapping(value = "system") +@Feature(cls = ClassFeature.SYSTEM_UPGRADE) +@SystemPermission(superUser = true) +@Slf4j +public class SystemUpdateController extends BaseServerController implements ILoadEvent { + + private static final String JOIN_JPOM_BETA_RELEASE = "JOIN_JPOM_BETA_RELEASE"; + + private final BackupInfoService backupInfoService; + private final ServerConfig serverConfig; + private final SystemParametersServer systemParametersServer; + + public SystemUpdateController(BackupInfoService backupInfoService, + ServerConfig serverConfig, + SystemParametersServer systemParametersServer) { + this.backupInfoService = backupInfoService; + this.serverConfig = serverConfig; + this.systemParametersServer = systemParametersServer; + } + + @PostMapping(value = "info", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage info(HttpServletRequest request, String machineId) { + IJsonMessage message = this.tryRequestMachine(machineId, request, NodeUrl.Info); + return Optional.ofNullable(message).orElseGet(() -> { + JpomManifest instance = JpomManifest.getInstance(); + cn.keepbx.jpom.RemoteVersion remoteVersion = RemoteVersion.cacheInfo(); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("manifest", instance); + jsonObject.put("remoteVersion", remoteVersion); + jsonObject.put("joinBetaRelease", RemoteVersion.betaRelease()); + return JsonMessage.success("", jsonObject); + }); + } + + @GetMapping(value = "change-beta-release", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage changeBetaRelease(String beta) { + boolean betaBool = this.changeBetaRelease2(beta); + RemoteVersion.loadRemoteInfo(); + String isBeta = I18nMessageUtil.get("i18n.joined_beta_program.c4e2"); + String closeBeta = I18nMessageUtil.get("i18n.close_beta_plan_success.5a94"); + return JsonMessage.success(betaBool ? isBeta : closeBeta); + } + + private boolean changeBetaRelease2(String beta) { + boolean betaBool = BooleanUtil.toBoolean(beta); + systemParametersServer.upsert(JOIN_JPOM_BETA_RELEASE, String.valueOf(betaBool), I18nMessageUtil.get("i18n.join_beta_program.5c1f")); + RemoteVersion.changeBetaRelease(String.valueOf(betaBool)); + return betaBool; + } + + /** + * 更新日志 + * + * @return changelog md + */ + @PostMapping(value = "change_log", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage changeLog(HttpServletRequest request, String machineId) { + boolean betaRelease = RemoteVersion.betaRelease(); + JsonMessage message = this.tryRequestMachine(machineId, request, NodeUrl.CHANGE_LOG, "beta", String.valueOf(betaRelease)); + if (message != null) { + return message; + } + // + + URL resource = ResourceUtil.getResource(betaRelease ? "CHANGELOG-BETA.md" : "CHANGELOG.md"); + String log = StrUtil.EMPTY; + if (resource != null) { + InputStream stream = URLUtil.getStream(resource); + log = IoUtil.readUtf8(stream); + } + return JsonMessage.success("", log); + } + + @PostMapping(value = "upload-jar-sharding", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE, log = false) + public IJsonMessage uploadJarSharding(MultipartFile file, + String machineId, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5) throws IOException { + MultipartHttpServletRequest multiRequest = getMultiRequest(); + if (StrUtil.isNotEmpty(machineId)) { + MachineNodeModel model = machineNodeServer.getByKey(machineId); + Assert.notNull(model, I18nMessageUtil.get("i18n.no_machine_found.c16c")); + return NodeForward.requestMultipart(model, multiRequest, NodeUrl.SystemUploadJar); + } + String absolutePath = serverConfig.getUserTempPath().getAbsolutePath(); + this.uploadSharding(file, absolutePath, sliceId, totalSlice, nowSlice, fileSumMd5, "jar", "zip"); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + @PostMapping(value = "upload-jar-sharding-merge", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage uploadJar(String sliceId, + Integer totalSlice, + String fileSumMd5, + HttpServletRequest request, + String machineId) throws IOException { + JsonMessage message = this.tryRequestMachine(machineId, request, NodeUrl.SystemUploadJarMerge); + if (message != null) { + // 判断-删除分片id + BaseServerController.SHARDING_IDS.remove(sliceId); + return message; + } + // + String absolutePath = serverConfig.getUserTempPath().getAbsolutePath(); + File successFile = this.shardingTryMerge(absolutePath, sliceId, totalSlice, fileSumMd5); + Objects.requireNonNull(JpomManifest.getScriptFile()); + String path = FileUtil.getAbsolutePath(successFile); + // 解析压缩包 + File file = JpomManifest.zipFileFind(path, Type.Server, absolutePath); + path = FileUtil.getAbsolutePath(file); + // 基础检查 + JsonMessage error = JpomManifest.checkJpomJar(path, Type.Server); + if (!error.success()) { + return new JsonMessage<>(error.getCode(), error.getMsg()); + } + Tuple data = error.getData(); + String version = data.get(0); + JpomManifest.releaseJar(path, version); + // + backupInfoService.autoBackup(); + // + JpomApplication.restart(); + return JsonMessage.success(Const.UPGRADE_MSG.get()); + } + + /** + * 检查是否存在新版本 + * + * @return json + * @see RemoteVersion + */ + @PostMapping(value = "check_version.json", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage checkVersion(HttpServletRequest request, + String machineId) { + IJsonMessage message = this.tryRequestMachine(machineId, request, NodeUrl.CHECK_VERSION); + return Optional.ofNullable(message).orElseGet(() -> { + cn.keepbx.jpom.RemoteVersion remoteVersion = RemoteVersion.loadRemoteInfo(); + return JsonMessage.success("", remoteVersion); + }); + } + + /** + * 远程下载升级 + * + * @return json + * @see RemoteVersion + */ + @GetMapping(value = "remote_upgrade.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DOWNLOAD) + public IJsonMessage upgrade(HttpServletRequest request, + String machineId) throws IOException { + + IJsonMessage message = this.tryRequestMachine(machineId, request, NodeUrl.REMOTE_UPGRADE); + return Optional.ofNullable(message).orElseGet(() -> { + try { + RemoteVersion.upgrade(JpomApplication.getInstance().getTempPath().getAbsolutePath(), objects -> backupInfoService.autoBackup()); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + return JsonMessage.success(Const.UPGRADE_MSG.get()); + }); + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + String config = systemParametersServer.getConfig(JOIN_JPOM_BETA_RELEASE, String.class); + boolean release2 = this.changeBetaRelease2(config); + log.debug("beta plan:{}", release2); + } +} 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 new file mode 100644 index 0000000000..d6186177a1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceController.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNode; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.func.system.model.ClusterInfoModel; +import org.dromara.jpom.func.system.service.ClusterInfoService; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.WorkspaceModel; +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.SystemParametersServer; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.service.user.UserBindWorkspaceService; +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; +import java.util.Optional; +import java.util.Set; + +/** + * @author bwcx_jzy + * @since 2021/12/3 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_WORKSPACE) +@RequestMapping(value = "/system/workspace/") +@SystemPermission +public class WorkspaceController extends BaseServerController { + + private final WorkspaceService workspaceService; + private final UserBindWorkspaceService userBindWorkspaceService; + private final SystemParametersServer systemParametersServer; + private final ClusterInfoService clusterInfoService; + + public WorkspaceController(WorkspaceService workspaceService, + UserBindWorkspaceService userBindWorkspaceService, + SystemParametersServer systemParametersServer, + ClusterInfoService clusterInfoService) { + this.workspaceService = workspaceService; + this.userBindWorkspaceService = userBindWorkspaceService; + this.systemParametersServer = systemParametersServer; + this.clusterInfoService = clusterInfoService; + } + + /** + * 编辑工作空间 + * + * @param name 工作空间名称 + * @param description 描述 + * @return json + */ + @PostMapping(value = "/edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage create(String id, + @ValidatorItem String name, + @ValidatorItem String description, + String group, + @ValidatorItem(msg = "i18n.select_cluster.f8c3") String clusterInfoId) { + // + ClusterInfoModel clusterInfoModel = clusterInfoService.getByKey(clusterInfoId); + Assert.notNull(clusterInfoModel, I18nMessageUtil.get("i18n.cluster_not_exist.4098")); + this.checkInfo(id, name); + // + WorkspaceModel workspaceModel = new WorkspaceModel(); + workspaceModel.setName(name); + workspaceModel.setDescription(description); + workspaceModel.setGroup(group); + workspaceModel.setClusterInfoId(clusterInfoModel.getId()); + if (StrUtil.isEmpty(id)) { + // 创建 + workspaceService.insert(workspaceModel); + } else { + workspaceModel.setId(id); + workspaceService.updateById(workspaceModel); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + private void checkInfo(String id, String name) { + Entity entity = Entity.create(); + entity.set("name", name); + if (StrUtil.isNotEmpty(id)) { + entity.set("id", StrUtil.format(" <> {}", id)); + } + boolean exists = workspaceService.exists(entity); + Assert.state(!exists, I18nMessageUtil.get("i18n.workspace_name_already_exists.0f82")); + } + + /** + * 工作空间分页列表 + * + * @return json + */ + @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto listPage = workspaceService.listPage(request); + return JsonMessage.success("", listPage); + } + + /** + * 查询所有的分组 + * + * @return list + */ + @GetMapping(value = "list-group-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listGroupAll() { + List listGroup = workspaceService.listGroup(); + return JsonMessage.success("", listGroup); + } + + /** + * 查询工作空间列表 + * + * @return json + */ + @GetMapping(value = "/list_all") + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listAll() { + List list = workspaceService.list(); + return JsonMessage.success("", list); + } + + /** + * 删除工作空间前检查 + * + * @param id 工作空间 ID + * @return json + */ + @GetMapping(value = "pre-check-delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission(superUser = true) + public IJsonMessage> preCheckDelete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id) { + // + Assert.state(!StrUtil.equals(id, Const.WORKSPACE_DEFAULT_ID), I18nMessageUtil.get("i18n.cannot_delete_default_workspace.0c06")); + // 判断是否存在关联数据 + Set> classes = BaseWorkspaceModel.allTableClass(); + + List> nodes = new ArrayList<>(classes.size()); + for (Class aClass : classes) { + TableName tableName = aClass.getAnnotation(TableName.class); + Class parents = tableName.parents(); + // + String parent = Optional.of(parents) + .map(aClass1 -> aClass1 != Void.class ? aClass1 : null) + .map(aClass1 -> { + TableName tableName1 = aClass1.getAnnotation(TableName.class); + return tableName1.value(); + }) + .orElse(StrUtil.EMPTY); + // + String sql = "select count(1) as cnt from " + tableName.value() + " where workspaceId=?"; + Number number = workspaceService.queryNumber(sql, id); + + TreeNode treeNode = new TreeNode<>(tableName.value(), parent, I18nMessageUtil.get(tableName.nameKey()), 0); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("workspaceBind", tableName.workspaceBind()); + jsonObject.put("count", ObjectUtil.defaultIfNull(number, Number::intValue, 0)); + treeNode.setExtra(jsonObject); + nodes.add(treeNode); + } + Tree stringTree = TreeUtil.buildSingle(nodes, StrUtil.EMPTY); + stringTree.setName(StrUtil.EMPTY); + return new JsonMessage<>(200, "", stringTree); + } + + /** + * 删除工作空间 + * + * @param id 工作空间 ID + * @return json + */ + @GetMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission(superUser = true) + public IJsonMessage delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id) { + // + Assert.state(!StrUtil.equals(id, Const.WORKSPACE_DEFAULT_ID), I18nMessageUtil.get("i18n.cannot_delete_default_workspace.0c06")); + // 判断是否存在关联数据 + Set> classes = BaseWorkspaceModel.allTableClass(); + + List> autoDeleteClass = new ArrayList<>(); + for (Class aClass : classes) { + TableName tableName = aClass.getAnnotation(TableName.class); + int workspaceBind = tableName.workspaceBind(); + if (workspaceBind == 2) { + // 先忽略不执行自动删除 + autoDeleteClass.add(aClass); + } else if (workspaceBind == 3) { + // 父级不存在自动删除 + Class parents = tableName.parents(); + Assert.state(parents != Void.class, I18nMessageUtil.get("i18n.table_info_configuration_error.b050")); + TableName tableName1 = parents.getAnnotation(TableName.class); + Assert.notNull(tableName1, I18nMessageUtil.get("i18n.parent_table_info_config_error.2f52") + aClass); + // + String sql = "select count(1) as cnt from " + tableName1.value() + " where workspaceId=?"; + int cnt = ObjectUtil.defaultIfNull(workspaceService.queryNumber(sql, id), Number::intValue, 0); + Assert.state(cnt <= 0, StrUtil.format(I18nMessageUtil.get("i18n.associated_data_and_exist_in_workspace.5fa7"), I18nMessageUtil.get(tableName.nameKey()), I18nMessageUtil.get(tableName1.nameKey()))); + // 等待自动删除 + autoDeleteClass.add(aClass); + } else { + // 其他严格检查的情况 + String sql = "select count(1) as cnt from " + tableName.value() + " where workspaceId=?"; + int cnt = ObjectUtil.defaultIfNull(workspaceService.queryNumber(sql, id), Number::intValue, 0); + Assert.state(cnt <= 0, I18nMessageUtil.get("i18n.associated_data_exists_in_workspace.8827") + I18nMessageUtil.get(tableName.nameKey())); + } + } + // 判断用户绑定关系 + boolean workspace = userBindWorkspaceService.existsWorkspace(id); + Assert.state(!workspace, I18nMessageUtil.get("i18n.user_or_group_bindings_exist_in_workspace.d57b")); + // 最后执行自动删除 + StringBuilder autoDelete = new StringBuilder(StrUtil.EMPTY); + for (Class aClass : autoDeleteClass) { + TableName tableName = aClass.getAnnotation(TableName.class); + // 自动删除 + String sql = "delete from " + tableName.value() + " where workspaceId=?"; + int execute = workspaceService.execute(sql, id); + if (execute > 0) { + autoDelete.append(StrUtil.format(I18nMessageUtil.get("i18n.auto_delete_data.ca62"), tableName.value(), execute)); + } + } + // 删除缓存 + String menusConfigKey = StrUtil.format("menus_config_{}", id); + systemParametersServer.delByKey(menusConfigKey); + String whitelistConfigKey = StrUtil.format("node_whitelist_{}", id); + systemParametersServer.delByKey(whitelistConfigKey); + systemParametersServer.delByKey(StrUtil.format("node_config_{}", id)); + // 删除信息 + workspaceService.delByKey(id); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.delete_success_with_colon.d44a") + autoDelete); + } + + /** + * 加载菜单配置 + * + * @return json + */ + @RequestMapping(value = "get_menus_config", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getMenusConfig(String workspaceId, HttpServletRequest request) { + WorkspaceModel workspaceModel = workspaceService.getByKey(workspaceId); + Assert.notNull(workspaceModel, I18nMessageUtil.get("i18n.workspace_not_exist.a6fd")); + JSONObject config = systemParametersServer.getConfigDefNewInstance(StrUtil.format("menus_config_{}", workspaceId), JSONObject.class); + //"classpath:/menus/index.json" + //"classpath:/menus/node-index.json" + String language = I18nMessageUtil.tryGetNormalLanguage(); + config.put("serverMenus", this.readMenusJson("classpath:/menus/" + language + "/index.json")); + return JsonMessage.success("", config); + } + + @PostMapping(value = "save_menus_config.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage saveMenusConfig(String serverMenuKeys, String nodeMenuKeys, String workspaceId) { + WorkspaceModel workspaceModel = workspaceService.getByKey(workspaceId); + Assert.notNull(workspaceModel, I18nMessageUtil.get("i18n.workspace_not_exist.a6fd")); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("nodeMenuKeys", StrUtil.splitTrim(nodeMenuKeys, StrUtil.COMMA)); + jsonObject.put("serverMenuKeys", StrUtil.splitTrim(serverMenuKeys, StrUtil.COMMA)); + String format = StrUtil.format("menus_config_{}", workspaceId); + systemParametersServer.upsert(format, jsonObject, format); + // + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + private JSONArray readMenusJson(String path) { + // 菜单 + InputStream inputStream = ResourceUtil.getStream(path); + String json = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); + return JSONArray.parseArray(json); + } +} 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 new file mode 100644 index 0000000000..7fbf813daa --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceEnvVarController.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.system; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +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.ServerConst; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.UrlRedirectUtil; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.data.WorkspaceEnvVarModel; +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.system.WorkspaceEnvVarService; +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; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2021/12/10 + */ + +@RestController +@Feature(cls = ClassFeature.SYSTEM_WORKSPACE_ENV) +@RequestMapping(value = "/system/workspace_env/") +public class WorkspaceEnvVarController extends BaseServerController { + + private final WorkspaceEnvVarService workspaceEnvVarService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public WorkspaceEnvVarController(WorkspaceEnvVarService workspaceEnvVarService, + TriggerTokenLogServer triggerTokenLogServer) { + this.workspaceEnvVarService = workspaceEnvVarService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto listPage = workspaceEnvVarService.listPage(request); + listPage.each(workspaceEnvVarModel -> { + Integer privacy = workspaceEnvVarModel.getPrivacy(); + if (privacy != null && privacy == 1) { + workspaceEnvVarModel.setValue(StrUtil.EMPTY); + } + }); + return JsonMessage.success("", listPage); + } + + /** + * 全部环境变量 + * + * @return json + */ + @PostMapping(value = "/all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> allList(HttpServletRequest request) { + List list = workspaceEnvVarService.listByWorkspace(request); + list.forEach(workspaceEnvVarModel -> { + Integer privacy = workspaceEnvVarModel.getPrivacy(); + if (privacy != null && privacy == 1) { + workspaceEnvVarModel.setValue(StrUtil.EMPTY); + } + }); + return JsonMessage.success("", list); + } + + /** + * 编辑变量 + * + * @param workspaceId 空间id + * @param name 变量名称 + * @param value 值 + * @param description 描述 + * @return json + */ + @PostMapping(value = "/edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(String id, + @ValidatorItem String workspaceId, + @ValidatorItem String name, + String value, + @ValidatorItem String description, + String privacy, + String nodeIds) { + if (!getUser().isSystemUser()) { + Assert.state(!StrUtil.equals(workspaceId, ServerConst.WORKSPACE_GLOBAL), I18nMessageUtil.get("i18n.global_workspace_variable_edit_in_system_management.58d2")); + } + + workspaceEnvVarService.checkUserWorkspace(workspaceId); + + this.checkInfo(id, name, workspaceId); + boolean privacyBool = BooleanUtil.toBoolean(privacy); + // + WorkspaceEnvVarModel workspaceModel = new WorkspaceEnvVarModel(); + workspaceModel.setName(name); + if (privacyBool) { + if (StrUtil.isNotEmpty(value)) { + workspaceModel.setValue(value); + } else { + // 隐私字段 创建必填 + Assert.state(StrUtil.isNotEmpty(id), I18nMessageUtil.get("i18n.parameter_value_required.3a29")); + } + } else { + // 非隐私必填 + Assert.hasText(value, I18nMessageUtil.get("i18n.parameter_value_required.3a29")); + workspaceModel.setValue(value); + } + workspaceModel.setWorkspaceId(workspaceId); + workspaceModel.setNodeIds(nodeIds); + workspaceModel.setDescription(description); + // + String oldNodeIds = null; + if (StrUtil.isEmpty(id)) { + // 创建 + workspaceModel.setPrivacy(privacyBool ? 1 : 0); + workspaceEnvVarService.insert(workspaceModel); + } else { + WorkspaceEnvVarModel byKey = workspaceEnvVarService.getByKey(id); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.no_corresponding_data.4703")); + Assert.state(StrUtil.equals(workspaceId, byKey.getWorkspaceId()), I18nMessageUtil.get("i18n.workspace_error_or_no_permission.7c8b")); + oldNodeIds = byKey.getNodeIds(); + workspaceModel.setId(id); + // 不能修改 + workspaceModel.setPrivacy(null); + workspaceEnvVarService.updateById(workspaceModel); + } + this.syncNodeEnvVar(workspaceModel, oldNodeIds); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + private void syncDelNodeEnvVar(String name, Collection delNode, String workspaceId) { + for (String s : delNode) { + NodeModel byKey = nodeService.getByKey(s); + Assert.state(StrUtil.equals(workspaceId, byKey.getWorkspaceId()), I18nMessageUtil.get("i18n.select_node_error.dc0f")); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", name); + JsonMessage jsonMessage = NodeForward.request(byKey, NodeUrl.Workspace_EnvVar_Delete, jsonObject); + Assert.state(jsonMessage.success(), StrUtil.format(I18nMessageUtil.get("i18n.handle_node_deletion_script_failure.071b"), byKey.getName(), jsonMessage.getMsg())); + } + } + + private void syncNodeEnvVar(WorkspaceEnvVarModel workspaceEnvVarModel, String oldNode) { + String workspaceId = workspaceEnvVarModel.getWorkspaceId(); + List newNodeIds = StrUtil.splitTrim(workspaceEnvVarModel.getNodeIds(), StrUtil.COMMA); + List oldNodeIds = StrUtil.splitTrim(oldNode, StrUtil.COMMA); + Collection delNode = CollUtil.subtract(oldNodeIds, newNodeIds); + // 删除 + this.syncDelNodeEnvVar(workspaceEnvVarModel.getName(), delNode, workspaceId); + // 更新 + for (String newNodeId : newNodeIds) { + NodeModel byKey = nodeService.getByKey(newNodeId); + Assert.state(StrUtil.equals(workspaceId, byKey.getWorkspaceId()), I18nMessageUtil.get("i18n.select_node_error.dc0f")); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("description", workspaceEnvVarModel.getDescription()); + jsonObject.put("name", workspaceEnvVarModel.getName()); + jsonObject.put("privacy", workspaceEnvVarModel.getPrivacy()); + if (StrUtil.isNotEmpty(workspaceEnvVarModel.getValue())) { + jsonObject.put("value", workspaceEnvVarModel.getValue()); + } else { + // 查询 + WorkspaceEnvVarModel byKeyExits = workspaceEnvVarService.getByKey(workspaceEnvVarModel.getId()); + jsonObject.put("value", byKeyExits.getValue()); + } + JsonMessage jsonMessage = NodeForward.request(byKey, NodeUrl.Workspace_EnvVar_Update, jsonObject); + Assert.state(jsonMessage.getCode() == 200, StrUtil.format(I18nMessageUtil.get("i18n.handle_node_sync_script_failure.e99f"), byKey.getName(), jsonMessage.getMsg())); + } + } + + private void checkInfo(String id, String name, String workspaceId) { + Validator.validateGeneral(name, 1, 50, I18nMessageUtil.get("i18n.variable_name_rules.480a")); + // + Entity entity = Entity.create(); + entity.set("name", name); + if (!StrUtil.equals(workspaceId, ServerConst.WORKSPACE_GLOBAL)) { + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + } + if (StrUtil.isNotEmpty(id)) { + entity.set("id", StrUtil.format(" <> {}", id)); + } + boolean exists = workspaceEnvVarService.exists(entity); + Assert.state(!exists, I18nMessageUtil.get("i18n.variable_name_already_exists.70f2")); + } + + + /** + * 删除变量 + * + * @param id 变量 ID + * @return json + */ + @GetMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id, + @ValidatorItem String workspaceId) { + if (!getUser().isSystemUser()) { + Assert.state(!StrUtil.equals(workspaceId, ServerConst.WORKSPACE_GLOBAL), I18nMessageUtil.get("i18n.global_workspace_variable_edit_in_system_management.58d2")); + } + workspaceEnvVarService.checkUserWorkspace(workspaceId); + WorkspaceEnvVarModel byKey = workspaceEnvVarService.getByKey(id); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.no_corresponding_data.4703")); + Assert.state(StrUtil.equals(workspaceId, byKey.getWorkspaceId()), I18nMessageUtil.get("i18n.select_workspace_error.426e")); + String oldNodeIds = byKey.getNodeIds(); + List delNode = StrUtil.splitTrim(oldNodeIds, StrUtil.COMMA); + this.syncDelNodeEnvVar(byKey.getName(), delNode, workspaceId); + // 删除信息 + workspaceEnvVarService.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @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, HttpServletRequest request) { + workspaceEnvVarService.checkUserWorkspace(workspaceId); + WorkspaceEnvVarModel item = workspaceEnvVarService.getByKey(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_environment_variable.c79f")); + Assert.state(StrUtil.equals(workspaceId, item.getWorkspaceId()), I18nMessageUtil.get("i18n.select_workspace_error.426e")); + // + Assert.state(ObjectUtil.defaultIfNull(item.getPrivacy(), -1) == 0, I18nMessageUtil.get("i18n.privacy_variable_cannot_trigger.dbc9")); + UserModel user = getUser(); + WorkspaceEnvVarModel updateInfo; + if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { + updateInfo = new WorkspaceEnvVarModel(); + updateInfo.setId(id); + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), workspaceEnvVarService.typeName(), + item.getId(), user.getId())); + workspaceEnvVarService.updateById(updateInfo); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + 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()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + Map map = new HashMap<>(10); + map.put("triggerUrl", FileUtil.normalize(triggerBuildUrl)); + + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } +} 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 new file mode 100644 index 0000000000..13a842b72b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserBasicInfoController.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.user; + +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +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; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.PermissionInterceptor; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.configuration.UserConfig; +import org.dromara.jpom.func.system.model.ClusterInfoModel; +import org.dromara.jpom.func.system.service.ClusterInfoService; +import org.dromara.jpom.func.user.model.UserLoginLogModel; +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.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.service.dblog.DbBuildHistoryLogService; +import org.dromara.jpom.service.dblog.DbUserOperateLogService; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.dromara.jpom.service.user.UserBindWorkspaceService; +import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.TwoFactorAuthUtils; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2019/8/10 + */ +@RestController +@RequestMapping(value = "/user") +@Slf4j +public class UserBasicInfoController extends BaseServerController { + + private static final TimedCache CACHE = new TimedCache<>(TimeUnit.MINUTES.toMillis(30)); + + private final SystemParametersServer systemParametersServer; + private final UserBindWorkspaceService userBindWorkspaceService; + private final UserService userService; + private final UserConfig userConfig; + private final UserLoginLogServer userLoginLogServer; + private final DbUserOperateLogService dbUserOperateLogService; + private final ClusterInfoService clusterInfoService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; + + public UserBasicInfoController(SystemParametersServer systemParametersServer, + UserBindWorkspaceService userBindWorkspaceService, + UserService userService, + ServerConfig serverConfig, + UserLoginLogServer userLoginLogServer, + DbUserOperateLogService dbUserOperateLogService, + ClusterInfoService clusterInfoService, + DbBuildHistoryLogService dbBuildHistoryLogService) { + this.systemParametersServer = systemParametersServer; + this.userBindWorkspaceService = userBindWorkspaceService; + this.userService = userService; + this.userConfig = serverConfig.getUser(); + this.userLoginLogServer = userLoginLogServer; + this.dbUserOperateLogService = dbUserOperateLogService; + this.clusterInfoService = clusterInfoService; + this.dbBuildHistoryLogService = dbBuildHistoryLogService; + } + + + /** + * get user basic info + * 获取管理员基本信息接口 + * + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "user-basic-info", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> getUserBasicInfo() { + UserModel userModel = getUser(); + userModel = userService.getByKey(userModel.getId(), false); + // return basic info + Map map = new HashMap<>(10); + 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()); + map.put("md5Token", userModel.getPassword()); + boolean bindMfa = userService.hasBindMfa(userModel.getId()); + map.put("bindMfa", bindMfa); + map.put("forceMfa", userConfig.isForceMfa()); + return JsonMessage.success("", map); + } + + @RequestMapping(value = "save_basicInfo.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage saveBasicInfo(String email, + String dingDing, String workWx, String code, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, range = "2:10", msg = "i18n.nickname_length_limit.6312") String name) { + UserModel user = getUser(); + UserModel userModel = userService.getByKey(user.getId()); + UserModel updateModel = new UserModel(user.getId()); + // 判断是否一样 + if (StrUtil.isNotEmpty(email) && !StrUtil.equals(email, userModel.getEmail())) { + Validator.validateEmail(email, I18nMessageUtil.get("i18n.invalid_email_format.7526")); + Integer cacheCode = CACHE.get(email); + if (cacheCode == null || !Objects.equals(cacheCode.toString(), code)) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.correct_verification_code2_required.df13")); + } + updateModel.setEmail(email); + } + + updateModel.setName(name); + // + if (StrUtil.isNotEmpty(dingDing) && !Validator.isUrl(dingDing)) { + Validator.validateMatchRegex(RegexPool.URL_HTTP, dingDing, I18nMessageUtil.get("i18n.correct_dingtalk_address_required.2b4a")); + } + updateModel.setDingDing(dingDing); + if (StrUtil.isNotEmpty(workWx)) { + Validator.validateMatchRegex(RegexPool.URL_HTTP, workWx, I18nMessageUtil.get("i18n.correct_enterprise_wechat_address_required.5f2d")); + } + updateModel.setWorkWx(workWx); + userService.updateById(updateModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 发送邮箱验证 + * + * @param email 邮箱 + * @return msg + */ + @RequestMapping(value = "sendCode.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage sendCode(@ValidatorItem(value = ValidatorRule.EMAIL, msg = "i18n.invalid_email_format.7526") String email) { + MailAccountModel config = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); + Assert.notNull(config, I18nMessageUtil.get("i18n.admin_email_not_configured.ecb8")); + int randomInt = RandomUtil.randomInt(1000, 9999); + try { + String title = I18nMessageUtil.get("i18n.jpom_verification_code.5b5b"); + EmailUtil.send(email, title, StrUtil.format(I18nMessageUtil.get("i18n.verification_code_is.5af5"), randomInt)); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_failed.9ca6"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.send_email_failure.1ab3") + e.getMessage()); + } + CACHE.put(email, randomInt); + return JsonMessage.success(I18nMessageUtil.get("i18n.send_success.9db9")); + } + + /** + * 查询用户自己的工作空间 + * + * @return msg + */ + @GetMapping(value = "my-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> myWorkspace() { + UserModel user = getUser(); + List userWorkspaceModels = userService.myWorkspace(user); + return JsonMessage.success("", userWorkspaceModels); + } + + /** + * 保存用户自己的工作空间 + * + * @return msg + */ + @PostMapping(value = "save-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage saveWorkspace(@RequestBody List workspaceModels) { + Assert.notEmpty(workspaceModels, I18nMessageUtil.get("i18n.no_workspace_selected.33d5")); + List collect = workspaceModels.stream() + .filter(workspaceModel -> StrUtil.isNotEmpty(workspaceModel.getId())) + .peek(userWorkspaceModel -> userWorkspaceModel.setOriginalName(null)) + .collect(Collectors.toList()); + UserModel user = getUser(); + Map map = CollStreamUtil.toMap(collect, UserWorkspaceModel::getId, workspaceModel -> workspaceModel); + String name = "user-my-workspace-" + user.getId(); + systemParametersServer.upsert(name, map, I18nMessageUtil.get("i18n.user_custom_workspace.ef93")); + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } + + /** + * 关闭自己到 mfa 相关信息 + * + * @return json + */ + @GetMapping(value = "close_mfa", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage closeMfa(@ValidatorItem String code) { + UserModel user = getUser(); + boolean mfaCode = userService.verifyMfaCode(user.getId(), code); + Assert.state(mfaCode, I18nMessageUtil.get("i18n.verification_code_incorrect.d8c0")); + UserModel userModel = new UserModel(user.getId()); + userModel.setTwoFactorAuthKey(StrUtil.EMPTY); + userService.updateById(userModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.close_success.8a31")); + } + + @GetMapping(value = "generate_mfa", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage generateMfa() { + UserModel user = getUser(); + JSONObject jsonObject = new JSONObject(); + String tfaKey = TwoFactorAuthUtils.generateTFAKey(); + jsonObject.put("mfaKey", tfaKey); + jsonObject.put("url", TwoFactorAuthUtils.generateOtpAuthUrl(user.getId(), tfaKey)); + return JsonMessage.success("", jsonObject); + } + + /** + * 绑定 mfa + * + * @param mfa mfa key + * @param twoCode 验证码 + * @return json + */ + @GetMapping(value = "bind_mfa", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage bindMfa(String mfa, String twoCode) { + // + UserModel user = getUser(); + boolean bindMfa = userService.hasBindMfa(user.getId()); + Assert.state(!bindMfa, I18nMessageUtil.get("i18n.account_already_bound_to_mfa.5122")); + // demo + Assert.state(!user.isDemoUser(), PermissionInterceptor.DEMO_TIP); + // + boolean tfaCode = TwoFactorAuthUtils.validateTFACode(mfa, twoCode); + Assert.state(tfaCode, I18nMessageUtil.get("i18n.mfa_incorrect_code.8783")); + userService.bindMfa(user.getId(), mfa); + return JsonMessage.success(I18nMessageUtil.get("i18n.binding_success.1974")); + } + + /** + * 登录日志列表 + * + * @return json + */ + @RequestMapping(value = "list-login-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> listLoginLogData(HttpServletRequest request) { + UserModel user = getUser(); + PageResultDto pageResult = userLoginLogServer.listPageByUserId(request, user.getId()); + return JsonMessage.success("", pageResult); + } + + /** + * 操作日志 + * + * @return json + */ + @RequestMapping(value = "list-operate-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + 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); + } + + /** + * 查询集群列表 + * + * @return json + */ + @GetMapping(value = "cluster-list") + public IJsonMessage clusterList() { + List list = clusterInfoService.list(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("list", list); + jsonObject.put("currentId", JpomManifest.getInstance().getInstallId()); + return JsonMessage.success("", jsonObject); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserInfoController.java new file mode 100644 index 0000000000..c80ef403ff --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserInfoController.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.user; + +import cn.hutool.core.util.ObjectUtil; +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.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.model.user.UserBindWorkspaceModel; +import org.dromara.jpom.model.user.UserModel; +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.GetMapping; +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.HttpSession; +import java.util.List; + +/** + * 用户管理 + * + * @author bwcx_jzy + * @since 2018/9/28 + */ +@RestController +@RequestMapping(value = "/user") +@Slf4j +public class UserInfoController extends BaseServerController { + + private final UserService userService; + private final UserBindWorkspaceService userBindWorkspaceService; + + public UserInfoController(UserService userService, + UserBindWorkspaceService userBindWorkspaceService) { + this.userService = userService; + this.userBindWorkspaceService = userBindWorkspaceService; + } + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + * @return json + */ + @RequestMapping(value = "updatePwd", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage updatePwd(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.password_cannot_be_empty.89b5") String oldPwd, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.password_cannot_be_empty.89b5") String newPwd, + HttpSession session) { + Assert.state(!StrUtil.equals(oldPwd, newPwd), I18nMessageUtil.get("i18n.old_and_new_passwords_match.55b4")); + UserModel userName = getUser(); + Assert.state(!userName.isDemoUser(), I18nMessageUtil.get("i18n.demo_account_password_change_not_supported.91f4")); + + UserModel userModel = userService.simpleLogin(userName.getId(), oldPwd); + Assert.notNull(userModel, I18nMessageUtil.get("i18n.old_password_incorrect.9cf6")); + Assert.state(ObjectUtil.defaultIfNull(userModel.getPwdErrorCount(), 0) <= 0, I18nMessageUtil.get("i18n.account_locked_cannot_change_password.d6ab")); + + userService.updatePwd(userName.getId(), newPwd); + // 如果修改成功,则销毁会话 + session.invalidate(); + return JsonMessage.success(I18nMessageUtil.get("i18n.password_change_success.8013")); + } + + /** + * 查询用户工作空间 + * + * @return json + */ + @GetMapping(value = "workspace_list", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> workspaceList(@ValidatorItem String userId) { + List workspaceModels = userBindWorkspaceService.listUserWorkspace(userId); + return JsonMessage.success("", workspaceModels); + } +} 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 new file mode 100644 index 0000000000..87c251e6a4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserListController.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +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.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +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.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; + +/** + * 用户列表 + * + * @author bwcx_jzy + */ +@RestController +@RequestMapping(value = "/user") +@Feature(cls = ClassFeature.USER) +@SystemPermission +public class UserListController extends BaseServerController { + + private final UserService userService; + private final UserBindWorkspaceService userBindWorkspaceService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public UserListController(UserService userService, + UserBindWorkspaceService userBindWorkspaceService, + TriggerTokenLogServer triggerTokenLogServer) { + this.userService = userService; + this.userBindWorkspaceService = userBindWorkspaceService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 查询所有用户 + * + * @return json + */ + @RequestMapping(value = "get_user_list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getUserList(HttpServletRequest request) { + PageResultDto userModelPageResultDto = userService.listPage(request); + userModelPageResultDto.each(userModel -> { + boolean bindMfa = userService.hasBindMfa(userModel.getId()); + if (bindMfa) { + userModel.setTwoFactorAuthKey("true"); + } + }); + return new JsonMessage<>(200, "", userModelPageResultDto); + } + + /** + * 获取所有管理员信息 + * get all admin user list + * + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "get_user_list_all", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getUserListAll() { + List list = userService.list(); + return JsonMessage.success("", list); + } + + /** + * 编辑用户 + * + * @param type 操作类型 + * @return String + */ + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage addUser(String type) { + // + boolean create = StrUtil.equals(type, "add"); + UserModel userModel = this.parseUser(create); + JSONObject result = new JSONObject(); + if (create) { + String randomPwd = RandomUtil.randomString(UserModel.SALT_LEN); + String sha1Pwd = SecureUtil.sha1(randomPwd); + userModel.setSalt(userService.generateSalt()); + userModel.setPassword(SecureUtil.sha1(sha1Pwd + userModel.getSalt())); + userModel.setSource("jpom"); + userService.insert(userModel); + result.put("randomPwd", randomPwd); + } else { + UserModel model = userService.getByKey(userModel.getId()); + Assert.notNull(model, I18nMessageUtil.get("i18n.user_not_exist.5387")); + boolean systemUser = userModel.isSystemUser(); + if (!systemUser) { + Assert.state(!model.isSuperSystemUser(), I18nMessageUtil.get("i18n.cannot_cancel_super_admin_permissions.99b5")); + } + if (model.isSuperSystemUser()) { + Assert.state(userModel.getStatus() == 1, I18nMessageUtil.get("i18n.cannot_disable_super_admin.6429")); + } + UserModel optUser = getUser(); + if (StrUtil.equals(model.getId(), optUser.getId())) { + Assert.state(optUser.isSuperSystemUser(), I18nMessageUtil.get("i18n.cannot_modify_own_info.4036")); + } + userService.updateById(userModel); + // 删除旧数据 + userBindWorkspaceService.deleteByUserId(userModel.getId()); + } + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313"), result); + } + + private UserModel parseUser(boolean create) { + String id = getParameter("id"); + boolean email = Validator.isEmail(id); + if (email) { + int length = id.length(); + Assert.state(length <= Const.ID_MAX_LEN && length >= UserModel.USER_NAME_MIN_LEN, StrUtil.format(I18nMessageUtil.get("i18n.login_name_email_format_length_range.25f3"), UserModel.USER_NAME_MIN_LEN, Const.ID_MAX_LEN)); + } else { + String checkId = StrUtil.replace(id, "-", "_"); + Validator.validateGeneral(checkId, UserModel.USER_NAME_MIN_LEN, Const.ID_MAX_LEN, StrUtil.format(I18nMessageUtil.get("i18n.login_name_format_incorrect.f789"), UserModel.USER_NAME_MIN_LEN, Const.ID_MAX_LEN)); + } + + Assert.state(!StrUtil.equalsAnyIgnoreCase(id, UserModel.SYSTEM_OCCUPY_NAME.get(), UserModel.SYSTEM_ADMIN), I18nMessageUtil.get("i18n.login_name_already_taken.5b46")); + + UserModel userModel = new UserModel(); + UserModel optUser = getUser(); + if (create) { + // 登录名重复 + boolean exists = userService.exists(new UserModel(id)); + Assert.state(!exists, I18nMessageUtil.get("i18n.login_name_already_exists.2511")); + userModel.setParent(optUser.getId()); + } + userModel.setId(id); + // + String name = getParameter("name"); + Assert.hasText(name, I18nMessageUtil.get("i18n.account_name_nickname_required.b757")); + int len = name.length(); + Assert.state(len <= 10 && len >= 2, I18nMessageUtil.get("i18n.nickname_length_limit.6312")); + + userModel.setName(name); + +// String password = getParameter("password"); +// if (create || StrUtil.isNotEmpty(password)) { +// Assert.hasText(password, "密码不能为空"); +// // 修改用户 +// Assert.state(create || optUser.isSystemUser(), "只有系统管理员才能重置用户密码"); +// userModel.setSalt(userService.generateSalt()); +// userModel.setPassword(SecureUtil.sha1(password + userModel.getSalt())); +// } + + int systemUser = getParameterInt("systemUser", 0); + userModel.setSystemUser(systemUser); + // + String permissionGroup = getParameter("permissionGroup"); + List permissionGroupList = StrUtil.split(permissionGroup, StrUtil.AT); + Assert.notEmpty(permissionGroupList, I18nMessageUtil.get("i18n.user_not_select_permission_group.1091")); + userModel.setPermissionGroup(CollUtil.join(permissionGroupList, StrUtil.AT, StrUtil.AT, StrUtil.AT)); + // + int status = getParameterInt("status", 1); + Assert.state(status == 0 || status == 1, I18nMessageUtil.get("i18n.selected_user_status_abnormal.efcf")); + userModel.setStatus(status); + return userModel; + } + + /** + * 删除用户 + * + * @param id 用户id + * @return String + */ + @RequestMapping(value = "deleteUser", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage deleteUser(String id) { + UserModel userName = getUser(); + Assert.state(!StrUtil.equals(userName.getId(), id), I18nMessageUtil.get("i18n.cannot_delete_self.fec9")); + + UserModel userModel = userService.getByKey(id); + Assert.notNull(userModel, I18nMessageUtil.get("i18n.illegal_access.c365")); + if (userModel.isSystemUser()) { + // 如果是系统管理员,判断个数 + Assert.state(userService.systemUserCount() > 1, I18nMessageUtil.get("i18n.admin_account_required.31e0")); + } + Assert.state(!userModel.isSuperSystemUser(), I18nMessageUtil.get("i18n.cannot_delete_super_admin.68e2")); + // 非系统管理员不支持删除演示账号 + Assert.state(!userModel.isRealDemoUser(), I18nMessageUtil.get("i18n.demo_account_not_support_delete.f9a6")); + userService.delByKey(id); + // 删除工作空间 + userBindWorkspaceService.deleteByUserId(id); + // + triggerTokenLogServer.delByUserId(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 解锁用户锁定状态 + * + * @param id id + * @return json + */ + @GetMapping(value = "unlock", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage unlock(@ValidatorItem String id) { + UserModel update = UserModel.unLock(id); + userService.updateById(update); + return JsonMessage.success(I18nMessageUtil.get("i18n.unlock_success.4cea")); + } + + /** + * 关闭用户 mfa + * + * @param id id + * @return json + */ + @GetMapping(value = "close_user_mfa", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + @SystemPermission(superUser = true) + public IJsonMessage closeMfa(@ValidatorItem String id) { + UserModel update = new UserModel(id); + update.setTwoFactorAuthKey(StrUtil.EMPTY); + userService.updateById(update); + return JsonMessage.success(I18nMessageUtil.get("i18n.close_success.8a31")); + } + + /** + * 重置用户密码 + * + * @param id id + * @return json + */ + @GetMapping(value = "rest-user-pwd", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage restUserPwd(@ValidatorItem String id) { + UserModel userModel = userService.getByKey(id); + Assert.notNull(userModel, I18nMessageUtil.get("i18n.account_does_not_exist.8402")); + Assert.state(!userModel.isSuperSystemUser(), I18nMessageUtil.get("i18n.super_admin_cannot_reset_password_this_way.0761")); + //不支持重置演示账号 + Assert.state(!userModel.isRealDemoUser(), I18nMessageUtil.get("i18n.demo_account_not_support_reset_password.a595")); + String randomPwd = RandomUtil.randomString(UserModel.SALT_LEN); + String sha1Pwd = SecureUtil.sha1(randomPwd); + userService.updatePwd(id, sha1Pwd); + // + JSONObject result = new JSONObject(); + result.put("randomPwd", randomPwd); + return JsonMessage.success(I18nMessageUtil.get("i18n.reset_success.faa3"), result); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserOptLogController.java b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserOptLogController.java new file mode 100644 index 0000000000..a2c09afb9d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserOptLogController.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.user; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.log.UserOperateLogV1; +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.dblog.DbUserOperateLogService; +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; + +/** + * 用户操作日志 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@RestController +@RequestMapping(value = "/user/log") +@Feature(cls = ClassFeature.USER_LOG) +@SystemPermission +public class UserOptLogController extends BaseServerController { + + private final DbUserOperateLogService dbUserOperateLogService; + + public UserOptLogController(DbUserOperateLogService dbUserOperateLogService) { + this.dbUserOperateLogService = dbUserOperateLogService; + } + + /** + * 展示用户列表 + * + * @return json + */ + @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResult = dbUserOperateLogService.listPage(request); + return JsonMessage.success("", pageResult); + } +} 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 new file mode 100644 index 0000000000..2da4ecef6b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserPermissionGroupController.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.UserPermissionGroupBean; +import org.dromara.jpom.oauth2.BaseOauth2Config; +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.SystemParametersServer; +import org.dromara.jpom.service.user.UserBindWorkspaceService; +import org.dromara.jpom.service.user.UserPermissionGroupServer; +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; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2022/8/3 + */ +@RestController +@RequestMapping(value = "/user-permission-group") +@Feature(cls = ClassFeature.USER_PERMISSION_GROUP) +@SystemPermission +public class UserPermissionGroupController extends BaseServerController { + + private final UserPermissionGroupServer userPermissionGroupServer; + private final UserBindWorkspaceService userBindWorkspaceService; + private final UserService userService; + private final SystemParametersServer systemParametersServer; + + public UserPermissionGroupController(UserPermissionGroupServer userPermissionGroupServer, + UserBindWorkspaceService userBindWorkspaceService, + UserService userService, + SystemParametersServer systemParametersServer) { + this.userPermissionGroupServer = userPermissionGroupServer; + this.userBindWorkspaceService = userBindWorkspaceService; + this.userService = userService; + this.systemParametersServer = systemParametersServer; + } + + /** + * 分页查询权限组 + * + * @return json + */ + @RequestMapping(value = "get-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getUserList(HttpServletRequest request) { + PageResultDto userModelPageResultDto = userPermissionGroupServer.listPage(request); + return new JsonMessage<>(200, "", userModelPageResultDto); + } + + /** + * 查询所有权限组 + * + * @return json + */ + @GetMapping(value = "get-list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> getListAll() { + List list = userPermissionGroupServer.list(); + return new JsonMessage<>(200, "", list); + } + + /** + * 编辑权限组 + * + * @return String + */ + @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) { + UserPermissionGroupBean userPermissionGroupBean = new UserPermissionGroupBean(); + userPermissionGroupBean.setName(name); + userPermissionGroupBean.setDescription(description); + // + userPermissionGroupBean.setProhibitExecute(this.resolveProhibitExecute(prohibitExecute)); + userPermissionGroupBean.setAllowExecute(this.resolveAllowExecute(allowExecute)); + if (StrUtil.isEmpty(id)) { + userPermissionGroupServer.insert(userPermissionGroupBean); + } else { + UserPermissionGroupBean permissionGroupBean = userPermissionGroupServer.getByKey(id); + Assert.notNull(permissionGroupBean, I18nMessageUtil.get("i18n.data_does_not_exist.b201")); + userPermissionGroupBean.setId(id); + userPermissionGroupServer.updateById(userPermissionGroupBean); + } + // + JSONArray jsonArray = JSONArray.parseArray(workspace); + List workspaceList = jsonArray.toJavaList(String.class); + userBindWorkspaceService.updateUserWorkspace(userPermissionGroupBean.getId(), workspaceList); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + private String resolveAllowExecute(String allowExecute) { + if (StrUtil.isEmpty(allowExecute)) { + return StrUtil.EMPTY; + } + JSONArray jsonArray = JSONArray.parseArray(allowExecute); + return JSON.toJSONString(jsonArray.stream().map(o -> { + JSONObject jsonObject = (JSONObject) o; + String startTime = jsonObject.getString("startTime"); + String endTime = jsonObject.getString("endTime"); + if (StrUtil.hasEmpty(startTime, endTime)) { + return null; + } + JSONArray week = jsonObject.getJSONArray("week"); + if (CollUtil.isEmpty(week)) { + return null; + } + int[] weeks = week.stream().mapToInt(value -> { + int week1 = Convert.toInt(value, 0); + Assert.state(week1 >= 1 && week1 <= 7, I18nMessageUtil.get("i18n.selected_weekday_incorrect.4cd4")); + return week1; + }).toArray(); + // + JSONObject result = new JSONObject(); + result.put("week", weeks); + result.put("startTime", DateUtil.parseTimeToday(startTime).toString("HH:mm:ss")); + result.put("endTime", DateUtil.parseTimeToday(endTime).toString("HH:mm:ss")); + return result; + }).filter(Objects::nonNull).collect(Collectors.toList())); + } + + private String resolveProhibitExecute(String prohibitExecute) { + if (StrUtil.isEmpty(prohibitExecute)) { + return StrUtil.EMPTY; + } + JSONArray jsonArray = JSONArray.parseArray(prohibitExecute); + return JSON.toJSONString(jsonArray.stream().map(o -> { + JSONObject jsonObject = (JSONObject) o; + String startTime = jsonObject.getString("startTime"); + String endTime = jsonObject.getString("endTime"); + if (StrUtil.hasEmpty(startTime, endTime)) { + return null; + } + JSONObject result = new JSONObject(); + result.put("startTime", DateUtil.parse(startTime).toString(DatePattern.NORM_DATETIME_FORMAT)); + result.put("endTime", DateUtil.parse(endTime).toString(DatePattern.NORM_DATETIME_FORMAT)); + result.put("reason", jsonObject.getString("reason")); + return result; + }).filter(Objects::nonNull).collect(Collectors.toList())); + } + + /** + * 删除 + * + * @param id 权限组 + * @return String + */ + @GetMapping(value = "delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delete(String id) { + UserPermissionGroupBean groupBean = userPermissionGroupServer.getByKey(id); + Assert.notNull(groupBean, I18nMessageUtil.get("i18n.data_does_not_exist.b201")); + // 判断是否绑定用户 + Entity entity = Entity.create(); + entity.set("permissionGroup", StrUtil.format(" like '%{}{}{}%'", StrUtil.AT, id, StrUtil.AT)); + long count = userService.count(entity); + Assert.state(count == 0, I18nMessageUtil.get("i18n.user_binding_warning.16b0")); + // 判断是否被 oauth2 绑定 + for (Map.Entry entry : BaseOauth2Config.DB_KEYS.entrySet()) { + Tuple value = entry.getValue(); + String dbKey = value.get(0); + BaseOauth2Config baseOauth2Config = systemParametersServer.getConfigDefNewInstance(dbKey, value.get(1)); + String permissionGroup = baseOauth2Config.getPermissionGroup(); + List permissionGroupList = StrUtil.split(permissionGroup, StrUtil.AT, true, true); + Assert.state(!CollUtil.contains(permissionGroupList, groupBean.getId()), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_binding_warning.d8f0"), baseOauth2Config.provide())); + } + // + userPermissionGroupServer.delByKey(id); + // 删除工作空间 + userBindWorkspaceService.deleteByUserId(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserWorkspaceModel.java b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserWorkspaceModel.java new file mode 100644 index 0000000000..def53c0b91 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserWorkspaceModel.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.user; + +import lombok.Data; +import org.dromara.jpom.func.system.model.ClusterInfoModel; + +/** + * 用户工作空间配置 + * + * @author bwcx_jzy + * @since 2023/3/15 + */ +@Data +public class UserWorkspaceModel { + + private String id; + + /** + * 用户自定义名 + */ + private String name; + /** + * 原始 + */ + private String originalName; + private String group; + /** + * 自定义排序规则 + */ + private Integer sort; + /** + * 集群Id + * + * @see ClusterInfoModel#getId() + */ + private String clusterInfoId; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/exception/AgentAuthorizeException.java b/modules/server/src/main/java/org/dromara/jpom/exception/AgentAuthorizeException.java new file mode 100644 index 0000000000..abc3561480 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/exception/AgentAuthorizeException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.exception; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; + +/** + * 授权错误 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +public class AgentAuthorizeException extends RuntimeException { + private final JsonMessage jsonMessage; + + public AgentAuthorizeException(JsonMessage jsonMessage) { + super(jsonMessage.getMsg()); + this.jsonMessage = jsonMessage; + } + + public IJsonMessage getJsonMessage() { + return jsonMessage; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/exception/AgentException.java b/modules/server/src/main/java/org/dromara/jpom/exception/AgentException.java new file mode 100644 index 0000000000..21381a994d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/exception/AgentException.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.exception; + +/** + * agent 插件端异常 + * + * @author bwcx_jzy + * @since 2019/4/17 + */ +public class AgentException extends RuntimeException { + + public AgentException(String message) { + super(message); + } + + public AgentException(String message, Throwable cause) { + super(message, cause); + } +} 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 0000000000..c4b5e2fc92 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/exception/PermissionException.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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/BaseGroupNameController.java b/modules/server/src/main/java/org/dromara/jpom/func/BaseGroupNameController.java new file mode 100644 index 0000000000..e09abb962d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/BaseGroupNameController.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.model.BaseGroupNameModel; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.Collection; + +/** + * @author bwcx_jzy + * @since 2023/2/25 + */ +public abstract class BaseGroupNameController extends BaseServerController { + + protected final BaseDbService dbService; + + protected BaseGroupNameController(BaseDbService dbService) { + this.dbService = dbService; + } + + + @GetMapping(value = "list-group", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listGroup() { + Collection list = dbService.listGroupName(); + return JsonMessage.success("", list); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/AssetsExecutorPoolService.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/AssetsExecutorPoolService.java new file mode 100644 index 0000000000..e201dc27c1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/AssetsExecutorPoolService.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets; + +import cn.hutool.core.thread.ExecutorBuilder; +import cn.hutool.core.util.RuntimeUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AssetsConfig; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ThreadPoolExecutor; + +@Service +@Slf4j +public class AssetsExecutorPoolService { + /** + * 监控线程池 + */ + private volatile ThreadPoolExecutor threadPoolExecutor; + + private final AssetsConfig assetsConfig; + + public AssetsExecutorPoolService(AssetsConfig assetsConfig) { + this.assetsConfig = assetsConfig; + } + + public void execute(Runnable command) { + this.createPool(); + threadPoolExecutor.execute(command); + } + + private void createPool() { + if (threadPoolExecutor == null) { + synchronized (AssetsExecutorPoolService.class) { + if (threadPoolExecutor == null) { + ExecutorBuilder executorBuilder = ExecutorBuilder.create(); + int poolSize = assetsConfig.getMonitorPoolSize(); + if (poolSize <= 0) { + // 获取 CPU 核心数 + poolSize = RuntimeUtil.getProcessorCount(); + } + executorBuilder.setCorePoolSize(poolSize).setMaxPoolSize(poolSize); + executorBuilder.useArrayBlockingQueue(Math.max(assetsConfig.getMonitorPoolWaitQueue(), 1)); + executorBuilder.setHandler(new ThreadPoolExecutor.DiscardPolicy() { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + log.warn(I18nMessageUtil.get("i18n.asset_monitoring_thread_pool_rejected_task.222e"), r.getClass()); + } + }); + threadPoolExecutor = executorBuilder.build(); + JpomApplication.register("assets-monitor", threadPoolExecutor); + } + } + } + } +} 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 new file mode 100644 index 0000000000..2faa1d7eaf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/BaseSshFileController.java @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.*; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.ssh.ChannelType; +import cn.hutool.extra.ssh.JschUtil; +import cn.hutool.extra.ssh.Sftp; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpException; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugins.JschUtils; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.CompressionFileUtil; +import org.dromara.jpom.util.StringUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; +import java.util.function.BiFunction; + +/** + * @author bwcx_jzy + * @since 2023/2/25 + */ +@Slf4j +public abstract class BaseSshFileController extends BaseServerController { + + @Resource + protected SshService sshService; + @Resource + protected MachineSshServer machineSshServer; + @Resource + private ServerConfig serverConfig; + + public interface ItemConfig { + /** + * 允许编辑的文件后缀 + * + * @return 文件后缀 + */ + List allowEditSuffix(); + + /** + * 允许管理的文件目录 + * + * @return 文件目录 + */ + List fileDirs(); + } + + /** + * 验证数据id 和目录合法性 + * + * @param id 数据id + * @param function 回调 + * @param 泛型 + * @return 处理后的数据 + */ + protected abstract T checkConfigPath(String id, BiFunction function); + + /** + * 验证数据id 和目录合法性 + * + * @param id 数据id + * @param allowPathParent 想要验证的目录 (授权) + * @param nextPath 授权后的二级路径 + * @param function 回调 + * @param 泛型 + * @return 处理后的数据 + */ + protected abstract T checkConfigPathChildren(String id, String allowPathParent, String nextPath, BiFunction function); + + @RequestMapping(value = "download", method = RequestMethod.GET) + @Feature(method = MethodFeature.DOWNLOAD) + public void download(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + @ValidatorItem String name, + HttpServletResponse response) throws IOException { + MachineSshModel machineSshModel = this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel1, itemConfig) -> machineSshModel1); + if (machineSshModel == null) { + ServletUtil.write(response, I18nMessageUtil.get("i18n.ssh_error_or_folder_not_configured.c087"), MediaType.TEXT_HTML_VALUE); + return; + } + try { + this.downloadFile(machineSshModel, allowPathParent, nextPath, name, response); + } catch (SftpException e) { + log.error(I18nMessageUtil.get("i18n.download_failed.65e2"), e); + ServletUtil.write(response, "download error", MediaType.TEXT_HTML_VALUE); + } + } + + /** + * 根据 id 获取 fileDirs 目录集合 + * + * @param id ssh id + * @return json + * @author Hotstrip + * @since for dev 3.x + */ + @RequestMapping(value = "root_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage rootFileList(@ValidatorItem String id) { + // + return this.checkConfigPath(id, (machineSshModel, itemConfig) -> { + JSONArray listDir = listRootDir(machineSshModel, itemConfig.fileDirs()); + return JsonMessage.success("", listDir); + }); + } + + + @RequestMapping(value = "list_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage listData(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath) { + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + try { + JSONArray listDir = listDir(machineSshModel, allowPathParent, nextPath, itemConfig); + return JsonMessage.success("", listDir); + } catch (SftpException e) { + throw Lombok.sneakyThrow(e); + } + }); + } + + @RequestMapping(value = "read_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage readFileData(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + @ValidatorItem String name) { + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + // + // + List allowEditSuffix = itemConfig.allowEditSuffix(); + Charset charset = AgentWhitelist.checkFileSuffix(allowEditSuffix, name); + // + String content = this.readFile(machineSshModel, allowPathParent, nextPath, name, charset); + return JsonMessage.success("", content); + }); + } + + @RequestMapping(value = "update_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage updateFileData(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + @ValidatorItem String name, + @ValidatorItem String content) { + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + // + List allowEditSuffix = itemConfig.allowEditSuffix(); + Charset charset = AgentWhitelist.checkFileSuffix(allowEditSuffix, name); + // 缓存到本地 + File file = FileUtil.file(serverConfig.getUserTempPath(), machineSshModel.getId(), allowPathParent, nextPath, name); + try { + FileUtil.writeString(content, file, charset); + // 上传 + this.syncFile(machineSshModel, allowPathParent, nextPath, name, file); + } finally { + // + FileUtil.del(file); + } + // + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + }); + } + + /** + * 读取文件 + * + * @param machineSshModel ssh + * @param allowPathParent 路径 + * @param nextPath 二级路径 + * @param name 文件 + * @param charset 编码格式 + */ + private String readFile(MachineSshModel machineSshModel, String allowPathParent, String nextPath, String name, Charset charset) { + Sftp sftp = null; + try { + Session session = sshService.getSessionByModel(machineSshModel); + sftp = new Sftp(session, machineSshModel.charset(), machineSshModel.timeout()); + String normalize = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + name); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + sftp.download(normalize, byteArrayOutputStream); + byte[] bytes = byteArrayOutputStream.toByteArray(); + return new String(bytes, charset); + } finally { + IoUtil.close(sftp); + } + } + + /** + * 上传文件 + * + * @param machineSshModel ssh + * @param allowPathParent 路径 + * @param nextPath 二级路径 + * @param name 文件 + * @param file 同步上传文件 + */ + private void syncFile(MachineSshModel machineSshModel, + String allowPathParent, + String nextPath, + String name, + File file) { + Sftp sftp = null; + try { + Session session = sshService.getSessionByModel(machineSshModel); + sftp = new Sftp(session, machineSshModel.charset(), machineSshModel.timeout()); + String normalize = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + name); + sftp.upload(normalize, file); + } finally { + IoUtil.close(sftp); + } + } + + /** + * 下载文件 + * + * @param machineSshModel ssh + * @param allowPathParent 路径 + * @param name 文件 + * @param response 响应 + * @throws IOException io + * @throws SftpException sftp + */ + private void downloadFile(MachineSshModel machineSshModel, String allowPathParent, String nextPath, String name, HttpServletResponse response) throws IOException, SftpException { + final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8); + String fileName = FileUtil.getName(name); + response.setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, Charset.forName(charset)))); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + Session session = null; + ChannelSftp channel = null; + try { + session = sshService.getSessionByModel(machineSshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + String normalize = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + name); + channel.get(normalize, response.getOutputStream()); + } finally { + JschUtil.close(channel); + JschUtil.close(session); + } + } + + /** + * 查询文件夹下所有文件 + * + * @param sshModel ssh + * @param allowPathParent 允许的路径 + * @param nextPath 下 N 级的文件夹 + * @return array + * @throws SftpException sftp + */ + @SuppressWarnings("unchecked") + private JSONArray listDir(MachineSshModel sshModel, String allowPathParent, String nextPath, ItemConfig itemConfig) throws SftpException { + Session session = null; + ChannelSftp channel = null; + List allowEditSuffix = itemConfig.allowEditSuffix(); + try { + session = sshService.getSessionByModel(sshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + + String children2 = StrUtil.emptyToDefault(nextPath, StrUtil.SLASH); + String allPath = StrUtil.format("{}/{}", allowPathParent, children2); + allPath = FileUtil.normalize(allPath); + JSONArray jsonArray = new JSONArray(); + Vector vector; + try { + vector = channel.ls(allPath); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.get_folder_failure.0fda"), e); + Throwable causedBy = ExceptionUtil.getCausedBy(e, SftpException.class); + if (causedBy != null) { + throw new IllegalStateException(I18nMessageUtil.get("i18n.query_folder_sftp_failed.9d35") + causedBy.getMessage()); + } + throw new IllegalStateException(I18nMessageUtil.get("i18n.query_folder_failed.3f0e") + e.getMessage()); + } + for (ChannelSftp.LsEntry lsEntry : vector) { + String filename = lsEntry.getFilename(); + if (StrUtil.DOT.equals(filename) || StrUtil.DOUBLE_DOT.equals(filename)) { + continue; + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", filename); + jsonObject.put("id", SecureUtil.sha1(allPath + StrUtil.SLASH + filename)); + SftpATTRS attrs = lsEntry.getAttrs(); + int mTime = attrs.getMTime(); + //String format = DateUtil.format(DateUtil.date(mTime * 1000L), DatePattern.NORM_DATETIME_MINUTE_PATTERN); + jsonObject.put("modifyTime", mTime * 1000L); + if (attrs.isDir()) { + jsonObject.put("dir", true); + } else { + long fileSize = attrs.getSize(); + jsonObject.put("size", fileSize); + // 允许编辑 + jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(allowEditSuffix, filename)); + } + String longname = lsEntry.getLongname(); + jsonObject.put("longname", longname); + jsonObject.put("link", attrs.isLink()); + jsonObject.put("extended", attrs.getExtended()); + jsonObject.put("permissions", attrs.getPermissionsString()); + jsonObject.put("allowPathParent", allowPathParent); + // + jsonObject.put("nextPath", FileUtil.normalize(children2)); +// jsonObject.put("absolutePath", FileUtil.normalize(StrUtil.format("{}/{}", nextPath, filename))); + jsonArray.add(jsonObject); + } + return jsonArray; + } finally { + JschUtil.close(channel); + JschUtil.close(session); + } + } + + /** + * 列出目前,判断是否存在 + * + * @param sshModel 数据信息 + * @param list 目录 + * @return Array + */ + private JSONArray listRootDir(MachineSshModel sshModel, List list) { + JSONArray jsonArray = new JSONArray(); + if (CollUtil.isEmpty(list)) { + return jsonArray; + } + Session session = null; + ChannelSftp channel = null; + try { + session = sshService.getSessionByModel(sshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + for (String allowPathParent : list) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", SecureUtil.sha1(allowPathParent)); + jsonObject.put("allowPathParent", allowPathParent); + try { + channel.ls(allowPathParent); + } catch (SftpException e) { + // 标记文件夹不存在 + jsonObject.put("error", true); + } + jsonArray.add(jsonObject); + } + return jsonArray; + } finally { + JschUtil.close(channel); + JschUtil.close(session); + } + } + + + @RequestMapping(value = "delete.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delete(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + String name) { + // name 可能为空,为空情况是删除目录 + String name2 = StrUtil.emptyToDefault(name, StrUtil.EMPTY); + Assert.state(!StrUtil.equals(name2, StrUtil.SLASH), I18nMessageUtil.get("i18n.cannot_delete_root_dir.fcdc")); + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + // + Session session = null; + Sftp sftp = null; + try { + // + String normalize = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + name2); + Assert.state(!StrUtil.equals(normalize, StrUtil.SLASH), I18nMessageUtil.get("i18n.cannot_delete_root_dir.fcdc")); + session = sshService.getSessionByModel(machineSshModel); + sftp = new Sftp(session, machineSshModel.charset(), machineSshModel.timeout()); + // 尝试删除 + boolean dirOrFile = this.tryDelDirOrFile(sftp, normalize); + if (dirOrFile) { + String parent = FileUtil.getParent(normalize, 1); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007"), parent); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.ssh_file_deletion_exception.5ba5"), e); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.delete_failure_with_colon.b429") + e.getMessage()); + } finally { + IoUtil.close(sftp); + JschUtil.close(session); + } + }); + } + + @RequestMapping(value = "rename.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage rename(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + @ValidatorItem String name, + @ValidatorItem String newname) { + + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + // + Session session = null; + ChannelSftp channel = null; + + try { + session = sshService.getSessionByModel(machineSshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + String oldPath = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + name); + String newPath = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + newname); + channel.rename(oldPath, newPath); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.ssh_rename_failed_exception.94aa"), e); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.rename_failed.0c76") + e.getMessage()); + } finally { + JschUtil.close(channel); + JschUtil.close(session); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + }); + } + + /** + * 删除文件 或者 文件夹 + * + * @param sftp ftp + * @param path 路径 + * @return true 删除的是 文件夹 + */ + private boolean tryDelDirOrFile(Sftp sftp, String path) { + try { + // 先尝试删除文件夹 + sftp.delDir(path); + return true; + } catch (Exception e) { + // 删除文件 + sftp.delFile(path); + } + return false; + } + + + @RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage upload(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + String unzip, + MultipartFile file) { + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + // + String remotePath = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath); + Session session = null; + ChannelSftp channel = null; + + try { + session = sshService.getSessionByModel(machineSshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + // 保存路径 + File tempPath = serverConfig.getUserTempPath(); + File savePath = FileUtil.file(tempPath, "ssh", machineSshModel.getId()); + FileUtil.mkdir(savePath); + String originalFilename = file.getOriginalFilename(); + File filePath = FileUtil.file(savePath, originalFilename); + // + if (Convert.toBool(unzip, false)) { + String extName = FileUtil.extName(originalFilename); + Assert.state(StrUtil.containsAnyIgnoreCase(extName, StringUtil.PACKAGE_EXT), I18nMessageUtil.get("i18n.file_type_not_supported2.d497") + extName); + file.transferTo(filePath); + // 解压 + File tempUnzipPath = FileUtil.file(savePath, IdUtil.fastSimpleUUID()); + try { + FileUtil.mkdir(tempUnzipPath); + CompressionFileUtil.unCompress(filePath, tempUnzipPath); + // 同步上传文件 + sshService.uploadDir(machineSshModel, remotePath, tempUnzipPath); + } finally { + // 删除临时文件 + CommandUtil.systemFastDel(filePath); + CommandUtil.systemFastDel(tempUnzipPath); + } + } else { + file.transferTo(filePath); + channel.cd(remotePath); + try (FileInputStream src = IoUtil.toStream(filePath)) { + channel.put(src, filePath.getName()); + } + } + + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.ssh_file_upload_exception.5c1c"), e); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.upload_failed.b019") + e.getMessage()); + } finally { + JschUtil.close(channel); + JschUtil.close(session); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + }); + } + + /** + * @return json + * @api {post} new_file_folder.json ssh 中创建文件夹/文件 + * @apiGroup ssh + * @apiUse defResultJson + * @apiParam {String} id ssh id + * @apiParam {String} path ssh 选择到目录 + * @apiParam {String} name 文件名 + * @apiParam {String} unFolder true/1 为文件夹,false/0 为文件 + * @apiSuccess {JSON} data + */ + @RequestMapping(value = "new_file_folder.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage newFileFolder(String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + @ValidatorItem String name, String unFolder) { + Assert.state(!StrUtil.contains(name, StrUtil.SLASH), I18nMessageUtil.get("i18n.file_name_error_message.7a25")); + return this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel, itemConfig) -> { + // + Session session = null; + try { + session = sshService.getSessionByModel(machineSshModel); + String remotePath = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + name); + Charset charset = machineSshModel.charset(); + int timeout = machineSshModel.timeout(); + try (Sftp sftp = new Sftp(session, charset, timeout)) { + if (sftp.exist(remotePath)) { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.folder_or_file_exists.c687")); + } + StringBuilder command = new StringBuilder(); + if (Convert.toBool(unFolder, false)) { + // 文件 + command.append("touch ").append(remotePath); + } else { + // 目录 + command.append("mkdir ").append(remotePath); + try { + if (sftp.mkdir(remotePath)) { + // 创建成功 + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.ssh_folder_creation_exception.6ed2"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.create_folder_failure.b632") + e.getMessage()); + } + } + List result = new ArrayList<>(); + JschUtils.execCallbackLine(session, charset, timeout, command.toString(), StrUtil.EMPTY, result::add); + + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded_with_details.c773") + CollUtil.join(result, StrUtil.LF)); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + } finally { + JschUtil.close(session); + } + }); + } + + /** + * 修改文件权限 + * + * @param id + * @param allowPathParent + * @param nextPath + * @param fileName + * @param permissionValue + * @return + */ + @RequestMapping(value = "change_file_permission.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage changeFilePermissions(@ValidatorItem String id, + @ValidatorItem String allowPathParent, + @ValidatorItem String nextPath, + @ValidatorItem String fileName, + @ValidatorItem String permissionValue) { + MachineSshModel machineSshModel = this.checkConfigPathChildren(id, allowPathParent, nextPath, (machineSshModel1, itemConfig) -> machineSshModel1); + if (machineSshModel == null) { + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.ssh_error_or_folder_not_configured.c087")); + } + Session session = sshService.getSessionByModel(machineSshModel); + Charset charset = machineSshModel.charset(); + int timeout = machineSshModel.timeout(); + String remotePath = FileUtil.normalize(allowPathParent + StrUtil.SLASH + nextPath + StrUtil.SLASH + fileName); + try (Sftp sftp = new Sftp(session, charset, timeout)) { + ChannelSftp client = sftp.getClient(); + // + int permissions = Integer.parseInt(permissionValue, 8); + client.chmod(permissions, remotePath); + } catch (SftpException e) { + log.error(I18nMessageUtil.get("i18n.ssh_modify_permission_error.0cd3"), remotePath, permissionValue, e); + return new JsonMessage<>(400, I18nMessageUtil.get("i18n.operation_failed.3d94") + e.getMessage()); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineDockerController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineDockerController.java new file mode 100644 index 0000000000..126362fd56 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineDockerController.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ZipUtil; +import cn.hutool.db.Entity; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.BaseGroupNameController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.func.cert.model.CertificateInfoModel; +import org.dromara.jpom.func.cert.service.CertificateInfoService; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.model.docker.DockerSwarmInfoMode; +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.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.system.ServerConfig; +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 org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.*; + +/** + * @author bwcx_jzy + * @since 2023/3/3 + */ +@RestController +@RequestMapping(value = "/system/assets/docker") +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@SystemPermission +@Slf4j +public class MachineDockerController extends BaseGroupNameController { + private final MachineDockerServer machineDockerServer; + private final ServerConfig serverConfig; + private final MachineSshServer machineSshServer; + private final DockerInfoService dockerInfoService; + private final DockerSwarmInfoService dockerSwarmInfoService; + private final WorkspaceService workspaceService; + private final CertificateInfoService certificateInfoService; + + public MachineDockerController(MachineSshServer machineSshServer, + MachineDockerServer machineDockerServer, + DockerInfoService dockerInfoService, + DockerSwarmInfoService dockerSwarmInfoService, + WorkspaceService workspaceService, + CertificateInfoService certificateInfoService, + ServerConfig serverConfig) { + super(machineDockerServer); + this.machineSshServer = machineSshServer; + this.machineDockerServer = machineDockerServer; + this.dockerInfoService = dockerInfoService; + this.dockerSwarmInfoService = dockerSwarmInfoService; + this.workspaceService = workspaceService; + this.certificateInfoService = certificateInfoService; + this.serverConfig = serverConfig; + } + + @PostMapping(value = "list-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listJson(HttpServletRequest request) { + PageResultDto pageResultDto = machineDockerServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(String id) throws Exception { + MachineDockerModel dockerInfoModel = this.takeOverModel(); + if (StrUtil.isEmpty(id)) { + // 创建 + this.check(dockerInfoModel); + // 默认正常 + dockerInfoModel.setStatus(1); + machineDockerServer.insert(dockerInfoModel); + } else { + this.check(dockerInfoModel); + machineDockerServer.updateById(dockerInfoModel); + } + // + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + + /** + * 接收前端参数 + * + * @return model + * @throws Exception 异常 + */ + private MachineDockerModel takeOverModel() throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + String name = getParameter("name"); + Assert.hasText(name, I18nMessageUtil.get("i18n.please_fill_in_name.52f3")); + String host = getParameter("host"); + String id = getParameter("id"); + String tlsVerifyStr = getParameter("tlsVerify"); + String registryUrl = getParameter("registryUrl"); + String registryUsername = getParameter("registryUsername"); + String registryPassword = getParameter("registryPassword"); + String registryEmail = getParameter("registryEmail"); + int heartbeatTimeout = getParameterInt("heartbeatTimeout", -1); + String groupName = getParameter("groupName"); + String certInfo = getParameter("certInfo"); + String enableSshStr = getParameter("enableSsh"); + boolean enableSsh = Convert.toBool(enableSshStr, false); + String machineSshId = getParameter("machineSshId"); + if (enableSsh) { + MachineSshModel model = machineSshServer.getByKey(machineSshId); + host = "ssh://" + model.getHost() + ":" + (model.getPort() == null ? 22 : model.getPort()); + } + boolean tlsVerify = Convert.toBool(tlsVerifyStr, false); + // + if (tlsVerify) { + File filePath = certificateInfoService.getFilePath(certInfo); + Assert.notNull(filePath, I18nMessageUtil.get("i18n.incorrect_certificate_info.aee1")); + // 验证证书文件是否正确 + boolean ok = (boolean) plugin.execute("certPath", "certPath", filePath.getAbsolutePath()); + Assert.state(ok, I18nMessageUtil.get("i18n.certificate_info_incorrect.a950")); + } else { + certInfo = StrUtil.emptyToDefault(certInfo, StrUtil.EMPTY); + } + boolean ok = (boolean) plugin.execute("host", "host", host); + Assert.state(ok, I18nMessageUtil.get("i18n.correct_host_required.8c49")); + // 验证重复 + Entity entity = Entity.create(); + entity.set("host", host); + if (StrUtil.isNotEmpty(id)) { + entity.set("id", StrUtil.format(" <> {}", id)); + } + boolean exists = machineDockerServer.exists(entity); + Assert.state(!exists, I18nMessageUtil.get("i18n.docker_already_exists.d9a5")); + // + MachineDockerModel machineDockerModel = new MachineDockerModel(); + machineDockerModel.setHeartbeatTimeout(heartbeatTimeout); + machineDockerModel.setHost(host); + machineDockerModel.setEnableSsh(enableSsh); + machineDockerModel.setMachineSshId(machineSshId); + // 保存是会验证证书一定存在 + machineDockerModel.setCertExist(tlsVerify); + machineDockerModel.setName(name); + machineDockerModel.setCertInfo(certInfo); + machineDockerModel.setTlsVerify(tlsVerify); + machineDockerModel.setRegistryUrl(registryUrl); + machineDockerModel.setRegistryUsername(registryUsername); + machineDockerModel.setRegistryPassword(registryPassword); + machineDockerModel.setRegistryEmail(registryEmail); + machineDockerModel.setGroupName(groupName); + // + machineDockerModel.setId(id); + return machineDockerModel; + } + + + private void check(MachineDockerModel dockerInfoModel) throws Exception { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + Map parameter = machineDockerServer.toParameter(dockerInfoModel); + parameter.put("closeBefore", true); + String errorReason = (String) plugin.execute("ping", parameter); + Assert.isNull(errorReason, () -> I18nMessageUtil.get("i18n.unable_to_connect_to_docker.2bb3") + errorReason); + // 检查授权 + String registryUrl = dockerInfoModel.getRegistryUrl(); + if (StrUtil.isNotEmpty(registryUrl)) { + MachineDockerModel oldInfoModel = machineDockerServer.getByKey(dockerInfoModel.getId(), false); + String registryPassword = Optional.ofNullable(dockerInfoModel.getRegistryPassword()).orElseGet(() -> Optional.ofNullable(oldInfoModel).map(MachineDockerModel::getRegistryPassword).orElse(null)); + Assert.hasText(registryPassword, I18nMessageUtil.get("i18n.repository_password_cannot_be_empty.20b3")); + parameter.put("closeBefore", true); + parameter.put("registryPassword", registryPassword); + try { + JSONObject jsonObject = (JSONObject) plugin.execute("testAuth", parameter); + log.info("{}", jsonObject); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.repository_authorization_error.4f50"), e); + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.incorrect_repository_credentials.f1c8") + e.getMessage()); + } + } + // 修改状态为在线 + dockerInfoModel.setStatus(1); + } + + @GetMapping(value = "try-local-docker", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage tryLocalDocker(HttpServletRequest request) { + try { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + String dockerHost = (String) plugin.execute("testLocal", new HashMap<>(1)); + Entity entity = Entity.create(); + entity.set("host", dockerHost); + boolean exists = machineDockerServer.exists(entity); + if (exists) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.local_docker_exists.ec31") + dockerHost); + } + MachineDockerModel dockerModel = new MachineDockerModel(); + dockerModel.setHost(dockerHost); + dockerModel.setName("localhost"); + dockerModel.setStatus(1); + machineDockerServer.insert(dockerModel); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.auto_detect_local_docker_and_add.af72") + dockerHost); + } catch (Throwable e) { + log.error(I18nMessageUtil.get("i18n.detect_local_docker_exception.ccfc"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.detect_local_docker_exception_with_details.7cc9") + e.getMessage()); + } + } + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id) throws Exception { + // + { + DockerInfoModel dockerInfoModel = new DockerInfoModel(); + dockerInfoModel.setMachineDockerId(id); + long count = dockerInfoService.count(dockerInfoModel); + Assert.state(count <= 0, StrUtil.format(I18nMessageUtil.get("i18n.docker_associated_workspaces_message.de78"), count)); + } + MachineDockerModel infoModel = machineDockerServer.getByKey(id); + Optional.ofNullable(infoModel).ifPresent(machineDockerModel -> { + if (StrUtil.isNotEmpty(machineDockerModel.getSwarmId())) { + // 判断集群 + DockerSwarmInfoMode dockerInfoModel = new DockerSwarmInfoMode(); + dockerInfoModel.setSwarmId(machineDockerModel.getSwarmId()); + long count = dockerSwarmInfoService.count(dockerInfoModel); + Assert.state(count <= 0, StrUtil.format(I18nMessageUtil.get("i18n.docker_cluster_associated_workspaces_message.5520"), count)); + } + }); + machineDockerServer.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + + /** + * 将 docker 分配到指定工作空间 + * + * @param ids docker id + * @param workspaceId 工作空间id + * @return json + */ + @PostMapping(value = "distribute", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage distribute(@ValidatorItem String ids, @ValidatorItem String workspaceId, String type) { + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String id : list) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker.733e")); + boolean exists = workspaceService.exists(new WorkspaceModel(workspaceId)); + Assert.state(exists, I18nMessageUtil.get("i18n.workspace_not_exist.a6fd")); + if (StrUtil.equals(type, "docker")) { + DockerInfoModel dockerInfoModel = new DockerInfoModel(); + dockerInfoModel.setMachineDockerId(id); + dockerInfoModel.setWorkspaceId(workspaceId); + // + exists = dockerInfoService.exists(dockerInfoModel); + if (!exists) { + // + dockerInfoModel.setName(machineDockerModel.getName()); + dockerInfoService.insert(dockerInfoModel); + } + } else if (StrUtil.equals(type, "swarm")) { + Assert.hasText(machineDockerModel.getSwarmId(), () -> StrUtil.format(I18nMessageUtil.get("i18n.current_docker_not_in_cluster.f70c"), machineDockerModel.getName())); + DockerSwarmInfoMode dockerInfoModel = new DockerSwarmInfoMode(); + dockerInfoModel.setSwarmId(machineDockerModel.getSwarmId()); + dockerInfoModel.setWorkspaceId(workspaceId); + // + if (!dockerSwarmInfoService.exists(dockerInfoModel)) { + // + dockerInfoModel.setName(machineDockerModel.getName()); + dockerSwarmInfoService.insert(dockerInfoModel); + } + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unknown_parameter.96dd")); + } + } + + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + @GetMapping(value = "list-workspace-docker", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage listWorkspaceSsh(@ValidatorItem String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker.733e")); + JSONObject jsonObject = new JSONObject(); + { + DockerInfoModel dockerInfoModel = new DockerInfoModel(); + dockerInfoModel.setMachineDockerId(id); + List modelList = dockerInfoService.listByBean(dockerInfoModel); + modelList = Optional.ofNullable(modelList).orElseGet(ArrayList::new); + for (DockerInfoModel model : modelList) { + model.setWorkspace(workspaceService.getByKey(model.getWorkspaceId())); + } + jsonObject.put("dockerList", modelList); + } + { + String swarmId = machineDockerModel.getSwarmId(); + if (StrUtil.isNotEmpty(swarmId)) { + DockerSwarmInfoMode dockerInfoModel = new DockerSwarmInfoMode(); + dockerInfoModel.setSwarmId(swarmId); + List modelList = dockerSwarmInfoService.listByBean(dockerInfoModel); + modelList = Optional.ofNullable(modelList).orElseGet(ArrayList::new); + for (DockerSwarmInfoMode model : modelList) { + model.setWorkspace(workspaceService.getByKey(model.getWorkspaceId())); + } + jsonObject.put("swarmList", modelList); + } + } + return JsonMessage.success("", jsonObject); + } + + + /** + * 导入 docker tls + * + * @param file 文件 + * @return model + * @throws Exception 异常 + */ + @PostMapping(value = "import-tls", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage importTls(MultipartFile file) throws Exception { + Assert.notNull(file, I18nMessageUtil.get("i18n.no_uploaded_file.07ef")); + // 保存路径 + File tempPath = FileUtil.file(serverConfig.getUserTempPath(), "docker", IdUtil.fastSimpleUUID()); + try { + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + Assert.state(StrUtil.containsIgnoreCase(extName, "zip"), I18nMessageUtil.get("i18n.invalid_file_type.7246")); + File saveFile = FileUtil.file(tempPath, originalFilename); + FileUtil.mkParentDirs(saveFile); + file.transferTo(saveFile); + // 解压 + ZipUtil.unzip(saveFile, tempPath); + // 先判断文件 + boolean checkCertPath = machineDockerServer.checkCertPath(tempPath.getAbsolutePath()); + Assert.state(checkCertPath, I18nMessageUtil.get("i18n.certificate_info_incorrect.a950")); + CertificateInfoModel certificateInfoModel = certificateInfoService.resolveX509(tempPath, true); + certificateInfoModel.setWorkspaceId(ServerConst.WORKSPACE_GLOBAL); + certificateInfoService.insert(certificateInfoModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.import_success.b6d1")); + } finally { + FileUtil.del(tempPath); + } + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineDockerSwarmController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineDockerSwarmController.java new file mode 100644 index 0000000000..715d592df0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineDockerSwarmController.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerSwarmInfoMode; +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.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +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 java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * @author bwcx_jzy + * @since 2023/3/4 + */ +@RestController +@RequestMapping(value = "/system/assets/docker") +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@SystemPermission +@Slf4j +public class MachineDockerSwarmController extends BaseServerController { + + private final MachineDockerServer machineDockerServer; + private final DockerSwarmInfoService dockerSwarmInfoService; + + public MachineDockerSwarmController(MachineDockerServer machineDockerServer, + DockerSwarmInfoService dockerSwarmInfoService) { + this.machineDockerServer = machineDockerServer; + this.dockerSwarmInfoService = dockerSwarmInfoService; + } + + @PostMapping(value = "init", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage init(@ValidatorItem String id) throws Exception { + MachineDockerModel dockerInfoModel1 = machineDockerServer.getByKey(id, false); + Assert.notNull(dockerInfoModel1, I18nMessageUtil.get("i18n.docker_does_not_exist.bb41")); + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map parameter = machineDockerServer.toParameter(dockerInfoModel1); + JSONObject data = (JSONObject) plugin.execute("tryInitializeSwarm", parameter); + + // + IPlugin pluginCheck = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + JSONObject info = pluginCheck.execute("info", parameter, JSONObject.class); + machineDockerServer.updateSwarmInfo(id, data, info); + // + return JsonMessage.success(I18nMessageUtil.get("i18n.cluster_created_successfully.6bf3")); + } + + /** + * 加入集群 + * + * @param id docker ID + * @param managerId 管理节点 + * @param remoteAddr 集群ID + * @param role 加入角色 + * @return json + * @throws Exception 异常 + */ + @PostMapping(value = "join", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage join(@ValidatorItem String managerId, + @ValidatorItem String id, + @ValidatorItem String remoteAddr, + @ValidatorItem String role) throws Exception { + MachineDockerModel managerSwarmDocker = machineDockerServer.getByKey(managerId, false); + Assert.notNull(managerSwarmDocker, I18nMessageUtil.get("i18n.docker_does_not_exist.bb41")); + MachineDockerModel joinSwarmDocker = machineDockerServer.getByKey(id, false); + Assert.notNull(joinSwarmDocker, I18nMessageUtil.get("i18n.docker_does_not_exist_with_code.689b")); + JSONObject managerSwarmInfo; + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + IPlugin checkPlugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + { + // 获取集群信息 + managerSwarmInfo = (JSONObject) plugin.execute("inSpectSwarm", machineDockerServer.toParameter(managerSwarmDocker)); + Assert.notNull(managerSwarmInfo, I18nMessageUtil.get("i18n.cluster_info_incomplete.84a1")); + } + { + // 检查节点存在的信息 + JSONObject info = checkPlugin.execute("info", machineDockerServer.toParameter(joinSwarmDocker), JSONObject.class); + Assert.notNull(info, I18nMessageUtil.get("i18n.get_docker_cluster_info_failure.c80d")); + JSONObject swarm = info.getJSONObject("swarm"); + Assert.notNull(swarm, I18nMessageUtil.get("i18n.get_docker_cluster_info_failure_with_code.fa77")); + JSONArray remoteManagers = swarm.getJSONArray("remoteManagers"); + if (ArrayUtil.isNotEmpty(remoteManagers)) { + Optional optional = remoteManagers.stream().filter(o -> { + JSONObject jsonObject = (JSONObject) o; + String addr = jsonObject.getString("addr"); + List strings = StrUtil.splitTrim(addr, StrUtil.COLON); + return StrUtil.equals(CollUtil.getFirst(strings), remoteAddr); + }).findFirst(); + Assert.state(optional.isPresent(), I18nMessageUtil.get("i18n.current_docker_already_in_other_cluster.e629")); + // 绑定数据 + machineDockerServer.updateSwarmInfo(joinSwarmDocker.getId(), managerSwarmInfo, info); + return JsonMessage.success(I18nMessageUtil.get("i18n.cluster_binding_success.eb7e")); + } + } + String roleToken; + { + // 准备加入集群 + JSONObject joinTokens = managerSwarmInfo.getJSONObject("joinTokens"); + Assert.notNull(joinTokens, I18nMessageUtil.get("i18n.cluster_info_incomplete_with_code.246b")); + roleToken = joinTokens.getString(role); + Assert.hasText(roleToken, StrUtil.format(I18nMessageUtil.get("i18n.cannot_join_cluster_as_role.01d4"), role)); + } + Map parameter = machineDockerServer.toParameter(joinSwarmDocker); + parameter.put("token", roleToken); + parameter.put("remoteAddrs", remoteAddr); + plugin.execute("joinSwarm", parameter); + // 绑定数据 + JSONObject info = checkPlugin.execute("info", machineDockerServer.toParameter(joinSwarmDocker), JSONObject.class); + machineDockerServer.updateSwarmInfo(joinSwarmDocker.getId(), managerSwarmInfo, info); + return JsonMessage.success(I18nMessageUtil.get("i18n.cluster_created_successfully.6bf3")); + } + + /** + * 强制退出集群 + * + * @param id 集群ID + * @return json + */ + @GetMapping(value = "leave-force", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage leaveForce(@ValidatorItem String id) throws Exception { + // + MachineDockerModel dockerInfoModel = machineDockerServer.getByKey(id, false); + Assert.notNull(dockerInfoModel, I18nMessageUtil.get("i18n.docker_not_exist.7ed8")); + Assert.hasText(dockerInfoModel.getSwarmId(), I18nMessageUtil.get("i18n.current_docker_has_no_cluster_info.0b52")); + { + MachineDockerModel where = new MachineDockerModel(); + where.setSwarmId(dockerInfoModel.getSwarmId()); + long allCount = machineDockerServer.count(where); + + where.setSwarmControlAvailable(true); + long controlCount = machineDockerServer.count(where); + if (controlCount == 1 && allCount > 1 && dockerInfoModel.isControlAvailable()) { + // 如果只有一个管理节点,并且还有多个节点, 并且当前操作的是管理节点 + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.multiple_worker_nodes_exist.7110")); + } + } + // + DockerSwarmInfoMode dockerSwarmInfoMode = new DockerSwarmInfoMode(); + dockerSwarmInfoMode.setSwarmId(dockerInfoModel.getSwarmId()); + long count = dockerSwarmInfoService.count(dockerSwarmInfoMode); + Assert.state(count <= 0, StrUtil.format(I18nMessageUtil.get("i18n.current_docker_cluster_still_associated_with_workspaces.b301"), count)); + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + Map parameter = machineDockerServer.toParameter(dockerInfoModel); + parameter.put("force", true); + plugin.execute("leaveSwarm", parameter, JSONObject.class); + // + MachineDockerModel update = new MachineDockerModel(); + update.setId(dockerInfoModel.getId()); + update.restSwarm(); + machineDockerServer.updateById(update); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.force_unbind_succeeded.5bfd")); + } + + /** + * 退出集群 + * + * @param id 集群ID + * @return json + */ + @GetMapping(value = "leave-node", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage leave(@ValidatorItem String id, @ValidatorItem String nodeId) throws Exception { + MachineDockerModel dockerModel = machineDockerServer.getByKey(id, false); + Assert.notNull(dockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker.733e")); + // 找到对应 docker 集群 node id 关联的数据 + MachineDockerModel find = new MachineDockerModel(); + find.setSwarmId(dockerModel.getSwarmId()); + find.setSwarmNodeId(nodeId); + List machineDockerModels1 = machineDockerServer.listByBean(find, false); + Assert.notEmpty(machineDockerModels1, I18nMessageUtil.get("i18n.cluster_node_not_in_system.0645")); + MachineDockerModel first = CollUtil.getFirst(machineDockerModels1); + // 找到管理节点 + MachineDockerModel machineDockerModel = new MachineDockerModel(); + machineDockerModel.setSwarmId(dockerModel.getSwarmId()); + machineDockerModel.setSwarmControlAvailable(true); + List machineDockerModels = machineDockerServer.listByBean(machineDockerModel, false); + Assert.notEmpty(machineDockerModels, I18nMessageUtil.get("i18n.manager_node_not_found.df04")); + Optional dockerModelOptional = machineDockerModels.stream() + .filter(machineDockerModel1 -> machineDockerModel1.getStatus() != null && machineDockerModel1.getStatus() == 1) + .findFirst(); + Assert.state(dockerModelOptional.isPresent(), I18nMessageUtil.get("i18n.no_online_manager_node_found.05d7")); + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + // + { + //节点强制退出 + Map parameter = machineDockerServer.toParameter(first); + parameter.put("force", true); + plugin.execute("leaveSwarm", parameter, JSONObject.class); + } + { + // 集群删除节点 + Map map = machineDockerServer.toParameter(dockerModelOptional.get()); + map.put("nodeId", nodeId); + plugin.execute("removeSwarmNode", map); + } + // + MachineDockerModel update = new MachineDockerModel(); + update.setId(first.getId()); + update.restSwarm(); + machineDockerServer.updateById(update); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.exclusion_success.7d46")); + } +} 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 new file mode 100644 index 0000000000..8f8081ebad --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineNodeController.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.BaseGroupNameController; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +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; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 机器节点 + * + * @author bwcx_jzy + * @since 2023/2/18 + */ +@RestController +@RequestMapping(value = "/system/assets/machine") +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE) +@SystemPermission +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, + 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) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listJson(HttpServletRequest request) { + PageResultDto pageResultDto = machineNodeServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + @GetMapping(value = "search", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> search(String name, String appendIds, int limit) { + Entity entity = new Entity(); + if (StrUtil.isNotEmpty(name)) { + entity.set("name", StrUtil.format(" like '%{}%'", name)); + } + limit = Math.max(limit, 1); + List appendIdList = StrUtil.splitTrim(appendIds, StrUtil.COMMA); + List machineNodeModels = machineNodeServer.queryList(entity, limit, machineNodeServer.defaultOrders()); + appendIdList = appendIdList.stream() + .filter(s -> machineNodeModels.stream() + .noneMatch(machineNodeModel -> StrUtil.equals(s, machineNodeModel.getId()))) + .collect(Collectors.toList()); + for (String s : appendIdList) { + MachineNodeModel nodeModel = machineNodeServer.getByKey(s); + if (nodeModel == null) { + continue; + } + machineNodeModels.add(nodeModel); + } + return JsonMessage.success("", machineNodeModels); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(HttpServletRequest request) { + machineNodeServer.update(request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + @PostMapping(value = "delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delete(@ValidatorItem String id) { + long count = nodeService.countByMachine(id); + Assert.state(count <= 0, StrUtil.format(I18nMessageUtil.get("i18n.associated_nodes_warning.64d8"), count)); + machineNodeServer.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 将机器分配到指定工作空间 + * + * @param ids 机器id + * @param workspaceId 工作空间id + * @return json + */ + @PostMapping(value = "distribute", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage distribute(@ValidatorItem String ids, @ValidatorItem String workspaceId) { + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String id : list) { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(id); + Assert.notNull(machineNodeModel, I18nMessageUtil.get("i18n.no_machine.89ed")); + WorkspaceModel workspaceModel = new WorkspaceModel(workspaceId); + boolean exists = workspaceService.exists(workspaceModel); + Assert.state(exists, I18nMessageUtil.get("i18n.workspace_not_exist.a6fd")); + // + if (!nodeService.existsNode2(workspaceId, id)) { + // + machineNodeServer.insertNode(machineNodeModel, workspaceId); + } + } + + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + @GetMapping(value = "list-node", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listData(@ValidatorItem String id) { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(id); + Assert.notNull(machineNodeModel, I18nMessageUtil.get("i18n.no_machine.89ed")); + NodeModel nodeModel = new NodeModel(); + nodeModel.setMachineId(id); + List modelList = nodeService.listByBean(nodeModel); + modelList = Optional.ofNullable(modelList).orElseGet(ArrayList::new); + for (NodeModel model : modelList) { + model.setWorkspace(workspaceService.getByKey(model.getWorkspaceId())); + } + return JsonMessage.success("", modelList); + } + + /** + * 查询模板节点 + * + * @return list + */ + @GetMapping(value = "list-template-node", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listTemplate() { + MachineNodeModel machineNodeModel = new MachineNodeModel(); + machineNodeModel.setTemplateNode(true); + List modelList = machineNodeServer.listByBean(machineNodeModel); + return JsonMessage.success("", modelList); + } + + + /** + * 保存授权配置 + * + * @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 = "i18n.distribution_machine_required.5921") String ids, + HttpServletRequest request) { + // + List idList = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String s : idList) { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(s); + Assert.notNull(machineNodeModel, I18nMessageUtil.get("i18n.no_machine.89ed")); + JsonMessage jsonMessage = NodeForward.request(machineNodeModel, request, NodeUrl.WhitelistDirectory_Submit); + Assert.state(jsonMessage.success(), StrUtil.format(I18nMessageUtil.get("i18n.distribute_node_authorization_failure.bb92"), machineNodeModel.getName(), jsonMessage.getMsg())); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } + + + @PostMapping(value = "save-node-config", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.SYSTEM_CONFIG, method = MethodFeature.EDIT) + @SystemPermission(superUser = true) + public IJsonMessage saveNodeConfig(@ValidatorItem(msg = "i18n.distribution_machine_required.5921") String ids, + String content, + String restart) { + List idList = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String s : idList) { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(s); + Assert.notNull(machineNodeModel, I18nMessageUtil.get("i18n.no_machine.89ed")); + JSONObject reqData = new JSONObject(); + reqData.put("content", content); + reqData.put("restart", restart); + JsonMessage jsonMessage = NodeForward.request(machineNodeModel, NodeUrl.SystemSaveConfig, reqData); + Assert.state(jsonMessage.success(), StrUtil.format(I18nMessageUtil.get("i18n.distribute_node_configuration_failure.8146"), machineNodeModel.getName(), jsonMessage.getMsg())); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 查询集群孤独的数据 + * + * @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, I18nMessageUtil.get("i18n.no_machine.89ed")); + 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, I18nMessageUtil.get("i18n.no_machine.89ed")); + { + NodeModel nodeModel = nodeService.getByKey(toNodeId); + Assert.notNull(nodeModel, I18nMessageUtil.get("i18n.no_node.2e83")); + Assert.hasText(nodeModel.getWorkspaceId(), I18nMessageUtil.get("i18n.node_has_no_workspace.69c0")); + Assert.state(StrUtil.equals(nodeModel.getMachineId(), machineNodeModel.getId()), I18nMessageUtil.get("i18n.asset_cluster_and_node_mismatch.8964")); + 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(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + 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, I18nMessageUtil.get("i18n.correction_data_failure.dac6") + 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(I18nMessageUtil.get("i18n.correction_success.38bc")); + } + + @GetMapping(value = "monitor-config", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage monitorConfig(HttpServletRequest request, String id) { + IJsonMessage message = this.tryRequestMachine(id, request, NodeUrl.Info); + Assert.notNull(message, I18nMessageUtil.get("i18n.no_asset_machine.c77c")); + Assert.state(message.success(), message.getMsg()); + JSONObject data = message.getData(); + JSONObject monitor = Optional.ofNullable(data).map(jsonObject -> jsonObject.getJSONObject("monitor")).orElse(null); + return JsonMessage.success("", monitor); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineSshController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineSshController.java new file mode 100644 index 0000000000..b80343c77e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineSshController.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.CharsetDetector; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.text.StrSplitter; +import cn.hutool.core.text.csv.*; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.ssh.JschUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.PermissionInterceptor; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.configuration.AssetsConfig; +import org.dromara.jpom.dialect.DialectUtil; +import org.dromara.jpom.func.BaseGroupNameController; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.log.SshTerminalExecuteLog; +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.dblog.SshTerminalExecuteLogService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.system.ServerConfig; +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 org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Function; + +/** + * @author bwcx_jzy + * @since 2023/2/25 + */ +@RestController +@RequestMapping(value = "/system/assets/ssh") +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_SSH) +@SystemPermission +@Slf4j +public class MachineSshController extends BaseGroupNameController { + + private final MachineSshServer machineSshServer; + private final SshService sshService; + private final SshTerminalExecuteLogService sshTerminalExecuteLogService; + private final WorkspaceService workspaceService; + private final ServerConfig serverConfig; + private final AssetsConfig.SshConfig sshConfig; + + public MachineSshController(MachineSshServer machineSshServer, + SshService sshService, + SshTerminalExecuteLogService sshTerminalExecuteLogService, + WorkspaceService workspaceService, + ServerConfig serverConfig, + AssetsConfig assetsConfig) { + super(machineSshServer); + this.machineSshServer = machineSshServer; + this.sshService = sshService; + this.sshTerminalExecuteLogService = sshTerminalExecuteLogService; + this.workspaceService = workspaceService; + this.serverConfig = serverConfig; + this.sshConfig = assetsConfig.getSsh(); + } + + @PostMapping(value = "list-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listJson(HttpServletRequest request) { + PageResultDto pageResultDto = machineSshServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + + @Override + @GetMapping(value = "list-group", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listGroup() { + Collection list = dbService.listGroupName(); + // 合并配置禁用分组 + List monitorGroupName = sshConfig.getDisableMonitorGroupName(); + if (monitorGroupName != null) { + list.addAll(monitorGroupName); + // + list.remove("*"); + list = new HashSet<>(list); + } + return JsonMessage.success("", list); + } + + /** + * 编辑 + * + * @param name 名称 + * @param host 端口 + * @param user 用户名 + * @param password 密码 + * @param connectType 连接方式 + * @param privateKey 私钥 + * @param port 端口 + * @param charset 编码格式 + * @param id ID + * @return json + */ + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_ssh_name_cannot_be_empty.ff4f") String name, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.host_cannot_be_empty.644a") String host, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.parameter_error_user_cannot_be_empty.9239") String user, + String password, + MachineSshModel.ConnectType connectType, + String privateKey, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.parameter_error_port_error.810d") int port, + String charset, + String id, + Integer timeout, + String allowEditSuffix, + String groupName) { + boolean add = StrUtil.isEmpty(id); + if (add) { + // 优先判断参数 如果是 password 在修改时可以不填写 + if (connectType == MachineSshModel.ConnectType.PASS) { + Assert.hasText(password, I18nMessageUtil.get("i18n.login_password_required.9605")); + } else if (connectType == MachineSshModel.ConnectType.PUBKEY) { + //Assert.hasText(privateKey, "请填写证书内容"); + } + } else { + boolean exists = machineSshServer.exists(new MachineSshModel(id)); + Assert.state(exists, I18nMessageUtil.get("i18n.ssh_not_exist.2e40")); + } + MachineSshModel sshModel = new MachineSshModel(); + sshModel.setId(id); + sshModel.setGroupName(groupName); + sshModel.setHost(host); + // 如果密码传递不为空就设置值 因为上面已经判断了只有修改的情况下 password 才可能为空 + Opt.ofBlankAble(password).ifPresent(sshModel::setPassword); + if (StrUtil.startWith(privateKey, URLUtil.FILE_URL_PREFIX)) { + String rsaPath = StrUtil.removePrefix(privateKey, URLUtil.FILE_URL_PREFIX); + Assert.state(FileUtil.isFile(rsaPath), I18nMessageUtil.get("i18n.private_key_file_not_exist.49ed")); + } + Opt.ofNullable(privateKey).ifPresent(sshModel::setPrivateKey); + + // 获取允许编辑的后缀 + List allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, I18nMessageUtil.get("i18n.suffix_cannot_be_empty.ec72")); + sshModel.allowEditSuffix(allowEditSuffixList); + sshModel.setPort(port); + sshModel.setUser(user); + sshModel.setName(name); + sshModel.setConnectType(connectType.name()); + sshModel.setTimeout(timeout); + try { + Charset.forName(charset); + sshModel.setCharset(charset); + } catch (Exception e) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.correct_encoding_format_required.1f7f") + e.getMessage()); + } + // 判断重复 + Entity entity = Entity.create(); + entity.set("host", sshModel.getHost()); + entity.set("port", sshModel.getPort()); + entity.set(DialectUtil.wrapField("user"), sshModel.getUser()); + entity.set("connectType", sshModel.getConnectType()); + Opt.ofBlankAble(id).ifPresent(s -> entity.set("id", StrUtil.format(" <> {}", s))); + boolean exists = machineSshServer.exists(entity); + Assert.state(!exists, I18nMessageUtil.get("i18n.ssh_already_exists_with_message.d284")); + try { + + String workspaceId = getWorkspaceId(); + Session session = machineSshServer.getSessionByModel(sshModel); + JschUtil.close(session); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.ssh_connection_failed.4719"), e); + return new JsonMessage<>(505, I18nMessageUtil.get("i18n.ssh_connection_failed.74ab") + e.getMessage()); + } + sshModel.setStatus(1); + int i = add ? machineSshServer.insert(sshModel) : machineSshServer.updateById(sshModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + + @PostMapping(value = "delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage delete(@ValidatorItem String id) { + long count = sshService.countByMachine(id); + Assert.state(count <= 0, StrUtil.format(I18nMessageUtil.get("i18n.ssh_connections_warning.1ddb"), count)); + machineSshServer.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 执行记录 + * + * @return json + */ + @PostMapping(value = "log-list-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(cls = ClassFeature.SSH_TERMINAL_LOG, method = MethodFeature.LIST) + public IJsonMessage> logListData(HttpServletRequest request) { + Map paramMap = ServletUtil.getParamMap(request); + PageResultDto pageResult = sshTerminalExecuteLogService.listPage(paramMap); + return JsonMessage.success(I18nMessageUtil.get("i18n.get_success.fb55"), pageResult); + } + + @GetMapping(value = "list-workspace-ssh", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listWorkspaceSsh(@ValidatorItem String id) { + MachineSshModel machineSshModel = machineSshServer.getByKey(id); + Assert.notNull(machineSshModel, I18nMessageUtil.get("i18n.no_machine.89ed")); + SshModel sshModel = new SshModel(); + sshModel.setMachineSshId(id); + List modelList = sshService.listByBean(sshModel); + modelList = Optional.ofNullable(modelList).orElseGet(ArrayList::new); + for (SshModel model : modelList) { + model.setWorkspace(workspaceService.getByKey(model.getWorkspaceId())); + } + return JsonMessage.success("", modelList); + } + + /** + * 保存工作空间配置 + * + * @param fileDirs 文件夹 + * @param id ID + * @param notAllowedCommand 禁止输入的命令 + * @return json + */ + @PostMapping(value = "save-workspace-config", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage saveWorkspaceConfig( + String fileDirs, + @ValidatorItem String id, + String notAllowedCommand, + String allowEditSuffix) { + SshModel sshModel = new SshModel(id); + // 目录 + if (StrUtil.isEmpty(fileDirs)) { + sshModel.fileDirs(null); + } else { + List list = StrSplitter.splitTrim(fileDirs, StrUtil.LF, true); + for (String s : list) { + String normalize = FileUtil.normalize(s + StrUtil.SLASH); + int count = StrUtil.count(normalize, StrUtil.SLASH); + Assert.state(count >= 2, I18nMessageUtil.get("i18n.ssh_authorization_directory_cannot_be_root.8125")); + } + // + UserModel userModel = getUser(); + Assert.state(!userModel.isDemoUser(), PermissionInterceptor.DEMO_TIP); + sshModel.fileDirs(list); + } + sshModel.setNotAllowedCommand(notAllowedCommand); + // 获取允许编辑的后缀 + List allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, I18nMessageUtil.get("i18n.suffix_cannot_be_empty.ec72")); + sshModel.allowEditSuffix(allowEditSuffixList); + sshService.updateById(sshModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 将 ssh 分配到指定工作空间 + * + * @param ids ssh id + * @param workspaceId 工作空间id + * @return json + */ + @PostMapping(value = "distribute", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage distribute(@ValidatorItem String ids, @ValidatorItem String workspaceId) { + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String id : list) { + MachineSshModel machineSshModel = machineSshServer.getByKey(id); + Assert.notNull(machineSshModel, I18nMessageUtil.get("i18n.no_corresponding_ssh.aa68")); + boolean exists = workspaceService.exists(new WorkspaceModel(workspaceId)); + Assert.state(exists, I18nMessageUtil.get("i18n.workspace_not_exist.a6fd")); + // + if (!sshService.existsSsh2(workspaceId, id)) { + // + sshService.insert(machineSshModel, workspaceId); + } + } + + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * edit + * + * @param id ssh id + * @return json + */ + @PostMapping(value = "rest-hide-field", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage restHideField(@ValidatorItem String id) { + MachineSshModel machineSshModel = new MachineSshModel(); + machineSshModel.setId(id); + machineSshModel.setPassword(StrUtil.EMPTY); + machineSshModel.setPrivateKey(StrUtil.EMPTY); + machineSshServer.updateById(machineSshModel); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + /** + * 下载导入模板 + */ + @GetMapping(value = "import-template", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public void importTemplate(HttpServletResponse response) throws IOException { + String fileName = I18nMessageUtil.get("i18n.ssh_import_template_csv.14fa"); + this.setApplicationHeader(response, fileName); + // + CsvWriter writer = CsvUtil.getWriter(response.getWriter()); + writer.writeLine("name", "groupName", "host", "port", "user", "password", "charset", "connectType", "privateKey", "timeout"); + writer.flush(); + } + + + /** + * 导出数据 + */ + @GetMapping(value = "export-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DOWNLOAD) + public void exportData(HttpServletResponse response, HttpServletRequest request) throws IOException { + String prefix = I18nMessageUtil.get("i18n.exported_ssh_data.2896"); + String fileName = prefix + DateTime.now().toString(DatePattern.NORM_DATE_FORMAT) + ".csv"; + this.setApplicationHeader(response, fileName); + // + CsvWriter writer = CsvUtil.getWriter(response.getWriter()); + int pageInt = 0; + writer.writeLine("name", "groupName", "host", "port", "user", "password", "charset", "connectType", "privateKey", "timeout"); + while (true) { + Map paramMap = ServletUtil.getParamMap(request); + paramMap.remove("workspaceId"); + // 下一页 + paramMap.put("page", String.valueOf(++pageInt)); + PageResultDto listPage = machineSshServer.listPage(paramMap, false); + if (listPage.isEmpty()) { + break; + } + listPage.getResult() + .stream() + .map((Function>) machineSshModel -> CollUtil.newArrayList( + machineSshModel.getName(), + machineSshModel.getGroupName(), + machineSshModel.getHost(), + machineSshModel.getPort(), + machineSshModel.getUser(), + machineSshModel.getPassword(), + machineSshModel.getCharset(), + machineSshModel.getConnectType(), + machineSshModel.getPrivateKey(), + machineSshModel.getTimeout() + )) + .map(objects -> objects.stream().map(StrUtil::toStringOrNull).toArray(String[]::new)) + .forEach(writer::writeLine); + if (ObjectUtil.equal(listPage.getPage(), listPage.getTotalPage())) { + // 最后一页 + break; + } + } + writer.flush(); + } + + /** + * 导入数据 + * + * @return json + */ + @PostMapping(value = "import-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage importData(MultipartFile file) throws IOException { + Assert.notNull(file, I18nMessageUtil.get("i18n.no_uploaded_file.07ef")); + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + boolean csv = StrUtil.endWithIgnoreCase(extName, "csv"); + Assert.state(csv, I18nMessageUtil.get("i18n.disallowed_file_format.d6e4")); + assert originalFilename != null; + File csvFile = FileUtil.file(serverConfig.getUserTempPath(), originalFilename); + int addCount = 0, updateCount = 0; + Charset fileCharset; + try { + file.transferTo(csvFile); + fileCharset = CharsetDetector.detect(csvFile); + Reader bomReader = FileUtil.getReader(csvFile, fileCharset); + CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.setHeaderLineNo(0); + CsvReader reader = CsvUtil.getReader(bomReader, csvReadConfig); + CsvData csvData; + try { + csvData = reader.read(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_csv_exception.885e"), e); + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.parse_file_exception.374d") + e.getMessage()); + } finally { + IoUtil.close(reader); + } + List rows = csvData.getRows(); + Assert.notEmpty(rows, I18nMessageUtil.get("i18n.no_data.55a2")); + + for (int i = 0; i < rows.size(); i++) { + CsvRow csvRow = rows.get(i); + String name = csvRow.getByName("name"); + int finalI = i; + Assert.hasText(name, () -> StrUtil.format(I18nMessageUtil.get("i18n.name_field_required.e0c5"), finalI + 1)); + String groupName = csvRow.getByName("groupName"); + String host = csvRow.getByName("host"); + Assert.hasText(host, () -> StrUtil.format(I18nMessageUtil.get("i18n.host_field_required.5c36"), finalI + 1)); + Integer port = Convert.toInt(csvRow.getByName("port")); + Assert.state(port != null && NetUtil.isValidPort(port), () -> StrUtil.format(I18nMessageUtil.get("i18n.port_field_required_or_incorrect.8426"), finalI + 1)); + String user = csvRow.getByName("user"); + Assert.hasText(host, () -> StrUtil.format(I18nMessageUtil.get("i18n.user_field_required.8732"), finalI + 1)); + String password = csvRow.getByName("password"); + String charset = csvRow.getByName("charset"); + // + String type = csvRow.getByName("connectType"); + type = StrUtil.emptyToDefault(type, "").toUpperCase(); + MachineSshModel.ConnectType connectType = EnumUtil.fromString(MachineSshModel.ConnectType.class, type, MachineSshModel.ConnectType.PASS); + String privateKey = csvRow.getByName("privateKey"); + Integer timeout = Convert.toInt(csvRow.getByName("timeout")); + // + MachineSshModel where = new MachineSshModel(); + where.setHost(host); + where.setUser(user); + where.setPort(port); + where.setConnectType(connectType.name()); + MachineSshModel machineSshModel = machineSshServer.queryByBean(where); + if (machineSshModel == null) { + // 添加 + where.setName(name); + where.setGroupName(groupName); + where.setPassword(password); + where.setPrivateKey(privateKey); + where.setTimeout(timeout); + where.setCharset(charset); + machineSshServer.insert(where); + addCount++; + } else { + MachineSshModel update = new MachineSshModel(); + update.setId(machineSshModel.getId()); + update.setName(name); + update.setGroupName(groupName); + update.setPassword(password); + update.setPrivateKey(privateKey); + update.setTimeout(timeout); + update.setCharset(charset); + machineSshServer.updateById(update); + updateCount++; + } + } + } finally { + FileUtil.del(csvFile); + } + String fileCharsetStr = Optional.ofNullable(fileCharset).map(Charset::name).orElse(StrUtil.EMPTY); + return JsonMessage.success(I18nMessageUtil.get("i18n.import_success_with_details.a4a0"), fileCharsetStr, addCount, updateCount); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineSshFileController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineSshFileController.java new file mode 100644 index 0000000000..0a921f4f29 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineSshFileController.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.function.BiFunction; + +/** + * @author bwcx_jzy + * @since 2023/2/27 + */ +@RestController +@RequestMapping(value = "/system/assets/ssh-file") +@Feature(cls = ClassFeature.SSH_FILE) +@Slf4j +@SystemPermission +public class MachineSshFileController extends BaseSshFileController { + @Override + protected T checkConfigPath(String id, BiFunction function) { + MachineSshModel machineSshModel = machineSshServer.getByKey(id, false); + Assert.notNull(machineSshModel, I18nMessageUtil.get("i18n.no_corresponding_ssh.aa68")); + return function.apply(machineSshModel, new ItemConfig() { + @Override + public List allowEditSuffix() { + return StringUtil.jsonConvertArray(machineSshModel.getAllowEditSuffix(), String.class); + } + + @Override + public List fileDirs() { + return CollUtil.newArrayList(StrUtil.SLASH); + } + }); + } + + @Override + protected T checkConfigPathChildren(String id, String path, String children, BiFunction function) { + FileUtils.checkSlip(path); + Opt.ofBlankAble(children).ifPresent(FileUtils::checkSlip); + // + MachineSshModel machineSshModel = machineSshServer.getByKey(id, false); + return function.apply(machineSshModel, new ItemConfig() { + @Override + public List allowEditSuffix() { + return StringUtil.jsonConvertArray(machineSshModel.getAllowEditSuffix(), String.class); + } + + @Override + public List fileDirs() { + return CollUtil.newArrayList(StrUtil.SLASH); + } + }); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/ScriptLibraryController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/ScriptLibraryController.java new file mode 100644 index 0000000000..51b86b42af --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/ScriptLibraryController.java @@ -0,0 +1,158 @@ +package org.dromara.jpom.func.assets.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +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.BaseServerController; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.func.assets.model.ScriptLibraryModel; +import org.dromara.jpom.func.assets.server.ScriptLibraryServer; +import org.dromara.jpom.model.PageResultDto; +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.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PostMapping; +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.Collection; +import java.util.List; + +/** + * @author bwcx_jzy1 + * @since 2024/6/1 + */ +@RestController +@RequestMapping(value = "/system/assets/script-library") +@Feature(cls = ClassFeature.SYSTEM_ASSETS_GLOBAL_SCRIPT) +@SystemPermission +@Slf4j +public class ScriptLibraryController extends BaseServerController { + + private final ScriptLibraryServer scriptLibraryServer; + + public ScriptLibraryController(ScriptLibraryServer scriptLibraryServer) { + this.scriptLibraryServer = scriptLibraryServer; + } + + @PostMapping(value = "list-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listJson(HttpServletRequest request) { + PageResultDto pageResultDto = scriptLibraryServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage save(HttpServletRequest request) { + ScriptLibraryModel scriptLibraryModel = ServletUtil.toBean(request, ScriptLibraryModel.class, true); + String tag = scriptLibraryModel.getTag(); + Assert.hasText(tag, I18nMessageUtil.get("i18n.mark_cannot_be_empty.1927")); + Validator.validateGeneral(tag, 4, 20, I18nMessageUtil.get("i18n.mark_must_contain_letters_numbers_underscores.667d")); + Assert.hasText(scriptLibraryModel.getScript(), I18nMessageUtil.get("i18n.script_cannot_be_empty.f566")); + // + Entity entity = Entity.create(); + entity.set("tag", tag); + String id = scriptLibraryModel.getId(); + if (StrUtil.isNotEmpty(id)) { + entity.set("id", StrUtil.format(" <> {}", id)); + } + Assert.state(!scriptLibraryServer.exists(entity), I18nMessageUtil.get("i18n.mark_already_exists.0ccc")); + String oldIds = StrUtil.EMPTY; + String version = StrUtil.sub(SecureUtil.md5(scriptLibraryModel.getScript()), 0, 6); + if (StrUtil.isNotEmpty(id)) { + ScriptLibraryModel libraryModel = scriptLibraryServer.getByKey(id); + Assert.notNull(libraryModel, I18nMessageUtil.get("i18n.data_does_not_exist.b201")); + Assert.state(StrUtil.equals(libraryModel.getTag(), tag), I18nMessageUtil.get("i18n.script_tag_modification_not_allowed.cb75")); + oldIds = libraryModel.getMachineIds(); + if (StrUtil.equals(libraryModel.getScript(), scriptLibraryModel.getScript())) { + // 内容没有变化不 + scriptLibraryModel.setVersion(null); + } else { + // 自动生成版本号 + String libraryModelVersion = libraryModel.getVersion(); + List list = StrUtil.splitTrim(libraryModelVersion, "#"); + int nextIncVersion = Convert.toInt(list.get(0), -2) + 1; + scriptLibraryModel.setVersion(StrUtil.format("{}#{}", nextIncVersion, version)); + } + scriptLibraryServer.updateById(scriptLibraryModel); + if (scriptLibraryModel.getVersion() == null) { + scriptLibraryModel.setVersion(libraryModel.getVersion()); + } + } else { + scriptLibraryModel.setVersion(StrUtil.format("1#{}", version)); + scriptLibraryServer.insert(scriptLibraryModel); + } + // 同步到机器节点 + this.syncMachineNodeScript(scriptLibraryModel, oldIds, request); + return JsonMessage.success(I18nMessageUtil.get("i18n.operation_succeeded.3313")); + } + + private void syncMachineNodeScript(ScriptLibraryModel scriptModel, String oldMachineIds, HttpServletRequest request) { + List oldNodeIds = StrUtil.splitTrim(oldMachineIds, StrUtil.COMMA); + List newNodeIds = StrUtil.splitTrim(scriptModel.getMachineIds(), StrUtil.COMMA); + Collection delNode = CollUtil.subtract(oldNodeIds, newNodeIds); + // 删除 + this.syncDelMachineNodeScriptLibrary(scriptModel.getTag(), delNode); + // 更新 + for (String machineId : newNodeIds) { + MachineNodeModel byKey = machineNodeServer.getByKey(machineId); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.no_node_found.6f85")); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", scriptModel.getTag()); + jsonObject.put("description", scriptModel.getDescription()); + jsonObject.put("tag", scriptModel.getTag()); + jsonObject.put("script", scriptModel.getScript()); + jsonObject.put("version", scriptModel.getVersion()); + JsonMessage jsonMessage = NodeForward.request(byKey, NodeUrl.SCRIPT_LIBRARY_SAVE, jsonObject); + String message = StrUtil.format(I18nMessageUtil.get("i18n.handle_node_synchronization_script_library_failure.14e4"), byKey.getName(), jsonMessage.getMsg()); + Assert.state(jsonMessage.success(), message); + } + } + + private void syncDelMachineNodeScriptLibrary(String tag, Collection delNode) { + for (String machineId : delNode) { + MachineNodeModel byKey = machineNodeServer.getByKey(machineId); + if (byKey == null) { + // 机器可能被删除了 + // 避免无法删除脚本库的清空 + continue; + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", tag); + JsonMessage request = NodeForward.request(byKey, NodeUrl.SCRIPT_LIBRARY_DEL, jsonObject); + String message = StrUtil.format(I18nMessageUtil.get("i18n.handle_node_deletion_script_library_failure.4205"), byKey.getName(), request.getMsg()); + Assert.state(request.getCode() == 200, message); + } + } + + @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, HttpServletRequest request) { + ScriptLibraryModel server = scriptLibraryServer.getByKey(id); + if (server != null) { + // 删除节点中的脚本 + String nodeIds = server.getMachineIds(); + List delNode = StrUtil.splitTrim(nodeIds, StrUtil.COMMA); + this.syncDelMachineNodeScriptLibrary(server.getTag(), delNode); + scriptLibraryServer.delByKey(id); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/ScriptLibraryNoPermissionController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/ScriptLibraryNoPermissionController.java new file mode 100644 index 0000000000..3d14d48022 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/ScriptLibraryNoPermissionController.java @@ -0,0 +1,41 @@ +package org.dromara.jpom.func.assets.controller; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.func.assets.model.ScriptLibraryModel; +import org.dromara.jpom.func.assets.server.ScriptLibraryServer; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.springframework.http.MediaType; +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; + +/** + * @author bwcx_jzy + * @since 2024/6/16 + */ +@RestController +@RequestMapping(value = "/system/assets/script-library") +@Feature(cls = ClassFeature.SYSTEM_ASSETS_GLOBAL_SCRIPT) +@Slf4j +public class ScriptLibraryNoPermissionController { + + private final ScriptLibraryServer scriptLibraryServer; + + public ScriptLibraryNoPermissionController(ScriptLibraryServer scriptLibraryServer) { + this.scriptLibraryServer = scriptLibraryServer; + } + + @PostMapping(value = "list-data-no-permission", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listJson(HttpServletRequest request) { + PageResultDto pageResultDto = scriptLibraryServer.listPage(request); + return JsonMessage.success("", pageResultDto); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerContainerController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerContainerController.java new file mode 100644 index 0000000000..f6bf95ac74 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerContainerController.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerContainerController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@RequestMapping(value = "/system/assets/docker/container") +public class AssetsDockerContainerController extends BaseDockerContainerController { + + private final MachineDockerServer machineDockerServer; + + public AssetsDockerContainerController(MachineDockerServer machineDockerServer, + ServerConfig ServerConfig) { + super(ServerConfig); + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerImagesController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerImagesController.java new file mode 100644 index 0000000000..f6f2e2ebc0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerImagesController.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerImagesController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@RequestMapping(value = "/system/assets/docker/images") +public class AssetsDockerImagesController extends BaseDockerImagesController { + + private final MachineDockerServer machineDockerServer; + + public AssetsDockerImagesController(ServerConfig serverConfig, + MachineDockerServer machineDockerServer) { + super(serverConfig); + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerNetworkController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerNetworkController.java new file mode 100644 index 0000000000..4511eb894c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerNetworkController.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerNetworkController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/15 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@RequestMapping(value = "/system/assets/docker/networks") +public class AssetsDockerNetworkController extends BaseDockerNetworkController { + + private final MachineDockerServer machineDockerServer; + + public AssetsDockerNetworkController(MachineDockerServer machineDockerServer) { + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerSwarmInfoController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerSwarmInfoController.java new file mode 100644 index 0000000000..0992f19767 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerSwarmInfoController.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller.docker; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerSwarmInfoController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/13 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@RequestMapping(value = "/system/assets/docker/swarm") +@Slf4j +public class AssetsDockerSwarmInfoController extends BaseDockerSwarmInfoController { + + private final MachineDockerServer machineDockerServer; + + public AssetsDockerSwarmInfoController(MachineDockerServer machineDockerServer) { + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id, false); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_info.c47a")); + if (machineDockerModel.isControlAvailable()) { + // 管理节点 + return machineDockerServer.toParameter(machineDockerModel); + } + // 非管理节点 + MachineDockerModel bySwarmId = machineDockerServer.getMachineDockerBySwarmId(machineDockerModel.getSwarmId()); + return machineDockerServer.toParameter(bySwarmId); + } + + /** + * @return json + */ + @GetMapping(value = "list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listAll() { + MachineDockerModel machineDockerModel = new MachineDockerModel(); + machineDockerModel.setSwarmControlAvailable(true); + // load list with all + List swarmInfoModes = machineDockerServer.listByBean(machineDockerModel); + return JsonMessage.success("", swarmInfoModes); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerSwarmServiceController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerSwarmServiceController.java new file mode 100644 index 0000000000..cff1179f0e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerSwarmServiceController.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller.docker; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerSwarmServiceController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/14 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@RequestMapping(value = "/system/assets/docker/swarm-service") +@Slf4j +public class AssetsDockerSwarmServiceController extends BaseDockerSwarmServiceController { + + private final MachineDockerServer machineDockerServer; + + public AssetsDockerSwarmServiceController(ServerConfig serverConfig, + MachineDockerServer machineDockerServer) { + super(serverConfig); + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id, false); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_info.c47a")); + if (machineDockerModel.isControlAvailable()) { + // 管理节点 + return machineDockerServer.toParameter(machineDockerModel); + } + // 非管理节点 + MachineDockerModel bySwarmId = machineDockerServer.getMachineDockerBySwarmId(machineDockerModel.getSwarmId()); + return machineDockerServer.toParameter(bySwarmId); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerVolumeController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerVolumeController.java new file mode 100644 index 0000000000..a1f479c307 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/docker/AssetsDockerVolumeController.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.controller.docker; + +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.docker.base.BaseDockerVolumeController; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +@RestController +@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER) +@RequestMapping(value = "/system/assets/docker/volumes") +public class AssetsDockerVolumeController extends BaseDockerVolumeController { + + private final MachineDockerServer machineDockerServer; + + public AssetsDockerVolumeController(MachineDockerServer machineDockerServer) { + this.machineDockerServer = machineDockerServer; + } + + @Override + protected Map toDockerParameter(String id) { + MachineDockerModel machineDockerModel = machineDockerServer.getByKey(id); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_corresponding_docker_asset.6f06")); + return machineDockerServer.toParameter(machineDockerModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineDockerModel.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineDockerModel.java new file mode 100644 index 0000000000..89ab40bf14 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineDockerModel.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.model; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseGroupNameModel; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.springframework.util.Assert; + +import java.io.File; + +/** + * @author bwcx_jzy + * @see DockerInfoModel + * @since 2023/3/3 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "MACHINE_DOCKER_INFO", + nameKey = "i18n.machine_docker_info.9914") +@Data +@NoArgsConstructor +public class MachineDockerModel extends BaseGroupNameModel { + /** + * 地址 + */ + private String host; + /** + * 开启 tls 验证 + */ + private Boolean tlsVerify; + /** + * 证书信息 + */ + private String certInfo; + + private Boolean certExist; + /** + * 状态 0 , 异常离线 1 正常 + */ + private Integer status; + /** + * 错误消息 + */ + private String failureMsg; + /** + * docker 版本 + */ + private String dockerVersion; + /** + * 最后心跳时间 + */ + private Long lastHeartbeatTime; + /** + * 超时时间,单位 秒 + */ + private Integer heartbeatTimeout; + /** + * 仓库账号 + */ + private String registryUsername; + + /** + * 仓库密码 + */ + private String registryPassword; + + /** + * 仓库邮箱 + */ + private String registryEmail; + + /** + * 仓库地址 + */ + private String registryUrl; + + /** + * 集群ID + */ + private String swarmId; + /** + * 集群节点ID + */ + private String swarmNodeId; + /** + * 集群的创建时间 + */ + private Long swarmCreatedAt; + /** + * 集群的更新时间 + */ + private Long swarmUpdatedAt; + /** + * 节点 地址 + */ + private String swarmNodeAddr; + /** + * 集群管理员 + */ + private Boolean swarmControlAvailable; + + /** + * 开启 SSH 访问 + */ + private Boolean enableSsh; + + /** + * SSH Id + */ + private String machineSshId; + + + public void setFailureMsg(String failureMsg) { + this.failureMsg = StrUtil.maxLength(failureMsg, 240); + } + + public boolean isControlAvailable() { + return swarmControlAvailable != null && swarmControlAvailable; + } + + /** + * 生成证书路径 + * + * @return path + */ + @Deprecated + public String generateCertPath() { + String dataPath = JpomApplication.getInstance().getDataPath(); + String host = this.getHost(); + Assert.hasText(host, "host empty"); + host = SecureUtil.sha1(host); + File docker = FileUtil.file(dataPath, "docker", "tls-cert", host); + return FileUtil.getAbsolutePath(docker); + } + + + public void restSwarm() { + this.setSwarmId(StrUtil.EMPTY); + this.setSwarmNodeId(StrUtil.EMPTY); + this.setSwarmCreatedAt(0L); + this.setSwarmUpdatedAt(0L); + this.setSwarmNodeAddr(StrUtil.EMPTY); + this.setSwarmControlAvailable(false); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineNodeModel.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineNodeModel.java new file mode 100644 index 0000000000..fe44c3e236 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineNodeModel.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.model; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseGroupNameModel; +import org.dromara.jpom.transport.INodeInfo; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "MACHINE_NODE_INFO", + nameKey = "i18n.machine_node_info.6a75") +@Data +public class MachineNodeModel extends BaseGroupNameModel implements INodeInfo { + /** + * 机器主机名 + */ + private String hostName; + + public void setHostName(String hostName) { + this.hostName = StrUtil.maxLength(hostName, 240); + } + + /** + * 机器的 IP (多个) + */ + private String hostIpv4s; + /** + * 负载 + */ + private String osLoadAverage; + /** + * 系统运行时间(自启动以来的时间)。 + * 自启动以来的秒数。 + */ + private Long osSystemUptime; + /** + * 系统名称 + */ + private String osName; + + public void setOsName(String osName) { + this.osName = StrUtil.maxLength(osName, 40); + } + + /** + * 系统版本 + */ + private String osVersion; + /** + * 硬件版本 + */ + private String osHardwareVersion; + /** + * CPU数 + */ + private Integer osCpuCores; + /** + * 总内存 + */ + private Long osMoneyTotal; + /** + * 交互总内存 + */ + private Long osSwapTotal; + /** + * 虚拟总内存 + */ + private Long osVirtualMax; + /** + * 硬盘总大小 + */ + private Long osFileStoreTotal; + /** + * CPU 型号 + */ + private String osCpuIdentifierName; + /** + * 占用cpu + */ + private Double osOccupyCpu; + /** + * 占用内存 (总共) + */ + private Double osOccupyMemory; + /** + * 占用磁盘 + */ + private Double osOccupyDisk; + /** + * 节点连接状态 + *

+ * 状态{0,无法连接,1 正常, 2 授权信息错误, 3 状态码错误,4 资源监控异常} + */ + private Integer status; + /** + * 状态消息 + */ + private String statusMsg; + /** + * 传输方式。0 服务器拉取,1 节点机器推送 + */ + private Integer transportMode; + /** + * jpom 通讯地址 + */ + private String jpomUrl; + /** + * 节点协议 + */ + private String jpomProtocol; + /** + * 通讯登录账号 + */ + private String jpomUsername; + /** + * 通讯登录密码 + */ + private String jpomPassword; + /** + * 超时时间 + */ + private Integer jpomTimeout; + /** + * http 代理 + */ + private String jpomHttpProxy; + /** + * http 代理 类型 + */ + private String jpomHttpProxyType; + /** + * jpom 版本号 + */ + private String jpomVersion; + /** + * jpom 启动时间 + */ + private Long jpomUptime; + /** + * Jpom 打包时间 + */ + private String jpomBuildTime; + /** + * 网络耗时(延迟) + */ + private Integer networkDelay; + /** + * jpom 项目数 + */ + private Integer jpomProjectCount; + /** + * jpom 脚本数据 + */ + private Integer jpomScriptCount; + /** + * java 版本 + */ + private String javaVersion; + /** + * jvm 总内存 + */ + private Long jvmTotalMemory; + /** + * jvm 剩余内存 + */ + private Long jvmFreeMemory; + /** + * 模板节点 ,1 模板节点 0 非模板节点 + */ + private Boolean templateNode; + /** + * 安装 id + */ + private String installId; + /** + * 扩展信息 + */ + private String extendInfo; + /** + * 传输加密方式 0 不加密 1 BASE64 2 AES + */ + private Integer transportEncryption; + + @Override + public String name() { + return this.getName(); + } + + @Override + public String url() { + return this.getJpomUrl(); + } + + @Override + public String scheme() { + return getJpomProtocol(); + } + + /** + * 获取 授权的信息 + * + * @return sha1 + */ + @Override + public String authorize() { + return SecureUtil.sha1(this.jpomUsername + "@" + this.jpomPassword); + } + + /** + * 获取节点的代理 + * + * @return proxy + */ + @Override + public Proxy proxy() { + String httpProxy = this.getJpomHttpProxy(); + if (StrUtil.isNotEmpty(httpProxy)) { + List split = StrUtil.splitTrim(httpProxy, StrUtil.COLON); + String host = CollUtil.getFirst(split); + int port = Convert.toInt(CollUtil.getLast(split), 0); + String type = this.getJpomHttpProxyType(); + Proxy.Type type1 = EnumUtil.fromString(Proxy.Type.class, type, Proxy.Type.HTTP); + return new Proxy(type1, new InetSocketAddress(host, port)); + } + return null; + } + + @Override + public Integer timeout() { + return this.getJpomTimeout(); + } + + @Override + public Integer transportEncryption() { + // 需要兼容旧数据 + return ObjectUtil.defaultIfNull(this.getTransportEncryption(), 0); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineNodeStatLogModel.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineNodeStatLogModel.java new file mode 100644 index 0000000000..090b078892 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineNodeStatLogModel.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseDbModel; +import org.dromara.jpom.model.data.NodeModel; + +/** + * @author bwcx_jzy + * @see NodeModel + * @since 2023/02/18 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "MACHINE_NODE_STAT_LOG", + nameKey = "i18n.asset_machine_node_statistics.4a03") +@Data +public class MachineNodeStatLogModel extends BaseDbModel { + /** + * 机器id + */ + private String machineId; + /** + * 占用cpu + */ + private Double occupyCpu; + /** + * 占用内存 (总共) + */ + private Double occupyMemory; + /** + * 交互内存 + */ + private Double occupySwapMemory; + /** + * 虚拟内存 + */ + private Double occupyVirtualMemory; + /** + * 占用磁盘 + */ + private Double occupyDisk; + /** + * 监控时间 + * 插件端返回的时间 + */ + private Long monitorTime; + /** + * 网络耗时(延迟) + */ + private Integer networkDelay; + /** + * 每秒发送的KB数,rxkB/s + */ + private Long netTxBytes; + + /** + * 每秒接收的KB数,rxkB/s + */ + private Long netRxBytes; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineSshModel.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineSshModel.java new file mode 100644 index 0000000000..52f0f36f81 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/MachineSshModel.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.model; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseGroupNameModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.plugins.ISshInfo; + +import java.nio.charset.Charset; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author bwcx_jzy + * @see SshModel + * @since 2023/2/25 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "MACHINE_SSH_INFO", + nameKey = "i18n.machine_ssh_info.8dbb") +@Data +@NoArgsConstructor +public class MachineSshModel extends BaseGroupNameModel implements ISshInfo { + + /** + * 主机地址 + */ + private String host; + /** + * 端口 + */ + private Integer port; + /** + * 登录账号 + */ + private String user; + /** + * 账号密码,证书密码 + */ + private String password; + /** + * 编码格式 + */ + private String charset; + /** + * ssh 私钥 + */ + private String privateKey; + + private String connectType; + /** + * 节点超时时间 + */ + private Integer timeout; + + /** + * ssh连接状态 + *

+ * 状态{0,无法连接,1 正常,2 禁用监控} + */ + private Integer status; + /** + * 状态消息 + */ + private String statusMsg; + + private String allowEditSuffix; + /** + * 系统名称 + */ + private String osName; + + public void setOsName(String osName) { + this.osName = StrUtil.maxLength(osName, 45); + } + + /** + * 机器主机名 + */ + private String hostName; + + public void setHostName(String hostName) { + this.hostName = StrUtil.maxLength(hostName, 240); + } + + /** + * 系统版本 + */ + private String osVersion; + /** + * 总内存 + */ + private Long osMoneyTotal; + /** + * 硬盘总大小 + */ + private Long osFileStoreTotal; + /** + * CPU 型号 + */ + private String osCpuIdentifierName; + /** + * CPU数 + */ + private Integer osCpuCores; + /** + * 占用cpu + */ + private Double osOccupyCpu; + /** + * 占用内存 (总共) + */ + private Double osOccupyMemory; + /** + * 占用磁盘 + */ + private Double osMaxOccupyDisk; + /** + * 占用磁盘 分区名 + */ + private String osMaxOccupyDiskName; + /** + * 负载 + */ + private String osLoadAverage; + /** + * 系统运行时间(自启动以来的时间)。 + * 自启动以来的毫秒数。 + */ + private Long osSystemUptime; + /** + * jpom 查询进程号 + */ + private Integer jpomAgentPid; + /** + * java 版本 + */ + private String javaVersion; + /** + * 服务器中的 docker 信息 + */ + private String dockerInfo; + + public MachineSshModel(String id) { + setId(id); + } + + public void allowEditSuffix(List allowEditSuffix) { + if (allowEditSuffix == null) { + this.allowEditSuffix = null; + } else { + this.allowEditSuffix = JSONArray.toJSONString(allowEditSuffix); + } + } + + @Override + public String id() { + return getId(); + } + + @Override + public String host() { + return getHost(); + } + + @Override + public String user() { + return getUser(); + } + + @Override + public String password() { + return getPassword(); + } + + @Override + public String privateKey() { + return getPrivateKey(); + } + + @Override + public int port() { + return ObjectUtil.defaultIfNull(getPort(), 0); + } + + @Override + public ISshInfo.ConnectType connectType() { + return EnumUtil.fromString(MachineSshModel.ConnectType.class, this.connectType, MachineSshModel.ConnectType.PASS); + } + + /** + * 超时时间 + * + * @return 最小值 1 秒钟 + */ + @Override + public int timeout() { + if (this.timeout == null) { + return (int) TimeUnit.SECONDS.toMillis(5); + } + return (int) TimeUnit.SECONDS.toMillis(Math.max(1, this.timeout)); + } + + @Override + public Charset charset() { + return CharsetUtil.parse(this.getCharset(), CharsetUtil.CHARSET_UTF_8); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/model/ScriptLibraryModel.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/ScriptLibraryModel.java new file mode 100644 index 0000000000..f860408b83 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/model/ScriptLibraryModel.java @@ -0,0 +1,41 @@ +package org.dromara.jpom.func.assets.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; + +/** + * @author bwcx_jzy1 + * @since 2024/6/1 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "SCRIPT_LIBRARY", nameKey = "脚本库信息") +@Data +public class ScriptLibraryModel extends BaseUserModifyDbModel { + /** + * 脚本唯一的标记 + */ + private String tag; + /** + * 脚本内容 + */ + private String script; + /** + * 描述 + */ + private String description; + /** + * 版本 + */ + private String version; + /** + * 关联的资产机器节点 + */ + private String machineIds; + + @Override + protected boolean hasCreateUser() { + return true; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineDockerServer.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineDockerServer.java new file mode 100644 index 0000000000..0e073a5d21 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineDockerServer.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.server; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.db.Entity; +import cn.keepbx.jpom.event.IAsyncLoad; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AssetsConfig; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.func.assets.AssetsExecutorPoolService; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.cert.service.CertificateInfoService; +import org.dromara.jpom.func.system.model.ClusterInfoModel; +import org.dromara.jpom.func.system.service.ClusterInfoService; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.model.docker.DockerSwarmInfoMode; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.annotation.Resource; +import java.io.File; +import java.nio.file.NoSuchFileException; +import java.util.*; +import java.util.function.Supplier; + +/** + * @author bwcx_jzy + * @since 2023/3/3 + */ +@Service +@Slf4j +public class MachineDockerServer extends BaseDbService implements ILoadEvent, IAsyncLoad, Task { + private static final String CRON_ID = "docker-monitor"; + private final MachineSshServer machineSshServer; + private final DockerInfoService dockerInfoService; + private final DockerSwarmInfoService dockerSwarmInfoService; + private final ClusterInfoService clusterInfoService; + private final AssetsConfig.DockerConfig dockerConfig; + private final AssetsExecutorPoolService assetsExecutorPoolService; + @Resource + @Lazy + private CertificateInfoService certificateInfoService; + + + public MachineDockerServer(MachineSshServer machineSshServer, + DockerInfoService dockerInfoService, + DockerSwarmInfoService dockerSwarmInfoService, + ClusterInfoService clusterInfoService, + AssetsConfig assetsConfig, + AssetsExecutorPoolService assetsExecutorPoolService) { + this.machineSshServer = machineSshServer; + this.dockerInfoService = dockerInfoService; + this.dockerSwarmInfoService = dockerSwarmInfoService; + this.clusterInfoService = clusterInfoService; + this.dockerConfig = assetsConfig.getDocker(); + this.assetsExecutorPoolService = assetsExecutorPoolService; + } + + @Override + protected void fillInsert(MachineDockerModel machineDockerModel) { + super.fillInsert(machineDockerModel); + machineDockerModel.setGroupName(StrUtil.emptyToDefault(machineDockerModel.getGroupName(), Const.DEFAULT_GROUP_NAME.get())); + machineDockerModel.setStatus(ObjectUtil.defaultIfNull(machineDockerModel.getStatus(), 0)); + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + long count = this.count(); + if (count != 0) { + log.debug(I18nMessageUtil.get("i18n.docker_data_repair_not_needed.0fb9"), count); + return; + } + List list = dockerInfoService.list(false); + if (CollUtil.isEmpty(list)) { + log.debug(I18nMessageUtil.get("i18n.no_docker_info_no_need_to_fix_machine_data.f45e")); + return; + } + Map> map = CollStreamUtil.groupByKey(list, DockerInfoModel::getHost); + List models = new ArrayList<>(map.size()); + for (Map.Entry> entry : map.entrySet()) { + List value = entry.getValue(); + // 排序,最近更新过优先 + value.sort((o1, o2) -> CompareUtil.compare(o2.getModifyTimeMillis(), o1.getModifyTimeMillis())); + DockerInfoModel first = CollUtil.getFirst(value); + if (value.size() > 1) { + log.warn(I18nMessageUtil.get("i18n.multiple_docker_addresses_found.0f82"), entry.getKey(), first.getName()); + } + models.add(this.dockerInfoToMachineDocker(first)); + } + this.insert(models); + log.info(I18nMessageUtil.get("i18n.machines_docker_data_fixed.af8a"), models.size()); + // 更新 docker 的机器id + for (MachineDockerModel value : models) { + Entity entity = Entity.create(); + entity.set("machineDockerId", value.getId()); + { + // + Entity where = Entity.create(); + where.set("host", value.getHost()); + int update = dockerInfoService.update(entity, where); + Assert.state(update > 0, I18nMessageUtil.get("i18n.update_docker_machine_id_failed.063d") + value.getName()); + } + } + } + + + private MachineDockerModel dockerInfoToMachineDocker(DockerInfoModel dockerInfoModel) { + MachineDockerModel machineDockerModel = new MachineDockerModel(); + machineDockerModel.setName(dockerInfoModel.getName()); + machineDockerModel.setHost(dockerInfoModel.getHost()); + machineDockerModel.setTlsVerify(dockerInfoModel.getTlsVerify()); + machineDockerModel.setHeartbeatTimeout(dockerInfoModel.getHeartbeatTimeout()); + // + machineDockerModel.setSwarmNodeId(dockerInfoModel.getSwarmNodeId()); + machineDockerModel.setSwarmId(dockerInfoModel.getSwarmId()); + // + machineDockerModel.setRegistryEmail(dockerInfoModel.getRegistryEmail()); + machineDockerModel.setRegistryUrl(dockerInfoModel.getRegistryUrl()); + machineDockerModel.setRegistryUsername(dockerInfoModel.getRegistryUsername()); + machineDockerModel.setRegistryPassword(dockerInfoModel.getRegistryPassword()); + return machineDockerModel; + } + + @Override + public void startLoad() { + String monitorCron = dockerConfig.getMonitorCron(); + String cron = Opt.ofBlankAble(monitorCron).orElse("0 0/1 * * * ?"); + CronUtils.add(CRON_ID, cron, () -> MachineDockerServer.this); + } + + @Override + public void execute() { + Entity entity = new Entity(); + if (clusterInfoService.isMultiServer()) { + // 查询对应分组的数据 + ClusterInfoModel current = clusterInfoService.getCurrent(); + String linkGroup = current.getLinkGroup(); + List linkGroups = StrUtil.splitTrim(linkGroup, StrUtil.COMMA); + if (CollUtil.isEmpty(linkGroups)) { + log.warn(I18nMessageUtil.get("i18n.cluster_not_bound_to_group_for_docker_monitoring.3926")); + return; + } + entity.set("groupName", linkGroups); + } + List list = this.listByEntity(entity, false); + if (CollUtil.isEmpty(list)) { + return; + } + this.checkList(list); + } + + private void checkList(List monitorModels) { + monitorModels.forEach(monitorModel -> assetsExecutorPoolService.execute(() -> this.updateMonitor(monitorModel))); + } + + /** + * 监控 容器 + * + * @param dockerInfoModel docker + */ + public boolean updateMonitor(MachineDockerModel dockerInfoModel) { + try { + DockerInfoModel model = new DockerInfoModel(); + model.setMachineDockerId(dockerInfoModel.getId()); + IPlugin pluginCheck = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + Map parameter = this.toParameter(dockerInfoModel); + // + JSONObject info = pluginCheck.execute("info", parameter, JSONObject.class); + // + MachineDockerModel update = new MachineDockerModel(); + update.setId(dockerInfoModel.getId()); + update.setStatus(1); + update.setLastHeartbeatTime(SystemClock.now()); + // + update.setDockerVersion(info.getString("serverVersion")); + JSONObject swarm = info.getJSONObject("swarm"); + // + IPlugin plugin = PluginFactory.getPlugin(DockerSwarmInfoService.DOCKER_PLUGIN_NAME); + JSONObject swarmData = null; + try { + if (dockerInfoModel.isControlAvailable()) { + swarmData = plugin.execute("inSpectSwarm", this.toParameter(dockerInfoModel), JSONObject.class); + } else { + // 找到管理节点 + MachineDockerModel managerDocker = this.getMachineDockerBySwarmId(dockerInfoModel.getSwarmId()); + swarmData = plugin.execute("inSpectSwarm", this.toParameter(managerDocker), JSONObject.class); + } + } catch (Exception e) { + log.debug(I18nMessageUtil.get("i18n.get_docker_cluster_failure_with_placeholder.06cb"), dockerInfoModel.getName(), e.getMessage()); + } + Optional.ofNullable(swarmData).ifPresent(jsonObject -> { + String swarmId = jsonObject.getString("id"); + update.setSwarmCreatedAt(DateUtil.parse(jsonObject.getString("createdAt")).getTime()); + update.setSwarmUpdatedAt(DateUtil.parse(jsonObject.getString("updatedAt")).getTime()); + update.setSwarmId(swarmId); + }); + Optional.ofNullable(swarm).ifPresent(jsonObject -> { + String nodeId = jsonObject.getString("nodeID"); + String nodeAddr = jsonObject.getString("nodeAddr"); + boolean controlAvailable = jsonObject.getBooleanValue("controlAvailable"); + update.setSwarmControlAvailable(controlAvailable); + update.setSwarmNodeAddr(nodeAddr); + update.setSwarmNodeId(nodeId); + }); + if (StrUtil.isEmpty(update.getSwarmNodeId())) { + // 集群退出 + update.restSwarm(); + } + update.setFailureMsg(StrUtil.EMPTY); + update.setCertExist(this.checkCertPath(dockerInfoModel)); + super.updateById(update); + // + return true; + } catch (Exception e) { + String message = e.getMessage(); + if (ExceptionUtil.isCausedBy(e, NoSuchFileException.class)) { + log.error(I18nMessageUtil.get("i18n.monitor_docker_exception_detail.e334"), dockerInfoModel.getName(), message); + } else if (StrUtil.containsIgnoreCase(message, "Connection timed out")) { + log.error(I18nMessageUtil.get("i18n.monitor_docker_timeout.b03b"), dockerInfoModel.getName(), message); + } else { + log.error(I18nMessageUtil.get("i18n.monitor_docker_exception.e326"), dockerInfoModel.getName(), e); + } + this.updateStatus(dockerInfoModel.getId(), 0, message); + return false; + } + } + + /** + * 验证 证书文件是否存在 + * + * @param dockerInfoModel docker + * @return true 证书文件存在 + */ + private boolean checkCertPath(MachineDockerModel dockerInfoModel) { + try { + File filePath = certificateInfoService.getFilePath(dockerInfoModel.getCertInfo()); + if (filePath == null) { + return false; + } + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + return (boolean) plugin.execute("certPath", "certPath", filePath.getAbsolutePath()); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.check_docker_cert_exception.8042"), e.getMessage()); + return false; + } + } + + /** + * 验证 证书文件是否存在 + * + * @param path 路径 + * @return true 证书文件存在 + */ + public boolean checkCertPath(String path) { + try { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_CHECK_PLUGIN_NAME); + return (boolean) plugin.execute("certPath", "certPath", path); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.check_docker_cert_exception.8042"), e.getMessage()); + return false; + } + } + + public void updateSwarmInfo(String id, JSONObject swarmData, JSONObject info) { + // + JSONObject swarm = info.getJSONObject("swarm"); + Assert.notNull(swarm, I18nMessageUtil.get("i18n.cluster_info_incomplete_for_operation.ad96")); + String nodeAddr = swarm.getString("nodeAddr"); + String nodeId = swarm.getString("nodeID"); + Assert.hasText(nodeAddr, I18nMessageUtil.get("i18n.node_address_not_found.f955")); + // + Date createdAt = swarmData.getDate("createdAt"); + Date updatedAt = swarmData.getDate("updatedAt"); + String swarmId = swarmData.getString("id"); + // + MachineDockerModel machineDockerModel = new MachineDockerModel(); + machineDockerModel.setSwarmUpdatedAt(createdAt.getTime()); + machineDockerModel.setSwarmUpdatedAt(updatedAt.getTime()); + machineDockerModel.setSwarmId(swarmId); + machineDockerModel.setSwarmNodeAddr(nodeAddr); + machineDockerModel.setSwarmNodeId(nodeId); + boolean controlAvailable = swarm.getBooleanValue("controlAvailable"); + machineDockerModel.setSwarmControlAvailable(controlAvailable); + machineDockerModel.setId(id); + this.updateById(machineDockerModel); + } + + /** + * 更新 容器状态 + * + * @param id ID + * @param status 状态值 + * @param msg 错误消息 + */ + private void updateStatus(String id, int status, String msg) { + MachineDockerModel dockerInfoModel = new MachineDockerModel(); + dockerInfoModel.setId(id); + dockerInfoModel.setStatus(status); + dockerInfoModel.setFailureMsg(msg); + super.updateById(dockerInfoModel); + } + + public Map dockerParameter(DockerInfoModel dockerInfoModel) { + String machineDockerId = dockerInfoModel.getMachineDockerId(); + MachineDockerModel machineDockerModel = this.getByKey(machineDockerId, false); + Assert.notNull(machineDockerModel, I18nMessageUtil.get("i18n.no_docker_info_found.6d38")); + Integer status = machineDockerModel.getStatus(); + Assert.state(status != null && status == 1, StrUtil.format(I18nMessageUtil.get("i18n.current_docker_offline.a509"), machineDockerModel.getName())); + return this.toParameter(machineDockerModel); + } + + /** + * 跟进 docker 列表找到一个可用的 docker 信息 + * + * @param dockerInfoModels docker 列表 + * @return map + */ + public Map dockerParameter(List dockerInfoModels) { + for (DockerInfoModel dockerInfoModel : dockerInfoModels) { + String machineDockerId = dockerInfoModel.getMachineDockerId(); + MachineDockerModel machineDockerModel = this.getByKey(machineDockerId, false); + if (machineDockerModel != null) { + Integer status = machineDockerModel.getStatus(); + if (status != null && status == 1) { + Map parameter = this.toParameter(machineDockerModel); + // 更新名称 + parameter.put("name", dockerInfoModel.getName()); + return parameter; + } + } + } + return null; + } + + /** + * 通过集群 id 获取 docker 管理参数 + * + * @param workspaceSwarmId 集群id + * @return map + */ + public Map dockerParameter(String workspaceSwarmId) { + MachineDockerModel first = this.getMachineDocker(workspaceSwarmId); + Assert.notNull(first, I18nMessageUtil.get("i18n.cluster_manager_node_not_found.1cd0")); + Integer status = first.getStatus(); + Assert.state(status != null && status == 1, StrUtil.format(I18nMessageUtil.get("i18n.current_docker_cluster_has_no_management_nodes_online.56cd"), first.getName())); + return toParameter(first); + } + + private MachineDockerModel getMachineDocker(String workspaceSwarmId) { + DockerSwarmInfoMode swarmInfoMode = dockerSwarmInfoService.getByKey(workspaceSwarmId); + Assert.notNull(swarmInfoMode, I18nMessageUtil.get("i18n.no_cluster_info_found.fb40")); + String modeSwarmId = swarmInfoMode.getSwarmId(); + // + return this.getMachineDockerBySwarmId(modeSwarmId); + } + + public MachineDockerModel getMachineDockerBySwarmId(String swarmId) { + // + MachineDockerModel dockerInfoModel = this.tryMachineDockerBySwarmId(swarmId); + Assert.notNull(dockerInfoModel, I18nMessageUtil.get("i18n.no_manager_node_found.5934")); + return dockerInfoModel; + } + + public MachineDockerModel tryMachineDockerBySwarmId(String swarmId) { + if (StrUtil.isEmpty(swarmId)) { + return null; + } + // + MachineDockerModel dockerInfoModel = new MachineDockerModel(); + dockerInfoModel.setSwarmId(swarmId); + dockerInfoModel.setSwarmControlAvailable(true); + List machineDockerModels = this.listByBean(dockerInfoModel, false); + if (machineDockerModels == null) { + return null; + } + // 跟进在线情况排序 + machineDockerModels.sort((o1, o2) -> CompareUtil.compare(o2.getStatus(), o1.getStatus())); + return CollUtil.getFirst(machineDockerModels); + } + + /** + * 插件 插件参数 map + * + * @return 插件需要使用到到参数 + */ + public Map toParameter(MachineDockerModel machineDockerModel) { + Map parameter = new HashMap<>(10); + parameter.put("dockerHost", machineDockerModel.getHost()); + parameter.put("name", machineDockerModel.getName()); + parameter.put("registryUsername", machineDockerModel.getRegistryUsername()); + parameter.put("registryPassword", machineDockerModel.getRegistryPassword()); + parameter.put("registryEmail", machineDockerModel.getRegistryEmail()); + parameter.put("registryUrl", machineDockerModel.getRegistryUrl()); + parameter.put("timeout", machineDockerModel.getHeartbeatTimeout()); + if (machineDockerModel.getTlsVerify()) { + File filePath = certificateInfoService.getFilePath(machineDockerModel.getCertInfo()); + Assert.notNull(filePath, I18nMessageUtil.get("i18n.docker_certificate_file_missing.ad46")); + parameter.put("dockerCertPath", filePath.getAbsolutePath()); + } + if (Boolean.TRUE.equals(machineDockerModel.getEnableSsh()) && StrUtil.isNotEmpty(machineDockerModel.getMachineSshId())) { + // 添加SSH的操作Session + MachineSshModel sshModel = machineSshServer.getByKey(machineDockerModel.getMachineSshId()); + Assert.notNull(sshModel, I18nMessageUtil.get("i18n.ssh_info_does_not_exist.5ed0")); + // 需要关闭之前的连接,避免阻塞 + parameter.put("closeBefore", true); + parameter.put("session", (Supplier) () -> machineSshServer.getSessionByModel(sshModel)); + } + return parameter; + } +} 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 new file mode 100644 index 0000000000..2320dc884e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeServer.java @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.server; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.event.IAsyncLoad; +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.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.AssetsExecutorPoolService; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.func.assets.model.MachineNodeStatLogModel; +import org.dromara.jpom.func.system.service.ClusterInfoService; +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.ServerConfig; +import org.dromara.jpom.system.db.InitDb; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +@Service +@Slf4j +public class MachineNodeServer extends BaseDbService implements ILoadEvent, IAsyncLoad, Runnable { + + private final NodeService nodeService; + private final NodeConfig nodeConfig; + private final MachineNodeStatLogServer machineNodeStatLogServer; + private final ClusterInfoService clusterInfoService; + private final AssetsExecutorPoolService assetsExecutorPoolService; + + private static final String TASK_ID = "system_monitor_node"; + + public MachineNodeServer(NodeService nodeService, + ServerConfig serverConfig, + MachineNodeStatLogServer machineNodeStatLogServer, + ClusterInfoService clusterInfoService, + AssetsExecutorPoolService assetsExecutorPoolService) { + this.nodeService = nodeService; + this.nodeConfig = serverConfig.getNode(); + this.machineNodeStatLogServer = machineNodeStatLogServer; + this.clusterInfoService = clusterInfoService; + this.assetsExecutorPoolService = assetsExecutorPoolService; + } + + @Override + protected void fillSelectResult(MachineNodeModel data) { + Optional.ofNullable(data).ifPresent(machineNodeModel -> machineNodeModel.setJpomPassword(null)); + } + + @Override + protected void fillInsert(MachineNodeModel machineNodeModel) { + super.fillInsert(machineNodeModel); + machineNodeModel.setGroupName(StrUtil.emptyToDefault(machineNodeModel.getGroupName(), Const.DEFAULT_GROUP_NAME.get())); + // + machineNodeModel.setTransportMode(0); + } + + /** + * 同步数据,兼容低版本数据 + * + * @param applicationContext 应用上下文 + * @throws Exception 异常 + */ + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + long count = this.count(); + if (count != 0) { + log.debug(I18nMessageUtil.get("i18n.node_machine_table_exists_no_need_to_fix.2625"), count); + return; + } + List list = nodeService.list(false); + if (CollUtil.isEmpty(list)) { + log.debug(I18nMessageUtil.get("i18n.no_node_info_no_need_to_fix_machine_data.562e")); + return; + } + // delete from MACHINE_NODE_INFO; + // drop table MACHINE_NODE_INFO; + Map> nodeUrlMap = CollStreamUtil.groupByKey(list, NodeModel::getUrl); + List machineNodeModels = new ArrayList<>(nodeUrlMap.size()); + for (Map.Entry> entry : nodeUrlMap.entrySet()) { + List value = entry.getValue(); + // 排序,最近更新过优先 + value.sort((o1, o2) -> CompareUtil.compare(o2.getModifyTimeMillis(), o1.getModifyTimeMillis())); + NodeModel first = CollUtil.getFirst(value); + if (value.size() > 1) { + log.warn(I18nMessageUtil.get("i18n.multiple_node_data_exists_merge_config.043f"), entry.getKey(), first.getName()); + } + machineNodeModels.add(this.nodeInfoToMachineNode(first)); + } + this.insert(machineNodeModels); + log.info(I18nMessageUtil.get("i18n.machines_node_data_fixed.7744"), machineNodeModels.size()); + // 更新节点的机器id + for (MachineNodeModel value : machineNodeModels) { + Entity entity = Entity.create(); + entity.set("machineId", value.getId()); + Entity where = Entity.create(); + where.set("url", value.getJpomUrl()); + int update = nodeService.update(entity, where); + Assert.state(update > 0, I18nMessageUtil.get("i18n.update_node_machine_id_failed.51d9") + value.getName()); + } + } + + /** + * 保证在数据库启动成功之后 + * + * @return 想要比数据库晚加载 + * @see InitDb#getOrder() + */ + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE + 1; + } + + /** + * 节点对象转机器对象 + * + * @param nodeModel 节点 + * @return 机器对象 + */ + private MachineNodeModel nodeInfoToMachineNode(NodeModel nodeModel) { + MachineNodeModel machineNodeModel = new MachineNodeModel(); + machineNodeModel.setName(nodeModel.getName()); + machineNodeModel.setGroupName(nodeModel.getGroup()); + machineNodeModel.setTransportMode(0); + machineNodeModel.setStatus(0); + machineNodeModel.setJpomTimeout(nodeModel.getTimeOut()); + machineNodeModel.setJpomUrl(nodeModel.getUrl()); + machineNodeModel.setJpomUsername(nodeModel.getLoginName()); + machineNodeModel.setJpomPassword(nodeModel.getLoginPwd()); + machineNodeModel.setJpomProtocol(nodeModel.getProtocol()); + machineNodeModel.setJpomHttpProxy(nodeModel.getHttpProxy()); + machineNodeModel.setJpomHttpProxyType(nodeModel.getHttpProxyType()); + machineNodeModel.setModifyUser(nodeModel.getModifyUser()); + machineNodeModel.setCreateTimeMillis(nodeModel.getCreateTimeMillis()); + machineNodeModel.setModifyTimeMillis(nodeModel.getModifyTimeMillis()); + return machineNodeModel; + } + + @Override + public void startLoad() { + // 启动心跳检测 + int heartSecond = nodeConfig.getHeartSecond(); + ScheduledExecutorService scheduler = JpomApplication.getScheduledExecutorService(); + scheduler.scheduleWithFixedDelay(this, 0, heartSecond, TimeUnit.SECONDS); + } + + @Override + public void run() { + Entity entity = new Entity(); + if (clusterInfoService.isMultiServer()) { + String linkGroup = clusterInfoService.getCurrent().getLinkGroup(); + List linkGroups = StrUtil.splitTrim(linkGroup, StrUtil.COMMA); + if (CollUtil.isEmpty(linkGroups)) { + log.warn(I18nMessageUtil.get("i18n.cluster_not_bound_to_group_for_node_monitoring.1586")); + return; + } + entity.set("groupName", linkGroups); + } + entity.set("transportMode", 0); + int heartSecond = nodeConfig.getHeartSecond(); + try { + CronUtils.TaskStat taskStat = CronUtils.getTaskStat(TASK_ID, StrUtil.format(I18nMessageUtil.get("i18n.execution_frequency.d014"), heartSecond)); + taskStat.onStart(); + //MachineNodeModel machineNodeModel = new MachineNodeModel(); + //machineNodeModel.setTransportMode(0); + List machineNodeModels = this.listByEntity(entity); + this.checkList(machineNodeModels); + taskStat.onSucceeded(); + } catch (Throwable throwable) { + CronUtils.TaskStat taskStat = CronUtils.getTaskStat(TASK_ID, StrUtil.format(I18nMessageUtil.get("i18n.execution_frequency.d014"), heartSecond)); + taskStat.onFailed(TASK_ID, throwable); + } + } + + + private void checkList(List machineNodeModels) { + if (CollUtil.isEmpty(machineNodeModels)) { + return; + } + machineNodeModels.forEach(machineNodeModel -> { + // 超时时间统一,避免长时间无响应 + machineNodeModel.setJpomTimeout(30); + // + assetsExecutorPoolService.execute(() -> { + try { + BaseServerController.resetInfo(UserModel.EMPTY); + long timeMillis = SystemClock.now(); + JsonMessage message = NodeForward.request(machineNodeModel, NodeUrl.GetStatInfo, new JSONObject()); + int networkTime = (int) (System.currentTimeMillis() - timeMillis); + JSONObject jsonObject; + if (message.success()) { + jsonObject = message.getData(JSONObject.class); + } else { + // 状态码错 + this.updateStatus(machineNodeModel, 3, message.toString()); + return; + } + jsonObject.put("networkDelay", networkTime); + this.saveStatInfo(machineNodeModel, jsonObject); + } catch (AgentAuthorizeException agentException) { + this.updateStatus(machineNodeModel, 2, agentException.getMessage()); + } catch (AgentException e) { + this.updateStatus(machineNodeModel, 0, e.getMessage()); + } catch (Exception e) { + this.updateStatus(machineNodeModel, 0, e.getMessage()); + log.error(I18nMessageUtil.get("i18n.get_node_monitoring_info_failure.595a"), e); + } finally { + BaseServerController.removeEmpty(); + } + }); + }); + } + + /** + * 更新统计信息 + * + * @param machineNode 机器数据 + * @param data 统计数据 + */ + private void saveStatInfo(MachineNodeModel machineNode, JSONObject data) { + MachineNodeModel machineNodeModel = new MachineNodeModel(); + machineNodeModel.setId(machineNode.getId()); + String oshiError = data.getString("oshiError"); + if (StrUtil.isEmpty(oshiError)) { + machineNodeModel.setStatus(1); + machineNodeModel.setStatusMsg("ok"); + } else { + machineNodeModel.setStatus(4); + machineNodeModel.setStatusMsg(oshiError); + } + int networkDelay = data.getIntValue("networkDelay"); + int systemSleep = data.getIntValue("systemSleep"); + // 减去系统固定休眠时间 + networkDelay = networkDelay - systemSleep; + machineNodeModel.setNetworkDelay(networkDelay); + // jpom 相关信息 + 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")); + machineNodeModel.setOsName(jsonObject1.getString("osName")); + machineNodeModel.setJpomUptime(jsonObject1.getLong("upTime")); + machineNodeModel.setInstallId(jsonObject1.getString("installId")); + }); + machineNodeModel.setJpomProjectCount(jsonObject.getIntValue("projectCount")); + machineNodeModel.setJpomScriptCount(jsonObject.getIntValue("scriptCount")); + // + machineNodeModel.setJvmFreeMemory(jsonObject.getLongValue("freeMemory")); + machineNodeModel.setJvmTotalMemory(jsonObject.getLongValue("totalMemory")); + machineNodeModel.setJavaVersion(jsonObject.getString("javaVersion")); + }); + // 基础状态信息 + MachineNodeStatLogModel machineNodeStatLogModel = new MachineNodeStatLogModel(); + machineNodeStatLogModel.setMachineId(machineNodeModel.getId()); + machineNodeStatLogModel.setNetworkDelay(networkDelay); + // + JSONObject extendInfo = new JSONObject(); + Optional.ofNullable(data.getJSONObject("simpleStatus")).ifPresent(jsonObject -> { + machineNodeModel.setOsOccupyMemory(ObjectUtil.defaultIfNull(jsonObject.getDouble("memory"), -1D)); + machineNodeModel.setOsOccupyDisk(ObjectUtil.defaultIfNull(jsonObject.getDouble("disk"), -1D)); + machineNodeModel.setOsOccupyCpu(ObjectUtil.defaultIfNull(jsonObject.getDouble("cpu"), -1D)); + // + machineNodeStatLogModel.setOccupyCpu(machineNodeModel.getOsOccupyCpu()); + machineNodeStatLogModel.setOccupyMemory(machineNodeModel.getOsOccupyMemory()); + machineNodeStatLogModel.setOccupyDisk(machineNodeModel.getOsOccupyDisk()); + machineNodeStatLogModel.setOccupySwapMemory(jsonObject.getDouble("swapMemory")); + machineNodeStatLogModel.setOccupyVirtualMemory(jsonObject.getDouble("virtualMemory")); + machineNodeStatLogModel.setNetTxBytes(jsonObject.getLong("netTxBytes")); + machineNodeStatLogModel.setNetRxBytes(jsonObject.getLong("netRxBytes")); + machineNodeStatLogModel.setMonitorTime(jsonObject.getLongValue("time")); + // + extendInfo.put("monitorIfsNames", jsonObject.getString("monitorIfsNames")); + }); + // 系统信息 + Optional.ofNullable(data.getJSONObject("systemInfo")).ifPresent(jsonObject -> { + machineNodeModel.setOsSystemUptime(jsonObject.getLong("systemUptime")); + machineNodeModel.setOsVersion(jsonObject.getString("osVersion")); + machineNodeModel.setHostName(jsonObject.getString("hostName")); + machineNodeModel.setOsHardwareVersion(jsonObject.getString("hardwareVersion")); + machineNodeModel.setHostIpv4s(CollUtil.join(jsonObject.getList("hostIpv4s", String.class), StrUtil.COMMA)); + machineNodeModel.setOsCpuIdentifierName(jsonObject.getString("osCpuIdentifierName")); + machineNodeModel.setOsCpuCores(jsonObject.getInteger("osCpuCores")); + machineNodeModel.setOsMoneyTotal(jsonObject.getLong("osMoneyTotal")); + machineNodeModel.setOsSwapTotal(jsonObject.getLong("osSwapTotal")); + machineNodeModel.setOsVirtualMax(jsonObject.getLong("osVirtualMax")); + List osLoadAverage = jsonObject.getList("osLoadAverage", Double.class); + if (osLoadAverage != null) { + // 保留两位小数 + osLoadAverage = osLoadAverage.stream() + .map(aDouble -> NumberUtil.div(aDouble, (Double) 1D, 2)) + .collect(Collectors.toList()); + } + machineNodeModel.setOsLoadAverage(CollUtil.join(osLoadAverage, StrUtil.COMMA)); + machineNodeModel.setOsFileStoreTotal(jsonObject.getLong("osFileStoreTotal")); + }); + machineNodeModel.setExtendInfo(extendInfo.toString()); + this.updateById(machineNodeModel); + if (machineNodeStatLogModel.getMonitorTime() != null) { + 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); + } + }); + } + + /** + * 更新机器状态 + * + * @param machineNode 机器信息 + * @param status 状态 + * @param msg 状态消息 + */ + private void updateStatus(MachineNodeModel machineNode, int status, String msg) { + MachineNodeModel machineNodeModel = new MachineNodeModel(); + machineNodeModel.setId(machineNode.getId()); + machineNodeModel.setStatus(status); + machineNodeModel.setStatusMsg(msg); + // 将信息置空,避免影响排序 + machineNodeModel.setNetworkDelay(-9999_999); + machineNodeModel.setOsOccupyCpu(-99D); + machineNodeModel.setOsOccupyMemory(-99D); + machineNodeModel.setOsOccupyDisk(-99D); + this.updateById(machineNodeModel); + } + + private MachineNodeModel resolveMachineData(HttpServletRequest request) { + // 创建对象 + MachineNodeModel machineNodeModel = ServletUtil.toBean(request, MachineNodeModel.class, true); + Assert.hasText(machineNodeModel.getName(), I18nMessageUtil.get("i18n.machine_name_required.e8cf")); + Assert.hasText(machineNodeModel.getJpomUrl(), I18nMessageUtil.get("i18n.please_fill_in_node_address.e77e")); + Assert.hasText(machineNodeModel.getJpomUsername(), I18nMessageUtil.get("i18n.node_account_required.2d90")); + + Assert.hasText(machineNodeModel.getJpomProtocol(), I18nMessageUtil.get("i18n.protocol_required.b4f8")); + // + MachineNodeModel update = new MachineNodeModel(); + update.setId(machineNodeModel.getId()); + update.setGroupName(machineNodeModel.getGroupName()); + update.setName(machineNodeModel.getName()); + update.setJpomHttpProxy(machineNodeModel.getJpomHttpProxy()); + update.setJpomHttpProxyType(machineNodeModel.getJpomHttpProxyType()); + update.setJpomUrl(machineNodeModel.getJpomUrl()); + update.setJpomProtocol(machineNodeModel.getJpomProtocol()); + update.setJpomUsername(machineNodeModel.getJpomUsername()); + update.setJpomPassword(machineNodeModel.getJpomPassword()); + update.setJpomTimeout(machineNodeModel.getJpomTimeout()); + update.setTemplateNode(machineNodeModel.getTemplateNode()); + update.setTransportEncryption(machineNodeModel.getTransportEncryption()); + return update; + } + + public boolean existsByUrl(String jpomUrl, String id) { + Assert.hasText(jpomUrl, I18nMessageUtil.get("i18n.node_address_required.71f1")); + // + Entity entity = Entity.create(); + entity.set("jpomUrl", jpomUrl); + if (StrUtil.isNotEmpty(id)) { + entity.set("id", StrUtil.format(" <> {}", id)); + } + return this.exists(entity); + } + + public MachineNodeModel getByUrl(String jpomUrl) { + MachineNodeModel machineNodeModel = new MachineNodeModel(); + machineNodeModel.setJpomUrl(jpomUrl); + List machineNodeModels = this.listByBean(machineNodeModel); + return CollUtil.getFirst(machineNodeModels); + } + + public void update(HttpServletRequest request) { + MachineNodeModel machineNodeModel = this.resolveMachineData(request); + boolean exists = this.existsByUrl(machineNodeModel.getJpomUrl(), machineNodeModel.getId()); + Assert.state(!exists, I18nMessageUtil.get("i18n.node_already_exists.28ea")); + this.testNode(machineNodeModel); + // 更新状态 + machineNodeModel.setStatus(1); + // + this.testHttpProxy(machineNodeModel.getJpomHttpProxy()); + // + if (StrUtil.isNotEmpty(machineNodeModel.getId())) { + this.updateById(machineNodeModel); + } else { + this.insert(machineNodeModel); + } + } + + /** + * 测试节点是否可以访问 + * + * @param nodeModel 节点信息 + */ + public void testNode(MachineNodeModel nodeModel) { + // + int timeout = ObjectUtil.defaultIfNull(nodeModel.getJpomTimeout(), 0); + // 检查是否可用默认为5秒,避免太长时间无法连接一直等待 + nodeModel.setJpomTimeout(5); + // + JsonMessage objectJsonMessage = NodeForward.request(nodeModel, StrUtil.EMPTY, NodeUrl.Info, "nodeId", nodeModel.getId()); + try { + JpomManifest jpomManifest = objectJsonMessage.getData(JpomManifest.class); + Assert.notNull(jpomManifest, I18nMessageUtil.get("i18n.node_connection_failure.896d")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.node_connection_failure.896d"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.node_return_info_exception.0961")); + } + // + nodeModel.setJpomTimeout(timeout); + } + + /** + * 探测 http proxy 是否可用 + * + * @param httpProxy http proxy + */ + public void testHttpProxy(String httpProxy) { + if (StrUtil.isNotEmpty(httpProxy)) { + List split = StrUtil.splitTrim(httpProxy, StrUtil.COLON); + Assert.isTrue(CollUtil.size(split) == 2, I18nMessageUtil.get("i18n.invalid_http_proxy_address.1da1")); + String host = split.get(0); + int port = Convert.toInt(split.get(1), 0); + Assert.isTrue(StrUtil.isNotEmpty(host) && NetUtil.isValidPort(port), I18nMessageUtil.get("i18n.invalid_http_proxy_address.1da1")); + // + try { + NetUtil.netCat(host, port, StrUtil.EMPTY.getBytes()); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.http_proxy_address_unavailable.b3f2") + httpProxy, e); + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.http_proxy_address_unavailable.b3f2") + e.getMessage()); + } + } + } + + public void insertAndNode(MachineNodeModel machineNodeModel, String workspaceId) { + this.insert(machineNodeModel); + // + this.insertNode(machineNodeModel, workspaceId); + } + + /** + * 根据机器添加 节点 + * + * @param machineNodeModel 机器信息 + * @param workspaceId 工作空间 + */ + public void insertNode(MachineNodeModel machineNodeModel, String workspaceId) { + NodeModel nodeModel = this.createModel(machineNodeModel, workspaceId); + nodeService.insert(nodeModel); + } + + private NodeModel createModel(MachineNodeModel machineNodeModel, String workspaceId) { + NodeModel nodeModel = new NodeModel(); + nodeModel.setMachineId(machineNodeModel.getId()); + nodeModel.setWorkspaceId(workspaceId); + nodeModel.setName(machineNodeModel.getName()); + nodeModel.setOpenStatus(1); + nodeModel.setGroup(machineNodeModel.getGroupName()); + return nodeModel; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeStatLogServer.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeStatLogServer.java new file mode 100644 index 0000000000..963fcb9da9 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeStatLogServer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.server; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.db.Entity; +import cn.keepbx.jpom.event.ISystemTask; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.NodeConfig; +import org.dromara.jpom.func.assets.model.MachineNodeStatLogModel; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.stereotype.Service; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +@Service +@Slf4j +public class MachineNodeStatLogServer extends BaseDbService implements ISystemTask { + + private final NodeConfig nodeConfig; + + public MachineNodeStatLogServer(ServerConfig serverConfig) { + this.nodeConfig = serverConfig.getNode(); + } + + @Override + public void executeTask() { + int statLogKeepDays = nodeConfig.getStatLogKeepDays(); + log.debug(I18nMessageUtil.get("i18n.log_retention_days.99d1"), statLogKeepDays); + if (statLogKeepDays <= 0) { + return; + } + Entity entity = Entity.create(); + DateTime dateTime = DateUtil.beginOfDay(DateTime.now()); + dateTime = DateUtil.offsetDay(dateTime, -statLogKeepDays); + entity.set(" monitorTime", "< " + dateTime.getTime()); + int del = this.del(entity); + log.info(I18nMessageUtil.get("i18n.auto_clear_machine_node_stats_logs.5279"), del); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineSshServer.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineSshServer.java new file mode 100644 index 0000000000..b6db684c47 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineSshServer.java @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.assets.server; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +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.map.CaseInsensitiveMap; +import cn.hutool.core.util.*; +import cn.hutool.cron.task.Task; +import cn.hutool.db.Entity; +import cn.hutool.extra.ssh.JschUtil; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.event.IAsyncLoad; +import com.alibaba.fastjson2.JSONObject; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import lombok.Lombok; +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.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.AssetsConfig; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.dialect.DialectUtil; +import org.dromara.jpom.func.assets.AssetsExecutorPoolService; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.system.service.ClusterInfoService; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.plugin.IWorkspaceEnvPlugin; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.plugins.ISshInfo; +import org.dromara.jpom.plugins.JschUtils; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.StringUtil; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.annotation.Resource; +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.*; + +/** + * @author bwcx_jzy + * @since 2023/2/25 + */ +@Service +@Slf4j +public class MachineSshServer extends BaseDbService implements ILoadEvent, IAsyncLoad, Task { + private static final String CRON_ID = "ssh-monitor"; + @Resource + @Lazy + private SshService sshService; + + private final JpomApplication jpomApplication; + private final ClusterInfoService clusterInfoService; + private final AssetsConfig.SshConfig sshConfig; + private final AssetsExecutorPoolService assetsExecutorPoolService; + + public MachineSshServer(JpomApplication jpomApplication, + ClusterInfoService clusterInfoService, + AssetsConfig assetsConfig, + AssetsExecutorPoolService assetsExecutorPoolService) { + this.jpomApplication = jpomApplication; + this.clusterInfoService = clusterInfoService; + this.sshConfig = assetsConfig.getSsh(); + this.assetsExecutorPoolService = assetsExecutorPoolService; + } + + @Override + protected void fillInsert(MachineSshModel machineSshModel) { + super.fillInsert(machineSshModel); + machineSshModel.setGroupName(StrUtil.emptyToDefault(machineSshModel.getGroupName(), Const.DEFAULT_GROUP_NAME.get())); + machineSshModel.setStatus(ObjectUtil.defaultIfNull(machineSshModel.getStatus(), 0)); + } + + @Override + protected void fillSelectResult(MachineSshModel data) { + if (data == null) { + return; + } + if (!StrUtil.startWithIgnoreCase(data.getPassword(), ServerConst.REF_WORKSPACE_ENV)) { + // 隐藏密码字段 + data.setPassword(null); + } + //data.setPassword(null); + data.setPrivateKey(null); + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + long count = this.count(); + if (count != 0) { + log.debug(I18nMessageUtil.get("i18n.ssh_data_repair_not_needed.203f"), count); + return; + } + List list = sshService.list(false); + if (CollUtil.isEmpty(list)) { + log.debug(I18nMessageUtil.get("i18n.no_ssh_info_no_need_to_fix_machine_data.0946")); + return; + } + Map> sshMap = CollStreamUtil.groupByKey(list, sshModel -> StrUtil.format("{} {} {} {}", sshModel.getHost(), sshModel.getPort(), sshModel.getUser(), sshModel.getConnectType())); + List machineSshModels = new ArrayList<>(sshMap.size()); + for (Map.Entry> entry : sshMap.entrySet()) { + List value = entry.getValue(); + // 排序,最近更新过优先 + value.sort((o1, o2) -> CompareUtil.compare(o2.getModifyTimeMillis(), o1.getModifyTimeMillis())); + SshModel first = CollUtil.getFirst(value); + if (value.size() > 1) { + log.warn(I18nMessageUtil.get("i18n.multiple_ssh_addresses_found.b3f7"), entry.getKey(), first.getName()); + } + machineSshModels.add(this.sshInfoToMachineSsh(first)); + } + this.insert(machineSshModels); + log.info(I18nMessageUtil.get("i18n.machines_ssh_data_fixed.1387"), machineSshModels.size()); + // 更新 ssh 的机器id + for (MachineSshModel value : machineSshModels) { + Entity entity = Entity.create(); + entity.set("machineSshId", value.getId()); + Entity where = Entity.create(); + where.set("host", value.getHost()); + where.set("port", value.getPort()); + // 关键词,如果不加 ` 会查询不出结果 + where.set(DialectUtil.wrapField("user"), value.getUser()); + where.set("connectType", value.getConnectType()); + int update = sshService.update(entity, where); + Assert.state(update > 0, I18nMessageUtil.get("i18n.update_ssh_machine_id_failed.bd24") + value.getName()); + } + } + + private MachineSshModel sshInfoToMachineSsh(SshModel sshModel) { + MachineSshModel machineSshModel = new MachineSshModel(); + machineSshModel.setName(sshModel.getName()); + machineSshModel.setGroupName(sshModel.getGroup()); + machineSshModel.setHost(sshModel.getHost()); + machineSshModel.setPort(sshModel.getPort()); + machineSshModel.setUser(sshModel.getUser()); + machineSshModel.setCharset(sshModel.getCharset()); + machineSshModel.setTimeout(sshModel.getTimeout()); + machineSshModel.setPrivateKey(sshModel.getPrivateKey()); + machineSshModel.setPassword(sshModel.getPassword()); + machineSshModel.setConnectType(sshModel.getConnectType()); + machineSshModel.setCreateTimeMillis(sshModel.getCreateTimeMillis()); + machineSshModel.setModifyTimeMillis(sshModel.getModifyTimeMillis()); + machineSshModel.setModifyUser(sshModel.getModifyUser()); + return machineSshModel; + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE + 1; + } + + @Override + public void startLoad() { + String monitorCron = sshConfig.getMonitorCron(); + String cron = Opt.ofBlankAble(monitorCron).orElse("0 0/1 * * * ?"); + CronUtils.add(CRON_ID, cron, () -> MachineSshServer.this); + } + + @Override + public void execute() { + Entity entity = new Entity(); + if (clusterInfoService.isMultiServer()) { + String linkGroup = clusterInfoService.getCurrent().getLinkGroup(); + List linkGroups = StrUtil.splitTrim(linkGroup, StrUtil.COMMA); + if (CollUtil.isEmpty(linkGroups)) { + log.warn(I18nMessageUtil.get("i18n.cluster_not_bound_to_group_for_ssh_monitoring.c894")); + return; + } + entity.set("groupName", linkGroups); + } + List list = this.listByEntity(entity, false); + if (CollUtil.isEmpty(list)) { + return; + } + this.checkList(list); + } + + private void checkList(List monitorModels) { + monitorModels.forEach(monitorModel -> assetsExecutorPoolService.execute(() -> this.updateMonitor(monitorModel))); + } + + /** + * 执行监控 ssh + * + * @param machineSshModel 资产 ssh + */ + private void updateMonitor(MachineSshModel machineSshModel) { + List monitorGroupName = sshConfig.getDisableMonitorGroupName(); + if (CollUtil.containsAny(monitorGroupName, CollUtil.newArrayList(machineSshModel.getGroupName(), "*"))) { + // 禁用监控 + if (machineSshModel.getStatus() != null && machineSshModel.getStatus() == 2) { + // 不需要更新 + return; + } + this.updateStatus(machineSshModel.id(), 2, I18nMessageUtil.get("i18n.disable_monitoring.4615")); + return; + } + Session session = null; + try { + InputStream sshExecTemplateInputStream = ExtConfigBean.getConfigResourceInputStream("/ssh/monitor-script.sh"); + String sshExecTemplate = IoUtil.readUtf8(sshExecTemplateInputStream); + Map map = new HashMap<>(10); + map.put("JPOM_AGENT_PID_TAG", Type.Agent.getTag()); + sshExecTemplate = StringUtil.formatStrByMap(sshExecTemplate, map); + Charset charset = machineSshModel.charset(); + // + session = this.getSessionByModelNoFill(machineSshModel); + int timeout = machineSshModel.timeout(); + List listStr = new ArrayList<>(); + List error = new ArrayList<>(); + JschUtils.execCallbackLine(session, charset, timeout, sshExecTemplate, StrUtil.EMPTY, listStr::add, error::add); + this.updateMonitorInfo(machineSshModel, listStr, error); + } catch (Exception e) { + String message = e.getMessage(); + if (StrUtil.containsIgnoreCase(message, "timeout")) { + log.error(I18nMessageUtil.get("i18n.monitor_ssh_timeout.59fd"), machineSshModel.getName(), message); + } else { + log.error(I18nMessageUtil.get("i18n.monitor_ssh_exception.e9ce"), machineSshModel.getName(), e); + } + this.updateStatus(machineSshModel.getId(), 0, message); + } finally { + JschUtil.close(session); + } + } + + /** + * 解析监控执行结果 + * + * @param machineSshModel 监控的ssh + * @param listStr 结果信息 + * @param errorList 错误信息 + */ + private void updateMonitorInfo(MachineSshModel machineSshModel, List listStr, List errorList) { + String error = CollUtil.join(errorList, StrUtil.LF); + if (StrUtil.isNotEmpty(error)) { + log.error(I18nMessageUtil.get("i18n.ssh_monitor_execution_error.2d3c"), machineSshModel.getName(), error); + } + if (log.isDebugEnabled()) { + log.debug(I18nMessageUtil.get("i18n.ssh_monitor_info_result.a660"), machineSshModel.getName(), CollUtil.join(listStr, StrUtil.LF), error); + } + if (CollUtil.isEmpty(listStr)) { + this.updateStatus(machineSshModel.getId(), 1, I18nMessageUtil.get("i18n.empty_execution_result.9fe8") + error); + return; + } + Map> map = new CaseInsensitiveMap<>(listStr.size()); + for (String strItem : listStr) { + String key = StrUtil.subBefore(strItem, StrUtil.COLON, false); + List list = map.computeIfAbsent(key, s2 -> new ArrayList<>()); + list.add(StrUtil.subAfter(strItem, StrUtil.COLON, false)); + } + MachineSshModel update = new MachineSshModel(); + update.setId(machineSshModel.getId()); + update.setStatus(1); + update.setOsName(this.getFirstValue(map, "os name")); + update.setOsVersion(this.getFirstValue(map, "os version")); + update.setOsLoadAverage(CollUtil.join(map.get("load average"), StrUtil.COMMA)); + String uptime = this.getFirstValue(map, "uptime"); + if (StrUtil.isNotEmpty(uptime)) { + try { + // 可能有时区问题 + DateTime dateTime = DateUtil.parse(uptime); + update.setOsSystemUptime((SystemClock.now() - dateTime.getTime())); + } catch (Exception e) { + error = error + I18nMessageUtil.get("i18n.parse_system_start_time_error.112c") + e.getMessage(); + update.setOsSystemUptime(0L); + } + } + update.setOsCpuCores(Convert.toInt(this.getFirstValue(map, "cpu core"), 0)); + update.setHostName(this.getFirstValue(map, "hostname")); + update.setOsCpuIdentifierName(this.getFirstValue(map, "model name")); + // kb + Long memoryTotal = Convert.toLong(this.getFirstValue(map, "memory total"), 0L); + Long memoryUsed = Convert.toLong(this.getFirstValue(map, "memory used"), 0L); + update.setOsMoneyTotal(memoryTotal * 1024); + error = Opt.ofBlankAble(error).map(s -> I18nMessageUtil.get("i18n.error_info.99ed") + s).orElse(StrUtil.EMPTY); + update.setStatusMsg(I18nMessageUtil.get("i18n.execution_succeeded.f56c") + error); + update.setOsOccupyCpu(Convert.toDouble(this.getFirstValue(map, "cpu usage"), -0D)); + if (memoryTotal > 0) { + update.setOsOccupyMemory(NumberUtil.div(memoryUsed, memoryTotal, 2).doubleValue()); + } else { + update.setOsOccupyMemory(-0D); + } + List list = map.get("disk info"); + update.setOsMaxOccupyDisk(-0D); + update.setOsMaxOccupyDiskName(StrUtil.EMPTY); + if (CollUtil.isNotEmpty(list)) { + long total = 0; + for (String s : list) { + List trim = StrUtil.splitTrim(s, StrUtil.COLON); + long total1 = Convert.toLong(CollUtil.get(trim, 1), 0L); + total += total1; + long used = Convert.toLong(CollUtil.get(trim, 2), 0L); + // 计算最大的硬盘占用 + if (total1 > 0) { + Double osMaxOccupyDisk = update.getOsMaxOccupyDisk(); + osMaxOccupyDisk = ObjectUtil.defaultIfNull(osMaxOccupyDisk, 0D); + double occupyDisk = NumberUtil.div(used, total1, 2); + if (occupyDisk > osMaxOccupyDisk) { + update.setOsMaxOccupyDisk(occupyDisk); + update.setOsMaxOccupyDiskName(CollUtil.getFirst(trim)); + } + } + } + update.setOsFileStoreTotal(total * 1024); + } + update.setJavaVersion(this.getFirstValue(map, "java version")); + update.setJpomAgentPid(Convert.toInt(this.getFirstValue(map, "jpom agent pid"))); + // + String dockerPath = this.getFirstValue(map, "docker path"); + String dockerVersion = this.getFirstValue(map, "docker version"); + if (StrUtil.isAllNotEmpty(dockerVersion, dockerPath)) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("version", dockerVersion); + jsonObject.put("path", dockerPath); + update.setDockerInfo(jsonObject.toString()); + } else { + update.setDockerInfo(StrUtil.EMPTY); + } + this.updateById(update); + } + + private String getFirstValue(Map> map, String name) { + List list = map.get(name); + String first = CollUtil.getFirst(list); + // 内存获取可能最后存在 : + return StrUtil.removeSuffix(first, StrUtil.COLON); + } + + /** + * 更新 容器状态 + * + * @param id ID + * @param status 状态值 + * @param msg 错误消息 + */ + private void updateStatus(String id, int status, String msg) { + MachineSshModel machineSshModel = new MachineSshModel(); + machineSshModel.setId(id); + machineSshModel.setStatus(status); + machineSshModel.setStatusMsg(msg); + // + machineSshModel.setOsLoadAverage("-"); + machineSshModel.setOsOccupyCpu(-1D); + machineSshModel.setOsMaxOccupyDisk(-1D); + machineSshModel.setOsOccupyMemory(-1D); + machineSshModel.setDockerInfo(""); + machineSshModel.setJavaVersion(""); + machineSshModel.setJpomAgentPid(0); + super.updateById(machineSshModel); + } + + /** + * 获取 ssh 回话 + * GLOBAL + * + * @param sshModel sshModel + * @return session + */ + public Session getSessionByModel(MachineSshModel sshModel) { + MachineSshModel model = this.getByKey(sshModel.getId(), false); + Optional.ofNullable(model).ifPresent(machineSshModel -> { + sshModel.setPassword(StrUtil.emptyToDefault(sshModel.getPassword(), machineSshModel.getPassword())); + sshModel.setPrivateKey(StrUtil.emptyToDefault(sshModel.getPrivateKey(), machineSshModel.getPrivateKey())); + }); + return this.getSessionByModelNoFill(sshModel); + } + + /** + * 获取 ssh 回话 + * GLOBAL + * + * @param sshModel sshModel + * @return session + */ + public Session getSessionByModelNoFill(ISshInfo sshModel) { + String workspaceId = ServerConst.WORKSPACE_GLOBAL; + if (sshModel instanceof MachineSshModel) { + SshModel sshModel1 = sshService.getByMachineSshId(((MachineSshModel) sshModel).getId()); + if (sshModel1 != null) { + workspaceId = sshModel1.getWorkspaceId(); + } + } + Assert.notNull(sshModel, I18nMessageUtil.get("i18n.no_ssh_info.a8ec")); + Session session = null; + int timeout = sshModel.timeout(); + MachineSshModel.ConnectType connectType = sshModel.connectType(); + String user = sshModel.user(); + String password = sshModel.password(); + // 转化密码字段 + IWorkspaceEnvPlugin plugin = (IWorkspaceEnvPlugin) PluginFactory.getPlugin(IWorkspaceEnvPlugin.PLUGIN_NAME); + try { + user = plugin.convertRefEnvValue(workspaceId, user); + password = plugin.convertRefEnvValue(workspaceId, password); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + if (connectType == MachineSshModel.ConnectType.PASS) { + session = JschUtil.openSession(sshModel.host(), sshModel.port(), user, password, timeout); + + } else if (connectType == MachineSshModel.ConnectType.PUBKEY) { + File rsaFile = null; + String privateKey = sshModel.privateKey(); + byte[] passwordByte = StrUtil.isEmpty(password) ? null : StrUtil.bytes(password); + //sshModel.password(); + if (StrUtil.startWith(privateKey, URLUtil.FILE_URL_PREFIX)) { + String rsaPath = StrUtil.removePrefix(privateKey, URLUtil.FILE_URL_PREFIX); + rsaFile = FileUtil.file(rsaPath); + } else if (StrUtil.startWith(privateKey, JschUtils.HEADER)) { + // 直接采用 private key content 登录,无需写入文件 + session = JschUtils.createSession(sshModel.host(), + sshModel.port(), + user, + StrUtil.trim(privateKey), + passwordByte); + } else if (StrUtil.isEmpty(privateKey)) { + File home = FileUtil.getUserHomeDir(); + Assert.notNull(home, I18nMessageUtil.get("i18n.user_directory_not_found.cfe3")); + File identity = FileUtil.file(home, ".ssh", "identity"); + rsaFile = FileUtil.isFile(identity) ? identity : null; + File idRsa = FileUtil.file(home, ".ssh", "id_rsa"); + rsaFile = FileUtil.isFile(idRsa) ? idRsa : rsaFile; + File idDsa = FileUtil.file(home, ".ssh", "id_dsa"); + rsaFile = FileUtil.isFile(idDsa) ? idDsa : rsaFile; + Assert.notNull(rsaFile, I18nMessageUtil.get("i18n.user_directory_not_found_private_key_info.6ce4")); + } else { + //这里的实现,用于把 private key 写入到一个临时文件中,此方式不太采取 + File tempPath = jpomApplication.getTempPath(); + String sshFile = StrUtil.emptyToDefault(sshModel.id(), IdUtil.fastSimpleUUID()); + rsaFile = FileUtil.file(tempPath, "ssh", sshFile); + FileUtil.writeString(privateKey, rsaFile, CharsetUtil.UTF_8); + } + // 如果是私钥正文,则 session 已经初始化了 + if (session == null) { + // 简要私钥文件是否存在 + Assert.state(FileUtil.isFile(rsaFile), I18nMessageUtil.get("i18n.private_key_file_not_found.4ad9") + FileUtil.getAbsolutePath(rsaFile)); + session = JschUtil.createSession(sshModel.host(), + sshModel.port(), user, FileUtil.getAbsolutePath(rsaFile), passwordByte); + } + try { + session.setServerAliveInterval(timeout); + session.setServerAliveCountMax(5); + } catch (JSchException e) { + log.warn(I18nMessageUtil.get("i18n.ssh_server_alive_interval_config_error.1f11"), e); + } + try { + session.connect(timeout); + } catch (JSchException e) { + throw Lombok.sneakyThrow(e); + } + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_mode.501d")); + } + + return session; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/server/ScriptLibraryServer.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/ScriptLibraryServer.java new file mode 100644 index 0000000000..85227f800e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/ScriptLibraryServer.java @@ -0,0 +1,60 @@ +package org.dromara.jpom.func.assets.server; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.ScriptLibraryModel; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bwcx_jzy1 + * @since 2024/6/1 + */ +@Service +@Slf4j +public class ScriptLibraryServer extends BaseDbService { + + private final Pattern pattern = PatternPool.get("G@\\(\"(.*?)\"\\)", Pattern.DOTALL); + + /** + * 引用替换 + * + * @param script 脚本 + * @return 替换后的脚本 + */ + public String referenceReplace(String script) { + if (StrUtil.isEmpty(script)) { + return script; + } + Map map = new HashMap<>(3); + Matcher matcher = pattern.matcher(script); + StringBuffer modified = new StringBuffer(); + while (matcher.find()) { + String tag = matcher.group(1); + ScriptLibraryModel scriptLibraryModel = map.get(tag); + if (scriptLibraryModel == null) { + ScriptLibraryModel where = new ScriptLibraryModel(); + where.setTag(tag); + List libraryModels = this.listByBean(where); + scriptLibraryModel = CollUtil.getFirst(libraryModels); + if (scriptLibraryModel != null) { + map.put(tag, scriptLibraryModel); + } + } + Assert.notNull(scriptLibraryModel, StrUtil.format(I18nMessageUtil.get("i18n.error_message.483d"), tag)); + matcher.appendReplacement(modified, scriptLibraryModel.getScript()); + } + matcher.appendTail(modified); + return modified.toString(); + } +} 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 new file mode 100644 index 0000000000..b2127edf25 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/cert/controller/CertificateInfoController.java @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.cert.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ZipUtil; +import cn.hutool.crypto.KeyUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.controller.outgiving.OutGivingWhitelistService; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.cert.model.CertificateInfoModel; +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.model.PageResultDto; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.model.data.ServerWhitelist; +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.system.ServerConfig; +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 org.springframework.web.multipart.MultipartFile; + +import javax.security.cert.X509Certificate; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.util.*; + +/** + * @author bwcx_jzy + * @since 2023/3/22 + */ +@RestController +@RequestMapping(value = "/certificate/") +@Feature(cls = ClassFeature.CERTIFICATE_INFO) +@Slf4j +public class CertificateInfoController extends BaseServerController { + + + private final ServerConfig serverConfig; + private final MachineDockerServer machineDockerServer; + private final CertificateInfoService certificateInfoService; + private final FileReleaseTaskService fileReleaseTaskService; + private final OutGivingWhitelistService outGivingWhitelistService; + private final FileStorageService fileStorageService; + + public CertificateInfoController(ServerConfig serverConfig, + MachineDockerServer machineDockerServer, + CertificateInfoService certificateInfoService, + FileReleaseTaskService fileReleaseTaskService, + OutGivingWhitelistService outGivingWhitelistService, + FileStorageService fileStorageService) { + this.serverConfig = serverConfig; + this.machineDockerServer = machineDockerServer; + this.certificateInfoService = certificateInfoService; + this.fileReleaseTaskService = fileReleaseTaskService; + this.outGivingWhitelistService = outGivingWhitelistService; + this.fileStorageService = fileStorageService; + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + // + PageResultDto listPage = certificateInfoService.listPage(request); + listPage.each(certificateInfoModel -> { + File file = certificateInfoService.getFilePath(certificateInfoModel); + certificateInfoModel.setFileExists(!FileUtil.isEmpty(file)); + }); + return JsonMessage.success("", listPage); + } + + /** + * 查询所有分页列表 + * + * @return json + */ + @PostMapping(value = "list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + @SystemPermission + public IJsonMessage> listAll(HttpServletRequest request) { + // + PageResultDto listPage = certificateInfoService.listPageAll(request); + listPage.each(certificateInfoModel -> { + File file = certificateInfoService.getFilePath(certificateInfoModel); + certificateInfoModel.setFileExists(!FileUtil.isEmpty(file)); + }); + return JsonMessage.success("", listPage); + } + + @PostMapping(value = "import-file", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage importFile(MultipartFile file, @ValidatorItem String type, String password) { + Assert.notNull(file, I18nMessageUtil.get("i18n.no_uploaded_file.07ef")); + String filename = file.getOriginalFilename(); + Assert.notNull(filename, I18nMessageUtil.get("i18n.file_name_not_found.b0ed")); + File tempPath = FileUtil.file(serverConfig.getUserTempPath(), "cert", IdUtil.fastSimpleUUID()); + CertificateInfoModel certificateInfoModel; + try { + switch (type) { + case KeyUtil.KEY_TYPE_PKCS12: + case KeyUtil.KEY_TYPE_JKS: + certificateInfoModel = this.resolvePkcs12OrJks(file, password, tempPath, type); + break; + case KeyUtil.CERT_TYPE_X509: + certificateInfoModel = this.resolveX509(file, tempPath); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_mode_with_colon.c6de") + type); + } + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } finally { + FileUtil.file(tempPath); + } + certificateInfoService.insert(certificateInfoModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + + /** + * 解析 x509 证书 + * + * @param multipartFile 上传的文件 + * @param tempPath 临时保存目录 + * @return 证书对象 + * @throws IOException io + */ + private CertificateInfoModel resolveX509(MultipartFile multipartFile, + File tempPath) throws IOException { + FileUtil.mkdir(tempPath); + String filename = multipartFile.getOriginalFilename(); + String extName = FileUtil.extName(filename); + Assert.state(StrUtil.containsIgnoreCase(extName, "zip"), I18nMessageUtil.get("i18n.invalid_file_type.7246")); + File saveFile = FileUtil.file(tempPath, filename); + multipartFile.transferTo(saveFile); + ZipUtil.unzip(saveFile, tempPath); + return certificateInfoService.resolveX509(tempPath, true); + } + + /** + * 解析 pfx /jks 证书 + * + * @param multipartFile 上传的文件 + * @param password 密码 + * @param tempPath 临时保存目录 + * @return 证书对象 + * @throws IOException io + */ + private CertificateInfoModel resolvePkcs12OrJks(MultipartFile multipartFile, + String password, File tempPath, + String type) throws IOException { + String suffix; + switch (type) { + case KeyUtil.KEY_TYPE_JKS: + suffix = "jks"; + break; + case KeyUtil.KEY_TYPE_PKCS12: + default: + suffix = "pfx"; + break; + } + FileUtil.mkdir(tempPath); + String filename = multipartFile.getOriginalFilename(); + String extName = FileUtil.extName(filename); + File saveFile = FileUtil.file(tempPath, filename); + File pfxFile = null; + String newPassword = password; + FileUtil.del(saveFile); + if (StrUtil.equalsIgnoreCase(extName, suffix)) { + multipartFile.transferTo(saveFile); + pfxFile = saveFile; + } else if (StrUtil.equalsIgnoreCase(extName, "zip")) { + multipartFile.transferTo(saveFile); + ZipUtil.unzip(saveFile, tempPath); + // 找到 suffix + File[] files = tempPath.listFiles(); + Assert.notEmpty(files, I18nMessageUtil.get("i18n.no_files_in_zip.1af6")); + for (File file1 : files) { + String extName2 = FileUtil.extName(file1); + if (pfxFile == null && StrUtil.equalsIgnoreCase(extName2, suffix)) { + pfxFile = file1; + } + if (StrUtil.isEmpty(newPassword) && StrUtil.equalsIgnoreCase(extName2, "txt")) { + newPassword = FileUtil.readString(file1, CharsetUtil.CHARSET_UTF_8); + } + } + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.file_format_not_supported.eac4")); + } + Assert.notNull(pfxFile, StrUtil.format(I18nMessageUtil.get("i18n.no_file_found.6f1b"), suffix)); + try { + char[] passwordChars = StrUtil.emptyToDefault(newPassword, StrUtil.EMPTY).toCharArray(); + KeyStore keyStore = StrUtil.equals(suffix, "jks") ? KeyUtil.readJKSKeyStore(pfxFile, passwordChars) : KeyUtil.readPKCS12KeyStore(pfxFile, passwordChars); + Enumeration aliases = keyStore.aliases(); + // we are readin just one certificate. + if (aliases.hasMoreElements()) { + // + String keyAlias = aliases.nextElement(); + Certificate certificate = keyStore.getCertificate(keyAlias); + PrivateKey prikey = (PrivateKey) keyStore.getKey(keyAlias, passwordChars); + PublicKey pubkey = certificate.getPublicKey(); + certificateInfoService.testKey(pubkey, prikey); + // + X509Certificate cert = X509Certificate.getInstance(certificate.getEncoded()); + // 填充 + CertificateInfoModel certificateInfoModel = certificateInfoService.filling(cert); + certificateInfoModel.setKeyType(keyStore.getType()); + certificateInfoModel.setKeyAlias(keyAlias); + // 判断是否存在 + Assert.state(!certificateInfoService.checkRepeat(certificateInfoModel.getSerialNumberStr(), certificateInfoModel.getKeyType()), + I18nMessageUtil.get("i18n.certificate_already_exists.adf9")); + //certificateInfoService.checkRepeat(certificateInfoModel.getSerialNumberStr(), certificateInfoModel.getKeyType()); + + certificateInfoModel.setCertPassword(newPassword); + // + File file1 = certificateInfoService.getFilePath(certificateInfoModel); + FileUtil.mkdir(file1); + // 避免文件夹已经存在 + FileUtil.clean(file1); + FileUtil.move(pfxFile, file1, true); + return certificateInfoModel; + } else { + throw new IllegalStateException(I18nMessageUtil.get("i18n.certificate_has_no_aliases.3a2f")); + } + } catch (IllegalStateException | IllegalArgumentException e) { + throw Lombok.sneakyThrow(e); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_certificate_exception.3b6c"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.parse_certificate_unknown_error.c43c") + e.getMessage()); + } + } + + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(@ValidatorItem String id, HttpServletRequest request) throws IOException { + CertificateInfoModel model = certificateInfoService.getByKeyAndGlobal(id, request); + // 判断是否被 docker 使用 + MachineDockerModel machineDockerModel = new MachineDockerModel(); + machineDockerModel.setCertInfo(model.getSerialNumberStr() + StrUtil.COLON + model.getKeyType()); + long count = machineDockerServer.count(machineDockerModel); + Assert.state(count == 0, I18nMessageUtil.get("i18n.certificate_in_use_by_docker.dd63")); + // + File file = certificateInfoService.getFilePath(model); + FileUtil.del(file); + if (FileUtil.isEmpty(file.getParentFile())) { + // 一并删除避免保留空文件夹 + FileUtil.del(file.getParentFile()); + } + // + certificateInfoService.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@ValidatorItem String id, + String description, + HttpServletRequest request) throws IOException { + // 验证权限 + certificateInfoService.getByKeyAndGlobal(id, request); + + CertificateInfoModel certificateInfoModel = new CertificateInfoModel(); + certificateInfoModel.setId(id); + certificateInfoModel.setDescription(description); + // + certificateInfoModel.setWorkspaceId(certificateInfoService.covertGlobalWorkspace(request)); + certificateInfoService.updateById(certificateInfoModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 导出证书 + * + * @param id 项目id + */ + @GetMapping(value = "export", produces = MediaType.APPLICATION_JSON_VALUE) + public void export(@ValidatorItem String id, HttpServletRequest request, HttpServletResponse response) { + CertificateInfoModel model = certificateInfoService.getByKeyAndGlobal(id, request); + File file = certificateInfoService.getFilePath(model); + Assert.state(!FileUtil.isEmpty(file), I18nMessageUtil.get("i18n.certificate_file_missing.c663")); + + File userTempPath = serverConfig.getUserTempPath(); + File tempSave = FileUtil.file(userTempPath, IdUtil.fastSimpleUUID()); + try { + FileUtil.mkdir(tempSave); + String absolutePath = FileUtil.file(tempSave, model.getSerialNumberStr() + ".zip").getAbsolutePath(); + File zip = ZipUtil.zip(file.getAbsolutePath(), absolutePath, false); + ServletUtil.write(response, zip); + } finally { + FileUtil.del(tempSave); + } + } + + @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) { + // 判断参数 + ServerWhitelist configDeNewInstance = outGivingWhitelistService.getServerWhitelistData(request); + List whitelistServerOutGiving = configDeNewInstance.getOutGiving(); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, releasePathParent), I18nMessageUtil.get("i18n.select_correct_project_path_or_no_auth_configured.366a")); + Assert.hasText(releasePathSecondary, I18nMessageUtil.get("i18n.publish_file_second_level_directory_required.2f65")); + // 判断证书是否存在 + CertificateInfoModel model = certificateInfoService.getByKeyAndGlobal(id, request); + File file = certificateInfoService.getFilePath(model); + Assert.state(!FileUtil.isEmpty(file), I18nMessageUtil.get("i18n.certificate_file_missing.c663")); + File userTempPath = serverConfig.getUserTempPath(); + File tempSave = FileUtil.file(userTempPath, IdUtil.fastSimpleUUID()); + try { + // 压缩成 zip + FileUtil.mkdir(tempSave); + String absolutePath = FileUtil.file(tempSave, model.getSerialNumberStr() + ".zip").getAbsolutePath(); + File zip = ZipUtil.zip(file.getAbsolutePath(), absolutePath, false); + // 添加到文件中心 + String description = model.getSerialNumberStr() + Optional.ofNullable(model.getDescription()).map(s -> "," + s).orElse(StrUtil.EMPTY); + String fileId = fileStorageService.addFile(zip, 3, certificateInfoService.getCheckUserWorkspace(request), description, null, 1); + String releasePath = FileUtil.normalize(releasePathParent + StrUtil.SLASH + releasePathSecondary); + // 创建发布任务 + Map env = new HashMap<>(); + env.put("CERT_SERIAL_NUMBER_STR", model.getSerialNumberStr()); + 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/model/CertificateInfoModel.java b/modules/server/src/main/java/org/dromara/jpom/func/cert/model/CertificateInfoModel.java new file mode 100644 index 0000000000..28768307bd --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/cert/model/CertificateInfoModel.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.cert.model; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; + +/** + * @author bwcx_jzy + * @since 2023/3/22 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "CERTIFICATE_INFO", + nameKey = "i18n.certificate_info_table.fff8") +@Data +@NoArgsConstructor +public class CertificateInfoModel extends BaseWorkspaceModel { + /** + * 证书类型 + */ + private String keyType; + private String keyAlias; + /** + * 指纹 + */ + private String fingerprint; + /** + * 证书密码 + */ + private String certPassword; + /** + * 证书序列号 + */ + private String serialNumberStr; + /** + * 颁发者 DN 名称 + */ + private String issuerDnName; + /** + * 主题 DN 名称 + */ + private String subjectDnName; + /** + * 版本号 + */ + private Integer certVersion; + /** + * + */ + private String sigAlgOid; + /** + * 算法名 + */ + private String sigAlgName; + + /** + * 证书到期时间 + */ + private Long expirationTime; + /** + * 证书生效日期 + */ + private Long effectiveTime; + + private String description; + + /** + * 文件是否存在 + */ + @PropIgnore + private Boolean fileExists; + + @Override + protected boolean hasCreateUser() { + return true; + } +} 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 new file mode 100644 index 0000000000..952b70867e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/cert/service/CertificateInfoService.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.cert.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +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; +import cn.hutool.extra.servlet.ServletUtil; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.cert.model.CertificateInfoModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.h2db.BaseGlobalOrWorkspaceService; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.security.cert.CertificateEncodingException; +import javax.security.cert.X509Certificate; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedInputStream; +import java.io.File; +import java.math.BigInteger; +import java.nio.file.StandardCopyOption; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/3/22 + */ +@Service +@Slf4j +public class CertificateInfoService extends BaseGlobalOrWorkspaceService implements IStatusRecover { + + static { + GlobalBouncyCastleProvider.setUseBouncyCastle(false); + } + + private final JpomApplication jpomApplication; + private final MachineDockerServer machineDockerServer; + + public CertificateInfoService(JpomApplication jpomApplication, + MachineDockerServer machineDockerServer) { + this.jpomApplication = jpomApplication; + this.machineDockerServer = machineDockerServer; + } + + @Override + public int statusRecover() { + Entity entity = Entity.create(); + entity.set("tlsVerify", true); + entity.set("certInfo", null); + List dockerModels = machineDockerServer.listByEntity(entity); + if (CollUtil.isEmpty(dockerModels)) { + return 0; + } + for (MachineDockerModel dockerModel : dockerModels) { + try { + String generateCertPath = dockerModel.generateCertPath(); + File file = FileUtil.file(generateCertPath); + CertificateInfoModel certificateInfoModel = this.resolveX509(file, false); + if (!this.checkRepeat(certificateInfoModel.getSerialNumberStr(), certificateInfoModel.getKeyType())) { + certificateInfoModel.setWorkspaceId(ServerConst.WORKSPACE_GLOBAL); + certificateInfoModel.setCreateUser(UserModel.SYSTEM_ADMIN); + certificateInfoModel.setModifyUser(UserModel.SYSTEM_ADMIN); + String description = StrUtil.format(I18nMessageUtil.get("i18n.docker_asset_imported.0ab4"), dockerModel.getName()); + certificateInfoModel.setDescription(description); + this.insert(certificateInfoModel); + } + // 更新 + MachineDockerModel update = new MachineDockerModel(); + update.setId(dockerModel.getId()); + update.setCertInfo(certificateInfoModel.getSerialNumberStr() + StrUtil.COLON + certificateInfoModel.getKeyType()); + machineDockerServer.updateById(update); + log.info(I18nMessageUtil.get("i18n.docker_certificate_migrated.b3d3"), dockerModel.getName()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.migration_docker_cert_error.a5ea"), dockerModel.getName(), e); + } + } + return CollUtil.size(dockerModels); + } + + /** + * 解析 x509 证书 + * + * @param dir 证书目录 + * @param checkRepeat 是否判断重复 + * @return 证书对象 + */ + public CertificateInfoModel resolveX509(File dir, boolean checkRepeat) { + String[] keyNameSuffixes = new String[]{"key.pem", ".key"}; + String[] pemNameSuffixes = new String[]{".crt", ".cer", ".pem"}; + // 找到 对应的文件 + File[] files = dir.listFiles(); + Assert.notNull(files, I18nMessageUtil.get("i18n.no_files_in_zip.1af6")); + File keyFile = Arrays.stream(files).filter(file -> StrUtil.endWithAnyIgnoreCase(file.getName(), keyNameSuffixes)).findAny().orElse(null); + Assert.notNull(keyFile, I18nMessageUtil.get("i18n.private_key_not_found_in_zip.e103")); + // + try { + List fileList = Arrays.stream(files) + .filter(file -> !FileUtil.equals(file, keyFile)) + .filter(file -> StrUtil.endWithAnyIgnoreCase(file.getName(), pemNameSuffixes)) + .collect(Collectors.toList()); + Assert.notEmpty(fileList, I18nMessageUtil.get("i18n.no_certificate_files_found.ff6d")); + Assert.state(fileList.size() <= 2, I18nMessageUtil.get("i18n.multiple_certificate_files_found.bee3")); + // + List certificates = fileList.stream() + .map(file -> { + try (BufferedInputStream inputStream = FileUtil.getInputStream(file)) { + return KeyUtil.readX509Certificate(inputStream); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }) + .collect(Collectors.toList()); + Certificate certificate0 = certificates.get(0); + Certificate certificate1 = CollUtil.get(certificates, 1); + X509Certificate x509Certificate0 = X509Certificate.getInstance(certificate0.getEncoded()); + X509Certificate x509Certificate1 = certificate1 != null ? X509Certificate.getInstance(certificate1.getEncoded()) : null; + Principal issuerDN = x509Certificate0.getIssuerDN(); + Principal subjectDN = x509Certificate0.getSubjectDN(); + Assert.state(issuerDN != null && subjectDN != null, I18nMessageUtil.get("i18n.certificate_info_error_issuer_or_subject_DN_not_found.805d")); + int rootIndex = StrUtil.equals(issuerDN.getName(), subjectDN.getName()) ? 0 : 1; + // + PrivateKey privateKey; + try (BufferedInputStream inputStream = FileUtil.getInputStream(keyFile)) { + privateKey = PemUtil.readPemPrivateKey(inputStream); + } + // 验证证书公钥和私钥 + PublicKey publicKey = Optional.ofNullable(rootIndex == 0 ? certificate1 : certificate0) + .map(Certificate::getPublicKey) + .orElse(null); + this.testKey(publicKey, privateKey); + // + X509Certificate pubCert = (rootIndex == 0 ? x509Certificate1 : x509Certificate0); + if (certificate1 != null) { + // 验证证书链 + pubCert.verify((rootIndex == 0 ? certificate0 : certificate1).getPublicKey()); + } + // 填充 + CertificateInfoModel certificateInfoModel = this.filling(pubCert); + // 类型已经确定 + certificateInfoModel.setKeyType(certificate0.getType()); + // 判断是否存在 + if (checkRepeat) { + // this.checkRepeat(certificateInfoModel.getSerialNumberStr(), certificateInfoModel.getKeyType()); + Assert.state(!this.checkRepeat(certificateInfoModel.getSerialNumberStr(), certificateInfoModel.getKeyType()), + I18nMessageUtil.get("i18n.certificate_already_exists.adf9")); + } + // 保存文件 + File file1 = this.getFilePath(certificateInfoModel); + FileUtil.mkdir(file1); + // 避免文件夹已经存在 + FileUtil.clean(file1); + FileUtil.copyFile(keyFile, file1, StandardCopyOption.REPLACE_EXISTING); + for (File file : fileList) { + FileUtil.copyFile(file, file1, StandardCopyOption.REPLACE_EXISTING); + } + return certificateInfoModel; + } catch (IllegalStateException | IllegalArgumentException e) { + throw Lombok.sneakyThrow(e); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.parse_certificate_exception.3b6c"), e); + throw new IllegalStateException(I18nMessageUtil.get("i18n.parse_certificate_unknown_error.c43c") + e.getMessage()); + } + } + + public File getFilePath(CertificateInfoModel model) { + File certificatePath = FileUtil.file(jpomApplication.getDataPath(), "certificate"); + return FileUtil.file(certificatePath, model.getSerialNumberStr(), model.getKeyType()); + } + + public File getFilePath(String certTag) { + CertificateInfoModel byCertTag = this.getByCertTag(certTag); + if (byCertTag == null) { + return null; + } + return this.getFilePath(byCertTag); + } + + /** + * 判断证书是否存在 + * + * @param serialNumber 证书编号 + * @param type 证书类型 + */ + public boolean checkRepeat(String serialNumber, String type) { + CertificateInfoModel certificateInfoModel = new CertificateInfoModel(); + certificateInfoModel.setSerialNumberStr(serialNumber); + certificateInfoModel.setKeyType(type); + return this.exists(certificateInfoModel); + } + + /** + * 查询证书 + * + * @param certTag 证书标记 + */ + public CertificateInfoModel getByCertTag(String certTag) { + if (StrUtil.isEmpty(certTag)) { + return null; + } + List list = StrUtil.splitTrim(certTag, StrUtil.COLON); + String serialNumberStr = CollUtil.get(list, 0); + String keyType = CollUtil.get(list, 1); + Assert.hasText(serialNumberStr, I18nMessageUtil.get("i18n.certificate_serial_number_not_found.c8d1")); + Assert.hasText(keyType, I18nMessageUtil.get("i18n.certificate_type_not_found.6706")); + CertificateInfoModel certificateInfoModel = new CertificateInfoModel(); + certificateInfoModel.setSerialNumberStr(serialNumberStr); + certificateInfoModel.setKeyType(keyType); + List infoModels = this.listByBean(certificateInfoModel); + return CollUtil.getFirst(infoModels); + } + + /** + * 验证 公钥和私钥是否匹配 + * + * @param pubkey 公钥 + * @param privateKey 私钥 + */ + public void testKey(PublicKey pubkey, PrivateKey privateKey) { + Assert.state(pubkey != null && privateKey != null, I18nMessageUtil.get("i18n.public_key_or_private_key_does_not_exist.dc0d")); + // 测试字符串 + String str = I18nMessageUtil.get("i18n.greeting.5ecd"); + + // 判断算法名称是否包含 “RSA” 或 “EC” + String algorithm = pubkey.getAlgorithm(); + if (algorithm.contains(ServerConst.RSA)) { + RSA rsa = new RSA(privateKey, pubkey); + String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); + String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); + Assert.state(StrUtil.equals(str, decryptStr), I18nMessageUtil.get("i18n.public_key_and_private_key_mismatch.4aa2")); + } else if (algorithm.contains(ServerConst.EC)) { + ECIES ecies = new ECIES(privateKey, pubkey); + String encryptStr = ecies.encryptBase64(str, KeyType.PublicKey); + String decryptStr = StrUtil.utf8Str(ecies.decrypt(encryptStr, KeyType.PrivateKey)); + Assert.state(StrUtil.equals(str, decryptStr), I18nMessageUtil.get("i18n.public_key_and_private_key_mismatch.4aa2")); + } + } + + /** + * 获取证书信息 + * + * @param cert 证书公钥 + * @return data + */ + public CertificateInfoModel filling(X509Certificate cert) throws CertificateEncodingException { + + //String algorithm = cert.getPublicKey().getAlgorithm(); + CertificateInfoModel certificateInfoModel = new CertificateInfoModel(); + Date notBefore = cert.getNotBefore(); + Date notAfter = cert.getNotAfter(); + Optional.ofNullable(notAfter).ifPresent(date -> certificateInfoModel.setExpirationTime(date.getTime())); + Optional.ofNullable(notBefore).ifPresent(date -> certificateInfoModel.setEffectiveTime(date.getTime())); + BigInteger serialNumber = cert.getSerialNumber(); + // 使用 16 进制 + certificateInfoModel.setSerialNumberStr(serialNumber.toString(16)); + byte[] encoded = cert.getEncoded(); + certificateInfoModel.setFingerprint(SecureUtil.sha1().digestHex(encoded)); + // + int version = cert.getVersion(); + certificateInfoModel.setCertVersion(version); + Optional.ofNullable(cert.getSubjectDN()).ifPresent(principal -> certificateInfoModel.setSubjectDnName(principal.getName())); + Optional.ofNullable(cert.getIssuerDN()).ifPresent(principal -> certificateInfoModel.setIssuerDnName(principal.getName())); + String sigAlgOID = cert.getSigAlgOID(); + String sigAlgName = cert.getSigAlgName(); + certificateInfoModel.setSigAlgName(sigAlgName); + certificateInfoModel.setSigAlgOid(sigAlgOID); + return certificateInfoModel; + } + + public PageResultDto listPageAll(HttpServletRequest request) { + // 验证工作空间权限 + Map paramMap = ServletUtil.getParamMap(request); + paramMap.remove("workspaceId"); + return super.listPage(paramMap); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/controller/FileReleaseTaskController.java b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/FileReleaseTaskController.java new file mode 100644 index 0000000000..d8aa8aa767 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/FileReleaseTaskController.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.files.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +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.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.controller.outgiving.OutGivingWhitelistService; +import org.dromara.jpom.func.files.model.FileReleaseTaskLogModel; +import org.dromara.jpom.func.files.service.FileReleaseTaskService; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.AgentWhitelist; +import org.dromara.jpom.model.data.ServerWhitelist; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.util.FileUtils; +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.File; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/3/18 + */ +@RestController +@RequestMapping(value = "/file-storage/release-task") +@Feature(cls = ClassFeature.FILE_STORAGE_RELEASE) +public class FileReleaseTaskController extends BaseServerController { + + private final FileReleaseTaskService fileReleaseTaskService; + private final OutGivingWhitelistService outGivingWhitelistService; + private final ScriptServer scriptServer; + + public FileReleaseTaskController(FileReleaseTaskService fileReleaseTaskService, + OutGivingWhitelistService outGivingWhitelistService, + NodeService nodeService, + ScriptServer scriptServer) { + this.fileReleaseTaskService = fileReleaseTaskService; + this.outGivingWhitelistService = outGivingWhitelistService; + this.scriptServer = scriptServer; + this.nodeService = nodeService; + } + + + @PostMapping(value = "add-task", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage addTask(@ValidatorItem String fileId, + @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.getOutGiving(); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, releasePathParent), I18nMessageUtil.get("i18n.select_correct_project_path_or_no_auth_configured.366a")); + Assert.hasText(releasePathSecondary, I18nMessageUtil.get("i18n.publish_file_second_level_directory_required.2f65")); + + if (StrUtil.startWith(beforeScript, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(beforeScript, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKeyAndGlobal(scriptId, request, I18nMessageUtil.get("i18n.select_correct_pre_publish_script.d230")); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_pre_publish_script.d230")); + } + if (StrUtil.startWith(afterScript, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(afterScript, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKeyAndGlobal(scriptId, request, I18nMessageUtil.get("i18n.select_correct_post_publish_script.49d2")); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_post_publish_script.49d2")); + } + + String releasePath = FileUtil.normalize(releasePathParent + StrUtil.SLASH + releasePathSecondary); + + 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.EXECUTE) + public IJsonMessage reTask(@ValidatorItem String fileId, + @ValidatorItem String name, + @ValidatorItem(value = ValidatorRule.NUMBERS) int taskType, + @ValidatorItem String taskDataIds, + @ValidatorItem String parentTaskId, + String beforeScript, + String afterScript, + HttpServletRequest request) { + FileReleaseTaskLogModel parentTask = fileReleaseTaskService.getByKey(parentTaskId, request); + Assert.notNull(parentTask, I18nMessageUtil.get("i18n.parent_task_not_exist.ca1b")); + Integer fileType = parentTask.getFileType(); + fileType = ObjectUtil.defaultIfNull(fileType, 1); + return fileReleaseTaskService.addTask(fileId, fileType, name, taskType, taskDataIds, parentTask.getReleasePath(), beforeScript, afterScript, null, request); + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + // + PageResultDto listPage = fileReleaseTaskService.listPage(request); + return JsonMessage.success("", listPage); + } + + /** + * 取消任务 + * + * @param id 任务id + * @return json + */ + @GetMapping(value = "cancel-task", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage hasFile(@ValidatorItem String id, HttpServletRequest request) { + FileReleaseTaskLogModel taskLogModel = fileReleaseTaskService.getByKey(id, request); + Assert.notNull(taskLogModel, I18nMessageUtil.get("i18n.task_not_exist.47e9")); + fileReleaseTaskService.cancelTask(taskLogModel.getId()); + return JsonMessage.success(I18nMessageUtil.get("i18n.cancel_success.285f")); + } + + /** + * 查询任务 + * + * @param id 任务id + * @return json + */ + @GetMapping(value = "details", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage details(@ValidatorItem String id, HttpServletRequest request) { + FileReleaseTaskLogModel taskLogModel = fileReleaseTaskService.getByKey(id, request); + Assert.notNull(taskLogModel, I18nMessageUtil.get("i18n.task_not_exist.47e9")); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("taskData", taskLogModel); + FileReleaseTaskLogModel fileReleaseTaskLogModel = new FileReleaseTaskLogModel(); + fileReleaseTaskLogModel.setTaskId(taskLogModel.getId()); + List logModels = fileReleaseTaskService.listByBean(fileReleaseTaskLogModel); + jsonObject.put("taskList", logModels); + return JsonMessage.success(I18nMessageUtil.get("i18n.cancel_success.285f"), jsonObject); + } + + /** + * 删除任务 + * + * @param id 任务id + * @return json + */ + @GetMapping(value = "delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage delete(@ValidatorItem String id, HttpServletRequest request) { + FileReleaseTaskLogModel taskLogModel = fileReleaseTaskService.getByKey(id, request); + Assert.notNull(taskLogModel, I18nMessageUtil.get("i18n.task_not_exist.47e9")); + + FileReleaseTaskLogModel fileReleaseTaskLogModel = new FileReleaseTaskLogModel(); + fileReleaseTaskLogModel.setTaskId(taskLogModel.getId()); + List logModels = fileReleaseTaskService.listByBean(fileReleaseTaskLogModel); + if (logModels != null) { + List ids = logModels.stream() + .map(logModel -> { + File file = fileReleaseTaskService.logFile(logModel); + FileUtil.del(file); + return logModel.getId(); + }) + .collect(Collectors.toList()); + fileReleaseTaskService.delByKey(ids, null); + } + File taskDir = fileReleaseTaskService.logTaskDir(taskLogModel); + FileUtil.del(taskDir); + // + fileReleaseTaskService.delByKey(taskLogModel.getId()); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 获取日志 + * + * @param id id + * @param line 需要获取的行号 + * @return json + */ + @GetMapping(value = "log-list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage log(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.no_data.1ac0") String id, + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "i18n.line_number_error.c65d") int line, + HttpServletRequest request) { + FileReleaseTaskLogModel item = fileReleaseTaskService.getByKey(id, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + File file = fileReleaseTaskService.logFile(item); + if (!FileUtil.isFile(file)) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.no_log_info_or_log_file_error.2c25")); + } + + JSONObject data = FileUtils.readLogFile(file, line); + // 运行中 + Integer status = item.getStatus(); + data.put("run", status != null && (status == 0 || status == 1)); + data.put("status", item.getStatus()); + return JsonMessage.success("", data); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/controller/FileStorageController.java b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/FileStorageController.java new file mode 100644 index 0000000000..3a7b0447f0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/FileStorageController.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.files.controller; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Validator; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.controller.outgiving.OutGivingWhitelistService; +import org.dromara.jpom.func.files.model.FileStorageModel; +import org.dromara.jpom.func.files.service.FileStorageService; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.ServerWhitelist; +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.dromara.jpom.system.ServerConfig; +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 org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2023/3/16 + */ +@RestController +@RequestMapping(value = "/file-storage") +@Feature(cls = ClassFeature.FILE_STORAGE) +public class FileStorageController extends BaseServerController { + private final ServerConfig serverConfig; + private final FileStorageService fileStorageService; + private final OutGivingWhitelistService outGivingWhitelistService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public FileStorageController(ServerConfig serverConfig, + FileStorageService fileStorageService, + OutGivingWhitelistService outGivingWhitelistService, + TriggerTokenLogServer triggerTokenLogServer) { + this.serverConfig = serverConfig; + this.fileStorageService = fileStorageService; + this.outGivingWhitelistService = outGivingWhitelistService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + File storageSavePath = serverConfig.fileStorageSavePath(); + // + PageResultDto listPage = fileStorageService.listPage(request); + listPage.each(fileStorageModel -> { + File file = FileUtil.file(storageSavePath, fileStorageModel.getPath()); + fileStorageModel.setExists(FileUtil.isFile(file)); + }); + return JsonMessage.success("", listPage); + } + + /** + * 判断是否存在文件 + * + * @param fileSumMd5 文件 md5 + * @return json + */ + @GetMapping(value = "has-file", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage hasFile(@ValidatorItem String fileSumMd5) { + FileStorageModel storageModel = fileStorageService.getByKey(fileSumMd5); + return JsonMessage.success("", storageModel); + } + + /** + * 上传分片 + * + * @param file 文件对象 + * @param sliceId 分片id + * @param totalSlice 总分片 + * @param nowSlice 当前分片 + * @param fileSumMd5 文件 md5 + * @return json + * @throws IOException io + */ + @PostMapping(value = "upload-sharding", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD, log = false) + public IJsonMessage uploadSharding(MultipartFile file, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5) throws IOException { + File userTempPath = serverConfig.getUserTempPath(); + this.uploadSharding(file, userTempPath.getAbsolutePath(), sliceId, totalSlice, nowSlice, fileSumMd5); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + /** + * 合并文件分片 + * + * @param sliceId 分片id + * @param totalSlice 增分片数 + * @param fileSumMd5 文件 md5 + * @return json + * @throws IOException 异常 + */ + @PostMapping(value = "upload-sharding-merge", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.UPLOAD) + public IJsonMessage uploadMerge(String sliceId, + Integer totalSlice, + String fileSumMd5, + Integer keepDay, + String description, + String aliasCode, + HttpServletRequest request) throws IOException { + Opt.ofBlankAble(aliasCode).ifPresent(s -> Validator.validateGeneral(s, I18nMessageUtil.get("i18n.alias_code_validation.8b99"))); + File storageSavePath = serverConfig.fileStorageSavePath(); + // 验证文件 + FileStorageModel fileStorageModel1 = fileStorageService.getByKey(fileSumMd5); + if (fileStorageModel1 != null) { + // 如果存在记录,判断文件是否存在 + File file = FileUtil.file(storageSavePath, fileStorageModel1.getPath()); + Assert.state(!FileUtil.exist(file), I18nMessageUtil.get("i18n.file_already_exists.d60c")); + } + // 合并文件 + File userTempPath = serverConfig.getUserTempPath(); + File successFile = this.shardingTryMerge(userTempPath.getAbsolutePath(), sliceId, totalSlice, fileSumMd5); + String extName = FileUtil.extName(successFile); + String path = StrUtil.format("/{}/{}.{}", DateTime.now().toString(DatePattern.PURE_DATE_FORMAT), fileSumMd5, extName); + + File fileStorageFile = FileUtil.file(storageSavePath, path); + FileUtil.mkParentDirs(fileStorageFile); + FileUtil.move(successFile, fileStorageFile, true); + // 保存 + FileStorageModel fileStorageModel = new FileStorageModel(); + fileStorageModel.setId(fileSumMd5); + fileStorageModel.setName(successFile.getName()); + fileStorageModel.setDescription(description); + fileStorageModel.setAliasCode(aliasCode); + fileStorageModel.setExtName(extName); + fileStorageModel.setPath(path); + fileStorageModel.setSize(FileUtil.size(fileStorageFile)); + fileStorageModel.setSource(0); + // + fileStorageModel.setWorkspaceId(fileStorageService.covertGlobalWorkspace(request)); + fileStorageModel.validUntil(keepDay, null); + // + fileStorageService.insert(fileStorageModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.upload_success.a769")); + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@ValidatorItem String id, + @ValidatorItem String name, + Integer keepDay, + String description, + String aliasCode, + HttpServletRequest request) throws IOException { + Opt.ofBlankAble(aliasCode).ifPresent(s -> Validator.validateGeneral(s, I18nMessageUtil.get("i18n.alias_code_validation.8b99"))); + FileStorageModel storageModel = fileStorageService.getByKeyAndGlobal(id, request); + + FileStorageModel fileStorageModel = new FileStorageModel(); + fileStorageModel.setId(id); + fileStorageModel.setName(name); + fileStorageModel.setAliasCode(aliasCode); + fileStorageModel.setDescription(description); + // + fileStorageModel.setWorkspaceId(fileStorageService.covertGlobalWorkspace(request)); + // + fileStorageModel.validUntil(keepDay, storageModel.getCreateTimeMillis()); + fileStorageService.updateById(fileStorageModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, String ids, HttpServletRequest request) throws IOException { + this.delItem(id, request); + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String s : list) { + this.delItem(s, request); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + private void delItem(String id, HttpServletRequest request) { + if (StrUtil.isEmpty(id)) { + return; + } + FileStorageModel storageModel = fileStorageService.getByKeyAndGlobal(id, request); + if (storageModel == null) { + return; + } + // + File storageSavePath = serverConfig.fileStorageSavePath(); + File fileStorageFile = FileUtil.file(storageSavePath, storageModel.getPath()); + FileUtil.del(fileStorageFile); + // + fileStorageService.delByKey(id); + } + + /** + * 远程下载 + * + * @param url 远程 url + * @param keepDay 保留天数 + * @param description 描述 + * @param global 是否全局共享 + * @param request 请求 + * @return json + * @throws IOException io + */ + @PostMapping(value = "remote-download", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.REMOTE_DOWNLOAD) + public IJsonMessage download( + @ValidatorItem String url, + Integer keepDay, + String description, + String aliasCode, + Boolean global, + HttpServletRequest request) throws IOException { + Opt.ofBlankAble(aliasCode).ifPresent(s -> Validator.validateGeneral(s, I18nMessageUtil.get("i18n.alias_code_validation.8b99"))); + // 验证远程 地址 + ServerWhitelist whitelist = outGivingWhitelistService.getServerWhitelistData(request); + whitelist.checkAllowRemoteDownloadHost(url); + String workspace = fileStorageService.getCheckUserWorkspace(request); + fileStorageService.download(url, global, workspace, keepDay, description, aliasCode); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_async_download.78cc")); + } + + /** + * 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(); + // 查询当前工作空间 + FileStorageModel item = fileStorageService.getByKey(id, request); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_file_info.db01")); + // + FileStorageModel updateInfo; + if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { + updateInfo = new FileStorageModel(); + updateInfo.setId(id); + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), fileStorageService.typeName(), + item.getId(), user.getId())); + fileStorageService.updateById(updateInfo); + // 避免无法查看发片下载地址 + updateInfo.setAliasCode(item.getAliasCode()); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, map); + } + + private Map getBuildToken(FileStorageModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); + Map map = new HashMap<>(10); + { + String url = ServerOpenApi.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)); + } + if (StrUtil.isNotEmpty(item.getAliasCode())) { + String url = ServerOpenApi.FILE_STORAGE_DOWNLOAD. + replace("{id}", item.getAliasCode()). + replace("{token}", item.getTriggerToken()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + map.put("triggerAliasDownloadUrl", FileUtil.normalize(triggerBuildUrl)); + } + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } +} 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 0000000000..5e7303be69 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/StaticFileStorageController.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.i18n.I18nMessageUtil; +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(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 删除数据 + * + * @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); + String string = I18nMessageUtil.get("i18n.reset_success.faa3"); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : string, 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(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + /** + * 判断是否存在文件 + * + * @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, I18nMessageUtil.get("i18n.scanning_in_progress.7444")); + String workspace = fileStorageService.getCheckUserWorkspace(request); + staticFileStorageService.scanByWorkspace(workspace); + return JsonMessage.success(I18nMessageUtil.get("i18n.scan_succeeded.7975")); + } +} 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 new file mode 100644 index 0000000000..8cb3eeb998 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileReleaseTaskLogModel.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.files.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; + +/** + * @author bwcx_jzy + * @since 2023/3/18 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "FILE_RELEASE_TASK_LOG", + nameKey = "i18n.file_publish_task_record.edc4", parents = FileStorageModel.class) +@Data +@NoArgsConstructor +public class FileReleaseTaskLogModel extends BaseWorkspaceModel { + /** + * 父级任务id + */ + public static final String TASK_ROOT_ID = "task-root"; + + /** + * 任务名 + */ + private String name; + /** + * 任务id + * + * @see FileReleaseTaskLogModel#TASK_ROOT_ID + */ + private String taskId; + /** + * 文件 id + * + * @see FileStorageModel#getId() + */ + private String fileId; + /** + * 文件来源类型 + * 1 文件中心 + * 2 静态文件 + */ + private Integer fileType; + /** + * 任务类型 0 ssh 1 节点 + */ + private Integer taskType; + /** + * 发布路径 + */ + private String releasePath; + /** + * 任务关联的数据id + */ + private String taskDataId; + /** + * 任务状态, 0 等待开始 1 进行中 2 任务结束 3 失败 4 取消任务 + */ + private Integer status; + /** + * 状态描述 + */ + private String statusMsg; + /** + * 发布之前的脚本 + */ + private String beforeScript; + /** + * 发布后的脚本 + */ + private String afterScript; +} 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 new file mode 100644 index 0000000000..5d114c1a9d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileStorageModel.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.files.model; + +import cn.hutool.core.annotation.PropIgnore; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +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.BaseWorkspaceModel; + +/** + * @author bwcx_jzy + * @since 2023/3/16 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "FILE_STORAGE", + nameKey = "i18n.file_management_center.0f5f") +@Data +@NoArgsConstructor +public class FileStorageModel extends BaseWorkspaceModel implements IFileStorage { + + @Override + public void setId(String id) { + // 文件 md5 + super.setId(id); + } + + /** + * 文件名 + */ + private String name; + + public void setName(String name) { + this.name = StrUtil.maxLength(name, 240); + } + + /** + * 文件大小 + */ + private Long size; + /** + * 文件描述 + */ + private String description; + /** + * 文件来源 0 上传 1 构建 2 下载 3 证书 + */ + private Integer source; + /** + * 文件有效期(毫秒) + */ + private Long validUntil; + /** + * 文件路径 + */ + private String path; + /** + * 文件扩展名 + */ + private String extName; + /** + * 只有下载的时候才使用本字段 + *

+ * 0 下载中 1 下载完成 2 下载异常 + */ + private Integer status; + /** + * 进度描述 + */ + private String progressDesc; + /** + * 文件是否存在 + */ + @PropIgnore + private Boolean exists; + /** + * 触发器 token + */ + private String triggerToken; + + /** + * 别名码 + */ + private String aliasCode; + + /** + * 设置保留天数的过期时间 + * + * @param keepDay 保留天数 + * @param startTime 文件开始的时间 + */ + public void validUntil(Integer keepDay, Long startTime) { + int keepDayInt = ObjectUtil.defaultIfNull(keepDay, 3650); + keepDayInt = Math.max(keepDayInt, 1); + DateTime dateTime = new DateTime(ObjectUtil.defaultIfNull(startTime, SystemClock.now())).offset(DateField.DAY_OF_YEAR, keepDayInt); + this.setValidUntil(DateUtil.endOfDay(dateTime).getTime()); + } + + @Override + protected boolean hasCreateUser() { + return true; + } +} 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 0000000000..1b3940b73d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/IFileStorage.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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 0000000000..45a9736eb5 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/StaticFileStorageModel.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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", + nameKey = "i18n.static_file_management.6ac2") +@Data +@NoArgsConstructor +public class StaticFileStorageModel extends BaseUserModifyDbModel implements IFileStorage { + + /** + * 文件名 + */ + private String name; + + /** + * 只保留 100 字符 + * + * @param name 名称 + */ + public void setName(String name) { + this.name = StrUtil.maxLength(name, 100); + } + + /** + * 文件大小 + */ + 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 new file mode 100644 index 0000000000..4fdd99aab4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileReleaseTaskService.java @@ -0,0 +1,633 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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; +import cn.hutool.core.util.*; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.ssh.JschUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +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; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.plugins.JschUtils; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.transport.*; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.MySftp; +import org.dromara.jpom.util.StrictSyncFinisher; +import org.dromara.jpom.util.SyncFinisherUtil; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/3/18 + */ +@Service +@Slf4j +public class FileReleaseTaskService extends BaseWorkspaceService implements IStatusRecover { + + private final SshService sshService; + private final JpomApplication jpomApplication; + private final WorkspaceEnvVarService workspaceEnvVarService; + private final NodeService nodeService; + private final BuildExtConfig buildExtConfig; + private final ServerConfig serverConfig; + private final FileStorageService fileStorageService; + private final StaticFileStorageService staticFileStorageService; + private final ScriptServer scriptServer; + + private final Map cancelTag = new SafeConcurrentHashMap<>(); + + public FileReleaseTaskService(SshService sshService, + JpomApplication jpomApplication, + WorkspaceEnvVarService workspaceEnvVarService, + NodeService nodeService, + BuildExtConfig buildExtConfig, + ServerConfig serverConfig, + FileStorageService fileStorageService, + StaticFileStorageService staticFileStorageService, + ScriptServer scriptServer) { + this.sshService = sshService; + this.jpomApplication = jpomApplication; + this.workspaceEnvVarService = workspaceEnvVarService; + this.nodeService = nodeService; + this.buildExtConfig = buildExtConfig; + this.serverConfig = serverConfig; + this.fileStorageService = fileStorageService; + this.staticFileStorageService = staticFileStorageService; + this.scriptServer = scriptServer; + } + + /** + * 获取任务记录(只查看主任务) + * + * @param request 请求对象 + * @return page + */ + @Override + public PageResultDto listPage(HttpServletRequest request) { + // 验证工作空间权限 + Map paramMap = ServletUtil.getParamMap(request); + String workspaceId = this.getCheckUserWorkspace(request); + paramMap.put("workspaceId", workspaceId); + paramMap.put("taskId", FileReleaseTaskLogModel.TASK_ROOT_ID); + return super.listPage(paramMap); + } + + /** + * 获取文件中心的文件 + * + * @param fileId 文件id + * @param request 请求 + * @return tuple 0 文件 1 文件信息 + */ + private Tuple getFileStorage(String fileId, HttpServletRequest request) { + FileStorageModel storageModel = fileStorageService.getByKey(fileId, request); + Assert.notNull(storageModel, I18nMessageUtil.get("i18n.file_not_exist.ea6a")); + File storageSavePath = serverConfig.fileStorageSavePath(); + File file = FileUtil.file(storageSavePath, storageModel.getPath()); + Assert.state(FileUtil.isFile(file), I18nMessageUtil.get("i18n.file_missing_cannot_publish.3818")); + + return new Tuple(file, storageModel); + } + + + /** + * 获取静态文件中心的文件 + * + * @param fileId 文件id + * @param request 请求 + * @return tuple 0 文件 1 文件信息 + */ + private Tuple getStaticFileStorage(String fileId, HttpServletRequest request) { + StaticFileStorageModel storageModel = staticFileStorageService.getByKey(fileId); + String workspaceId = getWorkspaceId(request); + staticFileStorageService.checkStaticDir(storageModel, workspaceId); + File file = FileUtil.file(storageModel.getAbsolutePath()); + Assert.state(FileUtil.isFile(file), I18nMessageUtil.get("i18n.file_missing_cannot_publish.3818")); + return new Tuple(file, storageModel); + } + + /** + * 创建任务 + * + * @param fileId 文件id + * @param name 名称 + * @param taskType 任务类型 + * @param taskDataIds 任务关联的数据id + * @param releasePath 发布目录 + * @param beforeScript 发布前脚本 + * @param afterScript 发布后的脚本 + * @param request 请求 + * @return json + */ + public IJsonMessage addTask(String fileId, + Integer fileType, + String name, + int taskType, + String taskDataIds, + String releasePath, + String beforeScript, + String afterScript, + Map env, + HttpServletRequest request) { + Tuple tuple; + switch (fileType) { + case 1: + tuple = this.getFileStorage(fileId, request); + break; + case 2: + tuple = this.getStaticFileStorage(fileId, request); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + fileType); + } + File file = tuple.get(0); + IFileStorage storageModel = tuple.get(1); + // + List list; + if (taskType == 0) { + list = StrUtil.splitTrim(taskDataIds, StrUtil.COMMA); + list = list.stream().filter(s -> sshService.exists(new SshModel(s))).collect(Collectors.toList()); + Assert.notEmpty(list, I18nMessageUtil.get("i18n.select_correct_ssh.aa93")); + } else if (taskType == 1) { + list = StrUtil.splitTrim(taskDataIds, StrUtil.COMMA); + list = list.stream().filter(s -> nodeService.exists(new NodeModel(s))).collect(Collectors.toList()); + Assert.notEmpty(list, I18nMessageUtil.get("i18n.select_correct_node.1b4e")); + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_method.a1de")); + } + // 生成任务id + FileReleaseTaskLogModel taskRoot = new FileReleaseTaskLogModel(); + taskRoot.setId(IdUtil.fastSimpleUUID()); + taskRoot.setTaskId(FileReleaseTaskLogModel.TASK_ROOT_ID); + taskRoot.setTaskDataId(FileReleaseTaskLogModel.TASK_ROOT_ID); + taskRoot.setName(name); + taskRoot.setFileId(fileId); + taskRoot.setFileType(fileType); + taskRoot.setStatus(0); + taskRoot.setTaskType(taskType); + taskRoot.setReleasePath(releasePath); + taskRoot.setAfterScript(afterScript); + taskRoot.setBeforeScript(beforeScript); + this.insert(taskRoot); + // 子任务列表 + for (String dataId : list) { + FileReleaseTaskLogModel releaseTaskLogModel = new FileReleaseTaskLogModel(); + releaseTaskLogModel.setTaskId(taskRoot.getId()); + releaseTaskLogModel.setTaskDataId(dataId); + releaseTaskLogModel.setName(name); + releaseTaskLogModel.setFileId(fileId); + releaseTaskLogModel.setFileType(fileType); + releaseTaskLogModel.setStatus(0); + releaseTaskLogModel.setTaskType(taskType); + releaseTaskLogModel.setReleasePath(taskRoot.getReleasePath()); + this.insert(releaseTaskLogModel); + } + this.startTask(taskRoot.getId(), file, env, storageModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.create_success.04a6")); + } + + /** + * 开始任务d + * + * @param taskId 任务id + * @param storageSaveFile 文件 + */ + private void startTask(String taskId, File storageSaveFile, Map env, IFileStorage storageModel) { + FileReleaseTaskLogModel taskRoot = this.getByKey(taskId); + Assert.notNull(taskRoot, I18nMessageUtil.get("i18n.parent_task_not_found.bac1")); + // + FileReleaseTaskLogModel fileReleaseTaskLogModel = new FileReleaseTaskLogModel(); + fileReleaseTaskLogModel.setTaskId(taskId); + List logModels = this.listByBean(fileReleaseTaskLogModel); + Assert.notEmpty(logModels, I18nMessageUtil.get("i18n.no_corresponding_task.3be5")); + // + EnvironmentMapBuilder environmentMapBuilder = workspaceEnvVarService.getEnv(taskRoot.getWorkspaceId()); + Optional.ofNullable(env).ifPresent(environmentMapBuilder::putStr); + environmentMapBuilder.put("TASK_ID", taskRoot.getTaskId()); + environmentMapBuilder.put("FILE_ID", taskRoot.getFileId()); + environmentMapBuilder.put("FILE_NAME", storageModel.getName()); + environmentMapBuilder.put("FILE_EXT_NAME", storageModel.getExtName()); + // + String syncFinisherId = "file-release:" + taskId; + StrictSyncFinisher strictSyncFinisher = SyncFinisherUtil.create(syncFinisherId, logModels.size()); + Integer taskType = taskRoot.getTaskType(); + if (taskType == 0) { + crateTaskSshWork(logModels, strictSyncFinisher, taskRoot, environmentMapBuilder, storageSaveFile); + } else if (taskType == 1) { + // 节点 + crateTaskNodeWork(logModels, strictSyncFinisher, taskRoot, environmentMapBuilder, storageSaveFile, storageModel); + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_method.a1de")); + } + I18nThreadUtil.execute(() -> { + try { + strictSyncFinisher.start(); + if (cancelTag.containsKey(taskId)) { + // 任务来源被取消 + this.cancelTaskUpdate(taskId); + } else { + this.updateRootStatus(taskId, 2, I18nMessageUtil.get("i18n.normal_end.3bfe")); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.publish_task_execution_failed.b075"), e); + updateRootStatus(taskId, 3, e.getMessage()); + } finally { + SyncFinisherUtil.close(syncFinisherId); + cancelTag.remove(taskId); + } + }); + } + + /** + * 取消任务 + * + * @param taskId 任务id + */ + public void cancelTask(String taskId) { + String syncFinisherId = "file-release:" + taskId; + SyncFinisherUtil.cancel(syncFinisherId); + // 异步线程无法标记 ,同步监听线程去操作 + cancelTag.put(taskId, taskId); + } + + private void cancelTaskUpdate(String taskId) { + // 将未完成的任务标记为取消 + FileReleaseTaskLogModel update = new FileReleaseTaskLogModel(); + update.setStatus(4); + update.setStatusMsg(I18nMessageUtil.get("i18n.manual_cancel_task.e592")); + Entity updateEntity = this.dataBeanToEntity(update); + // + Entity where = Entity.create().set("taskId", taskId).set("status", CollUtil.newArrayList(0, 1)); + this.update(updateEntity, where); + this.updateRootStatus(taskId, 4, I18nMessageUtil.get("i18n.manual_cancel_task.e592")); + } + + /** + * 创建 节点 发布任务 + * + * @param values 需要发布的任务列表 + * @param strictSyncFinisher 线程同步器 + * @param taskRoot 任务 + * @param environmentMapBuilder 环境变量 + * @param storageSaveFile 文件 + */ + private void crateTaskNodeWork(Collection values, + StrictSyncFinisher strictSyncFinisher, + FileReleaseTaskLogModel taskRoot, + EnvironmentMapBuilder environmentMapBuilder, + File storageSaveFile, + IFileStorage storageModel) { + String taskId = taskRoot.getId(); + for (FileReleaseTaskLogModel model : values) { + model.setAfterScript(taskRoot.getAfterScript()); + model.setBeforeScript(taskRoot.getBeforeScript()); + strictSyncFinisher.addWorker(() -> { + String modelId = model.getId(); + LogRecorder logRecorder = null; + try { + this.updateStatus(taskId, modelId, 1, I18nMessageUtil.get("i18n.start_publishing_file.a14e")); + File logFile = logFile(model); + logRecorder = LogRecorder.builder().file(logFile).charset(CharsetUtil.CHARSET_UTF_8).build(); + NodeModel item = nodeService.getByKey(model.getTaskDataId()); + if (item == null) { + logRecorder.systemError(I18nMessageUtil.get("i18n.no_node_entry_found.b1ef"), model.getTaskDataId()); + this.updateStatus(taskId, modelId, 3, StrUtil.format(I18nMessageUtil.get("i18n.no_node_entry_found.b1ef"), model.getTaskDataId())); + return; + } + + String releasePath = model.getReleasePath(); + String beforeScript = model.getBeforeScript(); + if (StrUtil.isNotEmpty(beforeScript)) { + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_upload_pre_command.fb5c")); + if (StrUtil.startWith(beforeScript, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(beforeScript, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKey(scriptId); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + beforeScript = keyAndGlobal.getContext(); + logRecorder.system(I18nMessageUtil.get("i18n.introducing_script_content.a55b"), keyAndGlobal.getName(), scriptId); + } + this.runNodeScript(beforeScript, item, logRecorder, modelId, environmentMapBuilder, releasePath); + } + logRecorder.system("{} start file upload", item.getName()); + // 上传文件 + JSONObject data = new JSONObject(); + data.put("path", releasePath); + Set 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); + return NodeForward.request(item, NodeUrl.Manage_File_Sharding_Merge2, sliceData); + }, + (total, progressSize) -> { + + double progressPercentage = Math.floor(((float) progressSize / total) * 100); + int progressRange = (int) Math.floor(progressPercentage / buildExtConfig.getLogReduceProgressRatio()); + if (progressRangeList.add(progressRange)) { + String info = I18nMessageUtil.get("i18n.upload_progress_template.ac3f"); + finalLogRecorder.system(info, + FileUtil.readableFileSize(progressSize), FileUtil.readableFileSize(total), + NumberUtil.formatPercent(((float) progressSize / total), 0)); + } + }); + if (!jsonMessage.success()) { + throw new IllegalStateException(I18nMessageUtil.get("i18n.file_upload_failed.462e") + jsonMessage); + } + logRecorder.system("{} file upload done", item.getName()); + + String afterScript = model.getAfterScript(); + if (StrUtil.isNotEmpty(afterScript)) { + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_upload_post_command.1c1b")); + if (StrUtil.startWith(afterScript, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(afterScript, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKey(scriptId); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + afterScript = keyAndGlobal.getContext(); + logRecorder.system(I18nMessageUtil.get("i18n.introducing_script_content.a55b"), keyAndGlobal.getName(), scriptId); + } + this.runNodeScript(afterScript, item, logRecorder, modelId, environmentMapBuilder, releasePath); + } + this.updateStatus(taskId, modelId, 2, I18nMessageUtil.get("i18n.publish_success.2fff")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.publish_task_execution_exception.c296"), e); + updateStatus(taskId, modelId, 3, e.getMessage()); + } finally { + IoUtil.close(logRecorder); + } + }); + } + } + + /** + * 执行节点脚本 + * + * @param content 脚本内容 + * @param model 节点 + * @param logRecorder 日志记录器 + * @param id 任务id + * @param environmentMapBuilder 环境变量 + * @param path 执行路径 + * @throws IOException io + */ + private void runNodeScript(String content, NodeModel model, LogRecorder logRecorder, String id, EnvironmentMapBuilder environmentMapBuilder, String path) throws IOException { + INodeInfo nodeInfo = NodeForward.parseNodeInfo(model); + IUrlItem urlItem = NodeForward.parseUrlItem(nodeInfo, model.getWorkspaceId(), NodeUrl.FreeScriptRun, DataContentType.FORM_URLENCODED); + try (IProxyWebSocket proxySession = TransportServerFactory.get().websocket(nodeInfo, urlItem)) { + proxySession.onMessage(s -> { + if (StrUtil.equals(s, "JPOM_SYSTEM_TAG:" + id)) { + try { + proxySession.close(); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.close_session_exception.3491"), e); + logRecorder.systemError(I18nMessageUtil.get("i18n.close_session_exception_with_detail.85f0"), e.getMessage()); + } + return; + } + logRecorder.info(s); + }); + // 等待链接 + proxySession.connectBlocking(); + // 发送操作消息 + JSONObject jsonObject = new JSONObject(); + jsonObject.put("tag", id); + jsonObject.put("path", path); + jsonObject.put("environment", environmentMapBuilder.toDataJson()); + jsonObject.put("content", content); + proxySession.send(jsonObject.toString()); + // 阻塞 + while (proxySession.isConnected()) { + ThreadUtil.sleep(500, TimeUnit.MILLISECONDS); + } + } + } + + /** + * 创建 ssh 发布任务 + * + * @param values 需要发布的任务列表 + * @param strictSyncFinisher 线程同步器 + * @param taskRoot 任务 + * @param environmentMapBuilder 环境变量 + * @param storageSaveFile 文件 + */ + private void crateTaskSshWork(Collection values, + StrictSyncFinisher strictSyncFinisher, + FileReleaseTaskLogModel taskRoot, + EnvironmentMapBuilder environmentMapBuilder, + File storageSaveFile) { + String taskId = taskRoot.getId(); + for (FileReleaseTaskLogModel model : values) { + model.setAfterScript(taskRoot.getAfterScript()); + model.setBeforeScript(taskRoot.getBeforeScript()); + strictSyncFinisher.addWorker(() -> { + String modelId = model.getId(); + Session session = null; + ChannelSftp channelSftp = null; + LogRecorder logRecorder = null; + try { + this.updateStatus(taskId, modelId, 1, I18nMessageUtil.get("i18n.start_publishing_file.a14e")); + File logFile = logFile(model); + logRecorder = LogRecorder.builder().file(logFile).charset(CharsetUtil.CHARSET_UTF_8).build(); + SshModel item = sshService.getByKey(model.getTaskDataId()); + if (item == null) { + logRecorder.systemError(I18nMessageUtil.get("i18n.no_ssh_entry_found.d0e1"), model.getTaskDataId()); + this.updateStatus(taskId, modelId, 3, StrUtil.format(I18nMessageUtil.get("i18n.no_ssh_entry_found.d0e1"), model.getTaskDataId())); + return; + } + MachineSshModel machineSshModel = sshService.getMachineSshModel(item); + Charset charset = machineSshModel.charset(); + int timeout = machineSshModel.timeout(); + session = sshService.getSessionByModel(machineSshModel); + Map environment = environmentMapBuilder.environment(); + environmentMapBuilder.eachStr(logRecorder::system); + String beforeScript = model.getBeforeScript(); + if (StrUtil.isNotEmpty(beforeScript)) { + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_upload_pre_command.fb5c")); + if (StrUtil.startWith(beforeScript, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(beforeScript, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKey(scriptId); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + beforeScript = keyAndGlobal.getContext(); + logRecorder.system(I18nMessageUtil.get("i18n.introducing_script_content.a55b"), keyAndGlobal.getName(), scriptId); + } + JschUtils.execCallbackLine(session, charset, timeout, beforeScript, StrUtil.EMPTY, environment, logRecorder::info); + } + logRecorder.system("{} start ftp upload", item.getName()); + + MySftp.ProgressMonitor sftpProgressMonitor = sshService.createProgressMonitor(logRecorder); + // 不需要关闭资源,因为共用会话 + MySftp sftp = new MySftp(session, charset, timeout, sftpProgressMonitor); + channelSftp = sftp.getClient(); + String releasePath = model.getReleasePath(); + sftp.syncUpload(storageSaveFile, releasePath); + logRecorder.system("{} ftp upload done", item.getName()); + + String afterScript = model.getAfterScript(); + if (StrUtil.isNotEmpty(afterScript)) { + logRecorder.system(I18nMessageUtil.get("i18n.start_executing_upload_post_command.1c1b")); + if (StrUtil.startWith(afterScript, ServerConst.REF_SCRIPT)) { + String scriptId = StrUtil.removePrefix(afterScript, ServerConst.REF_SCRIPT); + ScriptModel keyAndGlobal = scriptServer.getByKey(scriptId); + Assert.notNull(keyAndGlobal, I18nMessageUtil.get("i18n.select_correct_script.ff2d")); + afterScript = keyAndGlobal.getContext(); + logRecorder.system(I18nMessageUtil.get("i18n.introducing_script_content.a55b"), keyAndGlobal.getName(), scriptId); + } + JschUtils.execCallbackLine(session, charset, timeout, afterScript, StrUtil.EMPTY, environment, logRecorder::info); + } + this.updateStatus(taskId, modelId, 2, I18nMessageUtil.get("i18n.publish_success.2fff")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.publish_task_execution_exception.c296"), e); + updateStatus(taskId, modelId, 3, e.getMessage()); + } finally { + IoUtil.close(logRecorder); + JschUtil.close(channelSftp); + JschUtil.close(session); + } + }); + } + } + + /** + * 更新总任务信息(忽略多线程并发问题,因为最终的更新是单线程) + * + * @param taskId 任务ID + * @param status 状态 + * @param statusMsg 状态描述 + */ + private void updateRootStatus(String taskId, int status, String statusMsg) { + FileReleaseTaskLogModel fileReleaseTaskLogModel = new FileReleaseTaskLogModel(); + fileReleaseTaskLogModel.setTaskId(taskId); + List logModels = this.listByBean(fileReleaseTaskLogModel); + Map> map = logModels.stream() + .collect(CollectorUtil.groupingBy(logModel -> ObjectUtil.defaultIfNull(logModel.getStatus(), 0), Collectors.toList())); + StringBuilder stringBuilder = new StringBuilder(); + // + Opt.ofBlankAble(statusMsg).ifPresent(s -> stringBuilder.append(s).append(StrUtil.SPACE)); + Set>> entries = map.entrySet(); + for (Map.Entry> entry : entries) { + Integer key = entry.getKey(); + // 0 等待开始 1 进行中 2 任务结束 3 失败 + switch (key) { + case 0: + stringBuilder.append(I18nMessageUtil.get("i18n.waiting_to_start.b267")); + break; + case 1: + stringBuilder.append(I18nMessageUtil.get("i18n.in_progress.b851")); + break; + case 2: + stringBuilder.append(I18nMessageUtil.get("i18n.task_ended.b341")); + break; + case 3: + stringBuilder.append(I18nMessageUtil.get("i18n.failure_prefix.115a")); + break; + default: + stringBuilder.append(I18nMessageUtil.get("i18n.unknown_error.84d3")); + break; + } + stringBuilder.append(CollUtil.size(entry.getValue())); + } + FileReleaseTaskLogModel update = new FileReleaseTaskLogModel(); + update.setStatus(status); + update.setId(taskId); + update.setStatusMsg(stringBuilder.toString()); + this.updateById(update); + } + + /** + * 更新单给任务状态 + * + * @param taskId 总任务 + * @param id 子任务id + * @param status 状态 + * @param statusMsg 状态描述 + */ + private void updateStatus(String taskId, String id, int status, String statusMsg) { + FileReleaseTaskLogModel fileReleaseTaskLogModel = new FileReleaseTaskLogModel(); + fileReleaseTaskLogModel.setId(id); + fileReleaseTaskLogModel.setStatus(status); + fileReleaseTaskLogModel.setStatusMsg(statusMsg); + this.updateById(fileReleaseTaskLogModel); + // 更新总任务 + updateRootStatus(taskId, 1, StrUtil.EMPTY); + } + + public File logFile(FileReleaseTaskLogModel model) { + return FileUtil.file(jpomApplication.getDataPath(), "file-release-log", + model.getTaskId(), + model.getId() + ".log" + ); + } + + public File logTaskDir(FileReleaseTaskLogModel model) { + return FileUtil.file(jpomApplication.getDataPath(), "file-release-log", model.getId()); + } + + @Override + public int statusRecover() { + FileReleaseTaskLogModel update = new FileReleaseTaskLogModel(); + update.setModifyTimeMillis(SystemClock.now()); + update.setStatus(4); + update.setStatusMsg(I18nMessageUtil.get("i18n.system_cancel.3df2")); + Entity updateEntity = this.dataBeanToEntity(update); + // + Entity where = Entity.create() + .set("status", CollUtil.newArrayList(0, 1)); + return this.update(updateEntity, where); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileStorageService.java b/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileStorageService.java new file mode 100644 index 0000000000..34c871e7b4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileStorageService.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.files.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.StreamProgress; +import cn.hutool.core.io.unit.DataSize; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.db.Entity; +import cn.hutool.http.HttpUtil; +import cn.keepbx.jpom.event.ISystemTask; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +import org.dromara.jpom.func.files.model.FileStorageModel; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseGlobalOrWorkspaceService; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author bwcx_jzy + * @since 2023/3/16 + */ +@Service +@Slf4j +public class FileStorageService extends BaseGlobalOrWorkspaceService implements ISystemTask, IStatusRecover, ITriggerToken { + + private final ServerConfig serverConfig; + private final JpomApplication configBean; + private final BuildExtConfig buildExtConfig; + + public FileStorageService(ServerConfig serverConfig, + JpomApplication configBean, + BuildExtConfig buildExtConfig) { + this.serverConfig = serverConfig; + this.configBean = configBean; + this.buildExtConfig = buildExtConfig; + } + + /** + * 远程下载 + * + * @param url url + * @param workspaceId 工作空间id + * @param description 描述 + * @param global 是否为全局共享 + * @param keepDay 保留天数 + */ + public void download(String url, Boolean global, String workspaceId, Integer keepDay, String description, String aliasCode) { + FileStorageModel fileStorageModel = new FileStorageModel(); + // 临时使用 uuid,代替 + String uuid = IdUtil.fastSimpleUUID(); + long startTime = SystemClock.now(); + { + fileStorageModel.setId(uuid); + fileStorageModel.setName(I18nMessageUtil.get("i18n.file_downloading.7a8f")); + String empty = StrUtil.emptyToDefault(description, StrUtil.EMPTY); + + fileStorageModel.setDescription(StrUtil.format(I18nMessageUtil.get("i18n.remote_download_url.011f"), empty, url)); + String extName = "download"; + String path = StrUtil.format("/{}/{}.{}", DateTime.now().toString(DatePattern.PURE_DATE_FORMAT), uuid, extName); + fileStorageModel.setExtName("download"); + fileStorageModel.setPath(path); + fileStorageModel.setAliasCode(aliasCode); + fileStorageModel.setSize(0L); + fileStorageModel.setStatus(0); + fileStorageModel.setSource(2); + if (global != null && global) { + fileStorageModel.setWorkspaceId(ServerConst.WORKSPACE_GLOBAL); + } else { + fileStorageModel.setWorkspaceId(workspaceId); + } + fileStorageModel.validUntil(keepDay, null); + this.insert(fileStorageModel); + } + // 异步下载 + I18nThreadUtil.execute(() -> { + try { + File tempPath = configBean.getTempPath(); + File file = FileUtil.file(tempPath, "file-storage-download", uuid); + FileUtil.mkdir(file); + StreamProgress streamProgress = this.createStreamProgress(uuid); + File fileFromUrl = HttpUtil.downloadFileFromUrl(url, file, -1, streamProgress); + String md5 = SecureUtil.md5(fileFromUrl); + FileStorageModel storageModel = this.getByKey(md5); + if (storageModel != null) { + this.updateError(uuid, I18nMessageUtil.get("i18n.file_already_exists.983d")); + FileUtil.del(fileFromUrl); + return; + } + String extName = FileUtil.extName(fileFromUrl); + // 避免跨天数据 + String path = StrUtil.format("/{}/{}.{}", new DateTime(startTime).toString(DatePattern.PURE_DATE_FORMAT), md5, extName); + File storageSavePath = serverConfig.fileStorageSavePath(); + File fileStorageFile = FileUtil.file(storageSavePath, path); + FileUtil.mkParentDirs(fileStorageFile); + FileUtil.move(fileFromUrl, fileStorageFile, true); + // + FileStorageModel update = new FileStorageModel(); + // 需要将 id 更新为真实 id + update.setId(md5); + update.setName(fileFromUrl.getName()); + update.setExtName(extName); + update.setModifyTimeMillis(SystemClock.now()); + update.setPath(path); + update.setStatus(1); + update.setSize(FileUtil.size(fileStorageFile)); + Entity updateEntity = this.dataBeanToEntity(update); + Entity id = Entity.create().set("id", uuid); + this.update(updateEntity, id); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.download_failed_generic.be4f"), e); + this.updateError(uuid, e.getMessage()); + } + }); + } + + private StreamProgress createStreamProgress(String uuid) { + int logReduceProgressRatio = buildExtConfig.getLogReduceProgressRatio(); + Set progressRangeList = ConcurrentHashMap.newKeySet((int) Math.floor((float) 100 / logReduceProgressRatio)); + long bytes = DataSize.ofMegabytes(1).toBytes(); + return new StreamProgress() { + @Override + public void start() { + + } + + @Override + public void progress(long total, long progressSize) { + if (total > 0) { + double progressPercentage = Math.floor(((float) progressSize / total) * 100); + String percent = NumberUtil.formatPercent((float) progressSize / total, 0); + int progressRange = (int) Math.floor(progressPercentage / logReduceProgressRatio); + // 存在文件总大小 + if (progressRangeList.add(progressRange)) { + // total, progressSize + updateProgress(uuid, percent, total, progressSize); + } + } else { + // 不存在文件总大小 + if (progressSize % bytes == 0) { + updateProgress(uuid, null, total, progressSize); + } + } + } + + @Override + public void finish() { + + } + }; + } + + private void updateProgress(String id, String desc, long total, long progressSize) { + FileStorageModel fileStorageModel = new FileStorageModel(); + fileStorageModel.setId(id); + String fileSize = FileUtil.readableFileSize(progressSize); + desc = StrUtil.emptyToDefault(desc, fileSize); + fileStorageModel.setName(I18nMessageUtil.get("i18n.file_downloading_status.c995") + desc); + fileStorageModel.setStatus(0); + fileStorageModel.setSize(progressSize); + + fileStorageModel.setProgressDesc(StrUtil.format(I18nMessageUtil.get("i18n.download_progress.898a"), desc, FileUtil.readableFileSize(total), fileSize)); + this.updateById(fileStorageModel); + } + + /** + * 更新进度 + * + * @param id 数据id + * @param error 错误信息 + */ + private void updateError(String id, String error) { + FileStorageModel fileStorageModel = new FileStorageModel(); + fileStorageModel.setId(id); + fileStorageModel.setName(I18nMessageUtil.get("i18n.file_download_failed.7983") + StrUtil.maxLength(error, 200)); + fileStorageModel.setStatus(2); + fileStorageModel.setProgressDesc(error); + this.updateById(fileStorageModel); + } + + /** + * 添加文件 + * + * @param source 文件来源 + * @param file 要文件的文件 + * @param description 描述 + * @param workspaceId 工作空间id + * @param aliasCode 别名码 + * @return 返回成功的文件id + */ + public String addFile(File file, int source, String workspaceId, String description, String aliasCode) { + return addFile(file, source, workspaceId, description, aliasCode, null); + } + + /** + * 添加文件 + * + * @param source 文件来源 + * @param file 要文件的文件 + * @param description 描述 + * @param workspaceId 工作空间id + * @param aliasCode 别名码 + * @return 返回成功的文件id + */ + public String addFile(File file, int source, String workspaceId, String description, String aliasCode, Integer keepDay) { + String md5 = SecureUtil.md5(file); + File storageSavePath = serverConfig.fileStorageSavePath(); + String extName = FileUtil.extName(file); + String path = StrUtil.format("/{}/{}.{}", DateTime.now().toString(DatePattern.PURE_DATE_FORMAT), md5, extName); + FileStorageModel storageModel = this.getByKey(md5); + if (storageModel != null) { + return null; + } + // 保存 + FileStorageModel fileStorageModel = new FileStorageModel(); + fileStorageModel.setId(md5); + fileStorageModel.setAliasCode(aliasCode); + fileStorageModel.setName(file.getName()); + fileStorageModel.setDescription(description); + fileStorageModel.setExtName(extName); + fileStorageModel.setPath(path); + fileStorageModel.setSize(FileUtil.size(file)); + fileStorageModel.setSource(source); + fileStorageModel.setWorkspaceId(workspaceId); + fileStorageModel.validUntil(keepDay, null); + this.insert(fileStorageModel); + // + File fileStorageFile = FileUtil.file(storageSavePath, path); + FileUtil.mkParentDirs(fileStorageFile); + FileUtil.copyFile(file, fileStorageFile, StandardCopyOption.REPLACE_EXISTING); + return md5; + } + + @Override + public void executeTask() { + // 定时删除文件 + Entity entity = Entity.create(); + entity.set("validUntil", " < " + SystemClock.now()); + List storageModels = this.listByEntity(entity); + if (CollUtil.isEmpty(storageModels)) { + return; + } + File storageSavePath = serverConfig.fileStorageSavePath(); + for (FileStorageModel storageModel : storageModels) { + log.info(I18nMessageUtil.get("i18n.start_deleting_files.210c"), storageModel.getName(), storageModel.getPath()); + File fileStorageFile = FileUtil.file(storageSavePath, storageModel.getPath()); + FileUtil.del(fileStorageFile); + this.delByKey(storageModel.getId()); + } + } + + @Override + public int statusRecover() { + FileStorageModel update = new FileStorageModel(); + update.setName(I18nMessageUtil.get("i18n.system_restart_cancel_download.444e")); + update.setModifyTimeMillis(SystemClock.now()); + update.setStatus(2); + Entity updateEntity = this.dataBeanToEntity(update); + // + Entity where = Entity.create() + .set("source", 2) + .set("status", 0); + return this.update(updateEntity, where); + } + + @Override + public String typeName() { + return getTableName(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/service/StaticFileStorageService.java b/modules/server/src/main/java/org/dromara/jpom/func/files/service/StaticFileStorageService.java new file mode 100644 index 0000000000..1ffeff6fbf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/service/StaticFileStorageService.java @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.files.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.watch.WatchMonitor; +import cn.hutool.core.io.watch.WatchUtil; +import cn.hutool.core.io.watch.Watcher; +import cn.hutool.core.io.watch.watchers.DelayWatcher; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.event.IAsyncLoad; +import cn.keepbx.jpom.model.BaseIdModel; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.FileStorageConfig; +import org.dromara.jpom.controller.outgiving.OutGivingWhitelistService; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.func.files.model.StaticFileStorageModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.ServerWhitelist; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 23/12/28 028 + */ +@Service +@Slf4j +public class StaticFileStorageService extends BaseDbService 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(I18nMessageUtil.get("i18n.static_file_scanning_disabled.2b2b")); + this.removeTask(); + return; + } + List list = this.staticDir(); + if (CollUtil.isEmpty(list)) { + log.debug(I18nMessageUtil.get("i18n.static_directory_not_configured.acbc")); + 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(I18nMessageUtil.get("i18n.monitored_directory_does_not_exist.fa4e"), 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, I18nMessageUtil.get("i18n.static_directory_not_configured.9bd6")); + 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(I18nMessageUtil.get("i18n.no_static_directory_configured.d3c0")); + 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(I18nMessageUtil.get("i18n.file_type_not_supported_with_placeholder.db22"), 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(I18nMessageUtil.get("i18n.file_directory_too_long.c101"), 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(I18nMessageUtil.get("i18n.file_directory_too_long.c101"), 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(I18nMessageUtil.get("i18n.file_type_not_supported3.f551"), 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(I18nMessageUtil.get("i18n.process_file_event_exception.e8e6"), 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(); + WatchMonitor watchMonitor1; + while (true) { + String staticPath = this.absNormalize(file); + watchMonitor1 = MapUtil.get(watchMonitor, staticPath, WatchMonitor.class); + if (watchMonitor1 == null) { + File parentFile = file.getParentFile(); + if (parentFile == null || FileUtil.equals(parentFile, file)) { + break; + } + file = parentFile; + } else { + break; + } + } + if (watchMonitor1 == null) { + log.warn(I18nMessageUtil.get("i18n.listen_task_lost_or_not_found.347f"), path); + return null; + } + WatchKey watchKey = watchMonitor1.getWatchKey(path); + if (watchKey == null) { + log.warn("{}{}", I18nMessageUtil.get("i18n.listener_key_not_found.6d3a"), path.getFileName()); + return null; + } + return watchKey; + } + + /** + * 解析事件全路径 + * + * @param currentPath 监听路径 + * @param context 上下文 + * @return path + */ + private Path fullPath(Path currentPath, Object context) { + WatchKey watchKey = getWatchKey(currentPath); + if (watchKey == null) { + return null; + } + Path watchablePath = (Path) watchKey.watchable(); + return watchablePath.resolve((Path) context); + } + + @Override + public void onCreate(WatchEvent event, Path currentPath) { + log.debug(I18nMessageUtil.get("i18n.file_modification_event.5bc2"), currentPath, event); + this.onModify2(event, currentPath); + } + + @Override + public void onModify(WatchEvent event, Path currentPath) { + log.debug(I18nMessageUtil.get("i18n.file_modification_event.5bc2"), 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); + if (path != null) { + log.debug(I18nMessageUtil.get("i18n.file_full_path.16cc"), path); + this.doFile(path, currentPath); + } + } else { + log.warn(I18nMessageUtil.get("i18n.event_type_not_supported.e9c3"), context.getClass().getName()); + } + } + + @Override + public void onDelete(WatchEvent event, Path currentPath) { + log.debug(I18nMessageUtil.get("i18n.file_deletion_event_with_details.7537"), currentPath, event); + Object context = event.context(); + if (context instanceof Path) { + Path path = this.fullPath(currentPath, context); + if (path != null) { + File file = path.toFile(); + try { + // 处理文件删除事件 + this.delete(file); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.process_file_deletion_exception.1c6e"), e); + } + } + } else { + log.warn(I18nMessageUtil.get("i18n.event_type_not_supported.e9c3"), 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(I18nMessageUtil.get("i18n.file_deletion_event.a51c"), 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(I18nMessageUtil.get("i18n.event_loss_or_execution_error.7b14"), 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, I18nMessageUtil.get("i18n.no_file_info.db01")); + 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 0000000000..58f3b17aaf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/BaseDownloadApiController.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.dromara.jpom.common.i18n.I18nMessageUtil; +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(I18nMessageUtil.get("i18n.download_file_description.10cb"), 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), I18nMessageUtil.get("i18n.multi_download_not_supported.94b9")); + // TODO 解析更多格式的 RANGE 请求头 + long[] split = StrUtil.splitToLong(rangeByte, StrUtil.DASHED); + Assert.state(split != null, I18nMessageUtil.get("i18n.incorrect_range_information.a41c")); + 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(I18nMessageUtil.get("i18n.range_format_not_supported.d69e") + rangeByte); + } + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.range_format_not_supported.d69e") + 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), I18nMessageUtil.get("i18n.file_does_not_exist_anymore.2fab")); + 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(I18nMessageUtil.get("i18n.client_terminated_connection.6886"), clientAbortException.getMessage()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.data_download_failed.9499"), 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/BuildTriggerApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/BuildTriggerApiController.java new file mode 100644 index 0000000000..85cd73b2be --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/BuildTriggerApiController.java @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.ContentType; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.event.IAsyncLoad; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.build.BuildExecuteService; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.build.ResultDirFileAction; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.dromara.jpom.system.JpomRuntimeException; +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.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2019/9/4 + */ +@RestController +@NotLogin +@Slf4j +public class BuildTriggerApiController extends BaseJpomController implements IAsyncLoad, Runnable { + + private final BuildInfoService buildInfoService; + private final BuildExecuteService buildExecuteService; + private final TriggerTokenLogServer triggerTokenLogServer; + /** + * 等待执行构建的队列 + */ + private final Map> waitQueue = new SafeConcurrentHashMap<>(); + + public BuildTriggerApiController(BuildInfoService buildInfoService, + BuildExecuteService buildExecuteService, + TriggerTokenLogServer triggerTokenLogServer) { + this.buildInfoService = buildInfoService; + this.buildExecuteService = buildExecuteService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + + private Object[] buildParametersEnv(HttpServletRequest request, String body) { + String contentType = request.getContentType(); + Object[] parametersEnv = new Object[4]; + parametersEnv[0] = "triggerContentType"; + parametersEnv[1] = contentType; + parametersEnv[2] = "triggerBodyData"; + if (ContentType.isDefault(contentType)) { + Map paramMap = ServletUtil.getParamMap(request); + parametersEnv[3] = JSONObject.toJSONString(paramMap); + } else { + // github issues 48 + parametersEnv[3] = body == null ? ServletUtil.getBody(request) : body; + } + return parametersEnv; + } + + /** + * 构建触发器 + * + * @param id 构建ID + * @param token 构建的token + * @param delay 延迟时间(单位秒) + * @return json + */ + @RequestMapping(value = ServerOpenApi.BUILD_TRIGGER_BUILD2, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token, + HttpServletRequest request, + String delay, + String buildRemark, String useQueue) { + BuildInfoModel item = buildInfoService.getByKey(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + UserModel userModel = this.triggerTokenLogServer.getUserByToken(token, buildInfoService.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.trigger_token_error_or_expired_with_code.393b")); + + Assert.state(StrUtil.equals(token, item.getTriggerToken()), I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976")); + // 构建外部参数 + Object[] parametersEnv = this.buildParametersEnv(request, null); + Integer delay1 = Convert.toInt(delay, 0); + if (Convert.toBool(useQueue, false)) { + // 提交到队列暂存 + BuildCache buildCache = new BuildCache(); + buildCache.setId(id); + buildCache.setUserModel(userModel); + buildCache.setDelay(delay1); + buildCache.setBuildRemark(buildRemark); + buildCache.setParametersEnv(parametersEnv); + // + Queue buildCaches = waitQueue.computeIfAbsent(id, s -> new ConcurrentLinkedDeque<>()); + buildCaches.add(buildCache); + return JsonMessage.success(I18nMessageUtil.get("i18n.submit_task_queue_success.5f5b") + buildCaches.size()); + } + + BaseServerController.resetInfo(userModel); + return buildExecuteService.start(id, userModel, delay1, 1, buildRemark, parametersEnv); + } + + /** + * 构建触发器 + *

+ * 参数 [ + * { + * "id":"1", + * "token":"a", + * "delay":"0" + * } + * ] + *

+ * 响应 [ + * { + * "id":"1", + * "token":"a", + * "delay":"0", + * "msg":"开始构建", + * "data":1 + * } + * ] + * + * @return json + */ + @PostMapping(value = ServerOpenApi.BUILD_TRIGGER_BUILD_BATCH, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> triggerBatch(HttpServletRequest request) { + String body = ServletUtil.getBody(request); + if (StrUtil.isEmpty(body)) { + return new JsonMessage<>(405, I18nMessageUtil.get("i18n.please_pass_body_parameter.4e5c")); + } + try { + // 构建外部参数 + Object[] parametersEnv = this.buildParametersEnv(request, body); + JSONArray jsonArray = JSONArray.parseArray(body); + List collect = jsonArray.stream().peek(o -> { + JSONObject jsonObject = (JSONObject) o; + String id = jsonObject.getString("id"); + String token = jsonObject.getString("token"); + Integer delay = jsonObject.getInteger("delay"); + String buildRemark = jsonObject.getString("buildRemark"); + String useQueue = jsonObject.getString("useQueue"); + BuildInfoModel item = buildInfoService.getByKey(id); + if (item == null) { + String value = I18nMessageUtil.get("i18n.no_data_found.4ffb"); + jsonObject.put("msg", value); + return; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, buildInfoService.typeName()); + if (userModel == null) { + String value = I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"); + jsonObject.put("msg", value); + return; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + String value = I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"); + jsonObject.put("msg", value); + return; + } + // 更新字段 + String updateItemErrorMsg = this.updateItem(jsonObject); + if (updateItemErrorMsg != null) { + jsonObject.put("msg", updateItemErrorMsg); + return; + } + if (Convert.toBool(useQueue, false)) { + // 提交到队列暂存 + BuildCache buildCache = new BuildCache(); + buildCache.setId(id); + buildCache.setUserModel(userModel); + buildCache.setDelay(delay); + buildCache.setBuildRemark(buildRemark); + buildCache.setParametersEnv(parametersEnv); + // + Queue buildCaches = waitQueue.computeIfAbsent(id, s -> new ConcurrentLinkedDeque<>()); + buildCaches.add(buildCache); + jsonObject.put("msg", I18nMessageUtil.get("i18n.submit_task_queue_success.5f5b") + buildCaches.size()); + } else { + BaseServerController.resetInfo(userModel); + // + IJsonMessage start = buildExecuteService.start(id, userModel, delay, 1, buildRemark, parametersEnv); + jsonObject.put("msg", start.getMsg()); + jsonObject.put("buildId", start.getData()); + } + }).collect(Collectors.toList()); + return JsonMessage.success(I18nMessageUtil.get("i18n.trigger_success.f9d1"), collect); + } catch (Exception e) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.build_trigger_batch_exception.47d5"), e); + //log.error("构建触发批量触发异常", e); + //return JsonMessage.getString(500, "触发异常", e.getMessage()); + } + } + + /** + * 接收参数,修改 + * + * @param jsonObject 参数 + * @return 错误消息 + */ + private String updateItem(JSONObject jsonObject) { + String id = jsonObject.getString("id"); + String branchName = jsonObject.getString("branchName"); + String branchTagName = jsonObject.getString("branchTagName"); + String script = jsonObject.getString("script"); + String resultDirFile = jsonObject.getString("resultDirFile"); + String webhook = jsonObject.getString("webhook"); + // + BuildInfoModel item = new BuildInfoModel(); + if (StrUtil.isNotEmpty(branchName)) { + item.setBranchName(branchName); + } + if (StrUtil.isNotEmpty(branchTagName)) { + item.setBranchTagName(branchTagName); + } + if (StrUtil.isNotEmpty(script)) { + item.setScript(script); + } + if (StrUtil.isNotEmpty(resultDirFile)) { + ResultDirFileAction parse = ResultDirFileAction.parse(resultDirFile); + parse.check(); + item.setResultDirFile(resultDirFile); + } + if (StrUtil.isNotEmpty(webhook)) { + if (!Validator.isMatchRegex(RegexPool.URL_HTTP, webhook)) { + return I18nMessageUtil.get("i18n.invalid_webhooks_address.d836"); + } + item.setWebhook(webhook); + } + if (ObjectUtil.isNotEmpty(item)) { + item.setId(id); + buildInfoService.updateById(item); + } + return null; + } + + + /** + * 批量获取构建状态 + * + * @return json + */ + @GetMapping(value = ServerOpenApi.BUILD_TRIGGER_STATUS, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage buildStatusGet(@ValidatorItem String id, @ValidatorItem String token) { + JSONObject statusData = this.getStatusData(id, token); + return JsonMessage.success("", statusData); + } + + /** + * 批量获取构建状态 + */ + @GetMapping(value = ServerOpenApi.BUILD_TRIGGER_LOG, produces = MediaType.APPLICATION_JSON_VALUE) + public void buildLogGet(@ValidatorItem String id, + @ValidatorItem String token, + @ValidatorItem(ValidatorRule.NUMBERS) Integer buildNumId, + HttpServletResponse response) throws IOException { + BuildInfoModel item = buildInfoService.getByKey(id); + if (item == null) { + ServletUtil.write(response, I18nMessageUtil.get("i18n.no_data_found.4ffb"), ContentType.TEXT_PLAIN.getValue()); + return; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, buildInfoService.typeName()); + if (userModel == null) { + ServletUtil.write(response, I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"), ContentType.TEXT_PLAIN.getValue()); + return; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + ServletUtil.write(response, I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"), ContentType.TEXT_PLAIN.getValue()); + return; + } + File file = BuildUtil.getLogFile(item.getId(), buildNumId); + if (!FileUtil.isFile(file)) { + ServletUtil.write(response, I18nMessageUtil.get("i18n.log_file_error.473b"), ContentType.TEXT_PLAIN.getValue()); + return; + } + try (BufferedInputStream inputStream = FileUtil.getInputStream(file)) { + ServletUtil.write(response, inputStream, ContentType.TEXT_PLAIN.getValue()); + } + } + + /** + * 批量获取构建状态 + * + * @return json + */ + @PostMapping(value = ServerOpenApi.BUILD_TRIGGER_STATUS, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> buildStatusPost(HttpServletRequest request) { + try { + String body = ServletUtil.getBody(request); + JSONArray jsonArray = JSONArray.parseArray(body); + List collect = jsonArray.stream().map(o -> { + JSONObject data = (JSONObject) o; + String id = data.getString("id"); + String token = data.getString("token"); + return this.getStatusData(id, token); + }).collect(Collectors.toList()); + return JsonMessage.success("", collect); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.get_build_status_exception.914e"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.unexpected_exception.2b52") + e.getMessage()); + } + } + + private JSONObject getStatusData(String id, String token) { + JSONObject jsonObject = new JSONObject(); + BuildInfoModel item = buildInfoService.getByKey(id); + if (item == null) { + String value = I18nMessageUtil.get("i18n.no_data_found.4ffb"); + jsonObject.put("msg", value); + return jsonObject; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, buildInfoService.typeName()); + if (userModel == null) { + String value = I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"); + jsonObject.put("msg", value); + return jsonObject; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + String value = I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"); + jsonObject.put("msg", value); + return jsonObject; + } + // 更新字段 + Integer status = item.getStatus(); + BuildStatus buildStatus = BaseEnum.getEnum(BuildStatus.class, status); + if (buildStatus == null) { + jsonObject.put("msg", "status code error"); + } else { + jsonObject.put("msg", buildStatus.getDesc()); + jsonObject.put("statusCode", buildStatus.getCode()); + jsonObject.put("status", buildStatus.name()); + } + jsonObject.put("buildNumberId", item.getBuildId()); + return jsonObject; + } + + @Data + private static class BuildCache { + private UserModel userModel; + // 构建外部参数 + private Object[] parametersEnv; + + private Integer delay; + + private String buildRemark; + private String id; + + private Long taskTime; + + public BuildCache() { + this.taskTime = SystemClock.now(); + } + + @Override + public String toString() { + return JSONObject.toJSONString(this); + } + } + + @Override + public void startLoad() { + ScheduledExecutorService scheduler = JpomApplication.getScheduledExecutorService(); + scheduler.scheduleWithFixedDelay(this, 0, 5, TimeUnit.SECONDS); + } + + @Override + public void run() { + String id = "build_trigger_queue"; + int heartSecond = 5; + try { + CronUtils.TaskStat taskStat = CronUtils.getTaskStat(id, StrUtil.format(I18nMessageUtil.get("i18n.execution_frequency.d014"), heartSecond)); + taskStat.onStart(); + // + this.runQueue(); + taskStat.onSucceeded(); + } catch (Throwable throwable) { + CronUtils.TaskStat taskStat = CronUtils.getTaskStat(id, StrUtil.format(I18nMessageUtil.get("i18n.execution_frequency.d014"), heartSecond)); + taskStat.onFailed(id, throwable); + } + } + + private void runQueue() { + // 先删除空队列 + Set>> entries = waitQueue.entrySet(); + Iterator>> entryIterator = entries.iterator(); + while (entryIterator.hasNext()) { + Map.Entry> next = entryIterator.next(); + Queue queue = next.getValue(); + if (queue.isEmpty()) { + entryIterator.remove(); + } + } + int size = waitQueue.size(); + if (size > 0) { + log.debug(I18nMessageUtil.get("i18n.need_handle_build_micro_queue_count.3010"), size); + // 遍历队列中的数据 + waitQueue.forEach((buildId, buildCaches) -> { + synchronized (buildId.intern()) { + log.debug(I18nMessageUtil.get("i18n.need_handle_build_queue_count.c01e"), buildId, buildCaches.size()); + BuildInfoModel item = buildInfoService.getByKey(buildId); + if (item == null) { + log.error(I18nMessageUtil.get("i18n.build_data_not_exist.0225"), buildId, buildCaches.poll()); + return; + } + String statusMsg = buildExecuteService.checkStatus(item); + if (statusMsg != null) { + log.debug(I18nMessageUtil.get("i18n.build_task_waiting.e303"), buildId, statusMsg); + return; + } + BuildCache cache = buildCaches.poll(); + if (cache == null) { + return; + } + try { + BaseServerController.resetInfo(cache.userModel); + IJsonMessage message = buildExecuteService.start(cache.id, cache.userModel, cache.delay, 1, cache.buildRemark, cache.parametersEnv); + log.info(I18nMessageUtil.get("i18n.build_trigger_queue_result.a1fe"), message); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.create_build_task_exception.06f1"), e); + // 重新添加任务 + buildCaches.add(cache); + } + } + }); + } + } +} 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 new file mode 100644 index 0000000000..aa3651ba28 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/FileStorageApiController.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +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.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.func.files.model.FileStorageModel; +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.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; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/3/17 + */ +@RestController +@NotLogin +@Slf4j +public class FileStorageApiController extends BaseDownloadApiController { + + private final TriggerTokenLogServer triggerTokenLogServer; + private final FileStorageService fileStorageService; + private final ServerConfig serverConfig; + + public FileStorageApiController(TriggerTokenLogServer triggerTokenLogServer, + FileStorageService fileStorageService, + ServerConfig serverConfig) { + this.triggerTokenLogServer = triggerTokenLogServer; + this.fileStorageService = fileStorageService; + this.serverConfig = serverConfig; + } + + + /** + * 解析别名参数 + * + * @param id 别名 + * @param token token + * @param sort 排序 + * @param request 请求 + * @return 数据 + */ + private FileStorageModel queryByAliasCode(String id, String token, String sort, HttpServletRequest request) { + // 先验证 token 和 id 是否都存在 + { + FileStorageModel data = new FileStorageModel(); + data.setAliasCode(id); + data.setTriggerToken(token); + Assert.state(fileStorageService.exists(data), I18nMessageUtil.get("i18n.alias_or_token_error.d5c6")); + } + 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 paramMap = ServletUtil.getParamMap(request); + FileStorageModel where = new FileStorageModel(); + for (Map.Entry entry : paramMap.entrySet()) { + String key = entry.getKey(); + if (StrUtil.startWith(key, "filter_")) { + key = StrUtil.removePrefix(key, "filter_"); + ReflectUtil.setFieldValue(where, key, entry.getValue()); + } + } + where.setAliasCode(id); + List fileStorageModels = fileStorageService.queryList(where, 1, orders.toArray(new Order[]{})); + return CollUtil.getFirst(fileStorageModels); + } + + @GetMapping(value = ServerOpenApi.FILE_STORAGE_DOWNLOAD, produces = MediaType.APPLICATION_JSON_VALUE) + public void download(@PathVariable String id, + @PathVariable String token, + String sort, + HttpServletRequest request, + HttpServletResponse response) throws IOException { + FileStorageModel storageModel = fileStorageService.getByKey(id); + if (storageModel == null) { + // 根据别名查询 + storageModel = this.queryByAliasCode(id, token, sort, request); + Assert.notNull(storageModel, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + } else { + Assert.state(StrUtil.equals(token, storageModel.getTriggerToken()), I18nMessageUtil.get("i18n.invalid_or_expired_token.bc43")); + } + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, fileStorageService.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.token_invalid_or_expired.cb96")); + // + File storageSavePath = serverConfig.fileStorageSavePath(); + File fileStorageFile = FileUtil.file(storageSavePath, storageModel.getPath()); + // 需要考虑文件名中存在非法字符 + 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); + this.download(fileStorageFile, fileSize, name, resolveRange, response); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeInfoController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeInfoController.java new file mode 100644 index 0000000000..e3a6b44511 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeInfoController.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +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.BaseServerController; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +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.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 节点管理 + * + * @author bwcx_jzy + * @since 2019/8/5 + */ +@RestController +@Slf4j +public class NodeInfoController extends BaseServerController { + + private static final Map CACHE_RECEIVE_PUSH = new HashMap<>(); + + private final NodeService nodeService; + private final WorkspaceService workspaceService; + + public NodeInfoController(NodeService nodeService, + WorkspaceService workspaceService) { + this.nodeService = nodeService; + this.workspaceService = workspaceService; + } + + /** + * 接收节点推送的信息 + *

+ * yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk + * --auto-push-to-server http://127.0.0.1:3000/api/node/receive_push?token=462a47b8fba8da1f824370bb9fcdc01aa1a0fe20&workspaceId=DEFAULT + * + * @return json + */ + @RequestMapping(value = ServerOpenApi.RECEIVE_PUSH, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @NotLogin + public IJsonMessage receivePush(@ValidatorItem(msg = "i18n.credential_cannot_be_empty.d055") String token, + @ValidatorItem(msg = "i18n.communication_ip_cannot_be_empty.ae35") String ips, + @ValidatorItem(msg = "i18n.login_name_cannot_be_empty.9a99") String loginName, + @ValidatorItem(msg = "i18n.password_cannot_be_empty.89b5") String loginPwd, + @ValidatorItem(msg = "i18n.workspace_id_required.c967") String workspaceId, + @ValidatorItem(value = ValidatorRule.NUMBERS, msg = "i18n.port_error.312e") int port, + String ping) { + Assert.state(StrUtil.equals(token, JpomManifest.getInstance().randomIdSign()), "token error"); + boolean exists = workspaceService.exists(new WorkspaceModel(workspaceId)); + Assert.state(exists, "workspaceId error"); + String sha1Id = SecureUtil.sha1(ips); + // + List ipsList = StrUtil.split(ips, StrUtil.COMMA); + String clientIp = getClientIP(); + if (!ipsList.contains(clientIp)) { + ipsList.add(clientIp); + } + List canUseIps = ipsList.stream() + .filter(s -> this.testIpPort(s, ping, port)) + .collect(Collectors.toList()); + List canUseNode = canUseIps.stream().map(s -> { + MachineNodeModel model = this.createMachineNodeModel(s, loginName, loginPwd, port); + try { + machineNodeServer.testNode(model); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.test_result.8441"), model.getJpomUrl(), e.getMessage()); + return null; + } + return model; + }).filter(Objects::nonNull).collect(Collectors.toList()); + // 只返回能通的 IP + canUseIps = canUseNode.stream().map(MachineNodeModel::getName).collect(Collectors.toList()); + // 标记为系统操作 + BaseServerController.resetInfo(UserModel.EMPTY); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("allIp", ipsList); + jsonObject.put("canUseIp", canUseIps); + jsonObject.put("port", port); + jsonObject.put("id", sha1Id); + jsonObject.put("canUseNode", canUseNode); + // + for (MachineNodeModel nodeModel : canUseNode) { + MachineNodeModel existsMachine = machineNodeServer.getByUrl(nodeModel.getJpomUrl()); + if (existsMachine != null) { + if (nodeService.existsNode2(workspaceId, existsMachine.getId())) { + // 存在 + jsonObject.put("type", "exists"); + } else { + // 自动同步 + jsonObject.put("type", "success"); + machineNodeServer.insertNode(existsMachine, workspaceId); + } + break; + } + } + if (!jsonObject.containsKey("type")) { + int size1 = CollUtil.size(canUseNode); + if (size1 == 1) { + // 只有一个 ip 可以使用,添加插件端 + BaseServerController.resetInfo(UserModel.EMPTY); + MachineNodeModel first = CollUtil.getFirst(canUseNode); + machineNodeServer.insertAndNode(first, workspaceId); + jsonObject.put("type", "success"); + } else { + jsonObject.put("type", size1 == 0 ? "canUseIpEmpty" : "multiIp"); + } + } + CACHE_RECEIVE_PUSH.put(sha1Id, jsonObject); + return JsonMessage.success("done", jsonObject); + } + + /** + * 查询所有缓存 + * + * @return list + */ + public static Collection listReceiveCache(String removeId) { + if (StrUtil.isNotEmpty(removeId)) { + CACHE_RECEIVE_PUSH.remove(removeId); + } + return CACHE_RECEIVE_PUSH.values(); + } + + public static JSONObject getReceiveCache(String id) { + return CACHE_RECEIVE_PUSH.get(id); + } + + /** + * 尝试 ping + * + * @param ip ip 地址 + * @param ping ping 时间 + * @return true + */ + private boolean testIpPort(String ip, String ping, int port) { + int pingTime = Convert.toInt(ping, 5); + if (pingTime <= 0) { + return true; + } + boolean pinged = NetUtil.ping(ip, pingTime * 1000); + // + return pinged || this.testIpCanPort(ip, pingTime, port); + } + + private boolean testIpCanPort(String ip, int timeout, int port) { + InetSocketAddress address = NetUtil.createAddress(ip, port); + return NetUtil.isOpen(address, (int) TimeUnit.SECONDS.toMillis(timeout)); + } + + private MachineNodeModel createMachineNodeModel(String ip, String loginName, String loginPwd, int port) { + MachineNodeModel machineNodeModel = new MachineNodeModel(); + machineNodeModel.setName(ip); + machineNodeModel.setStatus(1); + machineNodeModel.setJpomUsername(loginName); + machineNodeModel.setJpomPassword(loginPwd); + machineNodeModel.setJpomUrl(ip + StrUtil.COLON + port); + machineNodeModel.setJpomProtocol("http"); + return machineNodeModel; + } +} 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 new file mode 100644 index 0000000000..23f28ddeef --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeScriptTriggerApiController.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.node.NodeScriptCacheModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.node.NodeService; +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.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 节点脚本触发器 + * + * @author bwcx_jzy + * @since 2022/7/25 + */ +@RestController +@NotLogin +@Slf4j +public class NodeScriptTriggerApiController extends BaseJpomController { + + private final NodeScriptServer nodeScriptServer; + private final NodeService nodeService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public NodeScriptTriggerApiController(NodeScriptServer nodeScriptServer, + NodeService nodeService, + TriggerTokenLogServer triggerTokenLogServer) { + this.nodeScriptServer = nodeScriptServer; + this.nodeService = nodeService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 执行脚本 + * + * @param id 构建ID + * @param token 构建的token + * @return json + */ + @RequestMapping(value = ServerOpenApi.NODE_SCRIPT_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token, HttpServletRequest request) { + NodeScriptCacheModel item = nodeScriptServer.getByKey(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + Assert.state(StrUtil.equals(token, item.getTriggerToken()), I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976")); + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, nodeScriptServer.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.trigger_token_error_or_expired_with_code.393b")); + + try { + NodeModel nodeModel = nodeService.getByKey(item.getNodeId()); + JSONObject reqData = new JSONObject(); + reqData.put("id", item.getScriptId()); + reqData.put("params", JSONObject.toJSONString(ServletUtil.getParamMap(request))); + JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.SCRIPT_EXEC, reqData); + // + JSONObject jsonObject = new JSONObject(); + if (jsonMessage.getCode() == 200) { + jsonObject.put("logId", jsonMessage.getData()); + } else { + jsonObject.put("msg", jsonMessage.getMsg()); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.start_execution.00d7"), jsonObject); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_server_script_exception.8e84"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } + } + + + /** + * 构建触发器 + *

+ * 参数 [ + * { + * "id":"1", + * "token":"a" + * } + * ] + *

+ * 响应 [ + * { + * "id":"1", + * "token":"a", + * "logId":"1", + * "msg":"没有对应数据", + * } + * ] + * + * @return json + */ + @PostMapping(value = ServerOpenApi.NODE_SCRIPT_TRIGGER_BATCH, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> triggerBatch(HttpServletRequest request) { + try { + String body = ServletUtil.getBody(request); + JSONArray jsonArray = JSONArray.parseArray(body); + List collect = jsonArray.stream().peek(o -> { + JSONObject jsonObject = (JSONObject) o; + String id = jsonObject.getString("id"); + String token = jsonObject.getString("token"); + NodeScriptCacheModel item = nodeScriptServer.getByKey(id); + if (item == null) { + String string = I18nMessageUtil.get("i18n.no_data_found.4ffb"); + jsonObject.put("msg", string); + return; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, nodeScriptServer.typeName()); + if (userModel == null) { + String string = I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"); + jsonObject.put("msg", string); + return; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + String value = I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"); + jsonObject.put("msg", value); + return; + } + NodeModel nodeModel = nodeService.getByKey(item.getNodeId()); + JSONObject reqData = new JSONObject(); + reqData.put("id", item.getScriptId()); + JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.SCRIPT_EXEC, reqData); + // + if (jsonMessage.getCode() == 200) { + jsonObject.put("logId", jsonMessage.getData()); + } else { + jsonObject.put("msg", jsonMessage.getMsg()); + } + }).collect(Collectors.toList()); + return JsonMessage.success(I18nMessageUtil.get("i18n.trigger_success.f9d1"), collect); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.batch_trigger_script_exception.8fb4"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.trigger_exception.d624") + e.getMessage()); + } + } +} 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 new file mode 100644 index 0000000000..d05c78b837 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ProjectTriggerApiController.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.node.ProjectInfoCacheModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.node.ProjectInfoCacheService; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 项目触发器 + * + * @author bwcx_jzy + * @since 2022/12/18 + */ +@RestController +@RequestMapping +@NotLogin +@Slf4j +public class ProjectTriggerApiController extends BaseJpomController { + + private final ProjectInfoCacheService projectInfoCacheService; + private final TriggerTokenLogServer triggerTokenLogServer; + private final NodeService nodeService; + + public ProjectTriggerApiController(ProjectInfoCacheService projectInfoCacheService, + TriggerTokenLogServer triggerTokenLogServer, + NodeService nodeService) { + this.projectInfoCacheService = projectInfoCacheService; + this.triggerTokenLogServer = triggerTokenLogServer; + this.nodeService = nodeService; + } + + private NodeUrl resolveAction(String action) { + if (StrUtil.equalsIgnoreCase(action, "stop")) { + return NodeUrl.Manage_Operate; + } + if (StrUtil.equalsIgnoreCase(action, "start")) { + return NodeUrl.Manage_Operate; + } + if (StrUtil.equalsIgnoreCase(action, "restart")) { + return NodeUrl.Manage_Operate; + } + if (StrUtil.equalsIgnoreCase(action, "reload")) { + return NodeUrl.Manage_Operate; + } + return NodeUrl.Manage_GetProjectStatus; + } + + private JsonMessage execAction(ProjectInfoCacheModel item, String action) { + NodeUrl resolveAction = this.resolveAction(action); + // + NodeModel nodeModel = nodeService.getByKey(item.getNodeId()); + return NodeForward.request(nodeModel, resolveAction, + "id", item.getProjectId(), "opt", action); + } + + /** + * 执行脚本 + * + * @param id 构建ID + * @param token 构建的token + * @return json + */ + @RequestMapping(value = ServerOpenApi.SERVER_PROJECT_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage trigger(@PathVariable String id, @PathVariable String token, String action) { + ProjectInfoCacheModel item = projectInfoCacheService.getByKey(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + Assert.state(StrUtil.equals(token, item.getTriggerToken()), I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976")); + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, projectInfoCacheService.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.trigger_token_error_or_expired_with_code.393b")); + + try { + return this.execAction(item, action); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_server_script_exception.8e84"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } + } + + + /** + * 构建触发器 + *

+ * 参数 [ + * { + * "id":"1", + * "token":"a", + * "action":"status" + * } + * ] + *

+ * 响应 [ + * { + * "id":"1", + * "token":"a", + * "code":"1", + * "data":{}, + * "msg":"没有对应数据", + * } + * ] + * + * @return json + */ + @PostMapping(value = ServerOpenApi.SERVER_PROJECT_TRIGGER_BATCH, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> triggerBatch(HttpServletRequest request) { + try { + String body = ServletUtil.getBody(request); + JSONArray jsonArray = JSONArray.parseArray(body); + List collect = jsonArray.stream().map(o -> { + JSONObject jsonObject = (JSONObject) o; + String id = jsonObject.getString("id"); + String token = jsonObject.getString("token"); + String action = jsonObject.getString("action"); + ProjectInfoCacheModel item = projectInfoCacheService.getByKey(id); + if (item == null) { + String value = I18nMessageUtil.get("i18n.no_data_found.4ffb"); + jsonObject.put("msg", value); + return jsonObject; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, projectInfoCacheService.typeName()); + if (userModel == null) { + String value = I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"); + jsonObject.put("msg", value); + return jsonObject; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + String value = I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"); + jsonObject.put("msg", value); + return jsonObject; + } + JsonMessage message = this.execAction(item, action); + jsonObject.put("msg", message.getMsg()); + jsonObject.put("data", message.getData()); + jsonObject.put("code", message.getCode()); + return jsonObject; + }).collect(Collectors.toList()); + return JsonMessage.success(I18nMessageUtil.get("i18n.trigger_success.f9d1"), collect); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.batch_trigger_project_exception.3c28"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.trigger_exception.d624") + e.getMessage()); + } + } +} 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 new file mode 100644 index 0000000000..9c19c6e353 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ServerScriptTriggerApiController.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.model.script.ScriptExecuteLogModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.dromara.jpom.socket.ServerScriptProcessBuilder; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 服务端脚本触发器 + * + * @author bwcx_jzy + * @since 2022/7/25 + */ +@RestController +@NotLogin +@Slf4j +public class ServerScriptTriggerApiController extends BaseJpomController { + + private final ScriptServer scriptServer; + private final ScriptExecuteLogServer scriptExecuteLogServer; + private final TriggerTokenLogServer triggerTokenLogServer; + + public ServerScriptTriggerApiController(ScriptServer scriptServer, + ScriptExecuteLogServer scriptExecuteLogServer, + TriggerTokenLogServer triggerTokenLogServer) { + this.scriptServer = scriptServer; + this.scriptExecuteLogServer = scriptExecuteLogServer; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 执行脚本 + * + * @param id 构建ID + * @param token 构建的token + * @return json + */ + @RequestMapping(value = ServerOpenApi.SERVER_SCRIPT_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token, HttpServletRequest request) { + ScriptModel item = scriptServer.getByKey(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + Assert.state(StrUtil.equals(token, item.getTriggerToken()), I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976")); + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, scriptServer.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.trigger_token_error_or_expired_with_code.393b")); + + try { + BaseServerController.resetInfo(userModel); + // 解析参数 + Map paramMap = ServletUtil.getParamMap(request); + Map newParamMap = new HashMap<>(10); + for (Map.Entry entry : paramMap.entrySet()) { + String key = StrUtil.format("trigger_{}", entry.getKey()); + key = StrUtil.toUnderlineCase(key); + newParamMap.put(key, entry.getValue()); + } + // 创建记录 + ScriptExecuteLogModel nodeScriptExecLogModel = scriptExecuteLogServer.create(item, 2); + // 执行 + ServerScriptProcessBuilder.create(item, nodeScriptExecLogModel.getId(), item.getDefArgs(), newParamMap); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("logId", nodeScriptExecLogModel.getId()); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_execution.00d7"), jsonObject); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_server_script_exception.8e84"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } + } + + + /** + * 构建触发器 + *

+ * 参数 [ + * { + * "id":"1", + * "token":"a" + * } + * ] + *

+ * 响应 [ + * { + * "id":"1", + * "token":"a", + * "logId":"1", + * "msg":"没有对应数据", + * } + * ] + * + * @return json + */ + @PostMapping(value = ServerOpenApi.SERVER_SCRIPT_TRIGGER_BATCH, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> triggerBatch(HttpServletRequest request) { + try { + String body = ServletUtil.getBody(request); + JSONArray jsonArray = JSONArray.parseArray(body); + List collect = jsonArray.stream().peek(o -> { + JSONObject jsonObject = (JSONObject) o; + String id = jsonObject.getString("id"); + String token = jsonObject.getString("token"); + ScriptModel item = scriptServer.getByKey(id); + if (item == null) { + String value = I18nMessageUtil.get("i18n.no_data_found.4ffb"); + jsonObject.put("msg", value); + return; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, scriptServer.typeName()); + if (userModel == null) { + String value = I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"); + jsonObject.put("msg", value); + return; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + String value = I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"); + jsonObject.put("msg", value); + return; + } + BaseServerController.resetInfo(userModel); + try { + // 创建记录 + ScriptExecuteLogModel nodeScriptExecLogModel = scriptExecuteLogServer.create(item, 2); + // 执行 + ServerScriptProcessBuilder.create(item, nodeScriptExecLogModel.getId(), item.getDefArgs()); + jsonObject.put("logId", nodeScriptExecLogModel.getId()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_command_template_exception.4e01"), e); + jsonObject.put("msg", I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } + // + }).collect(Collectors.toList()); + return JsonMessage.success(I18nMessageUtil.get("i18n.trigger_success.f9d1"), collect); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.batch_trigger_script_exception.8fb4"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.trigger_exception.d624") + e.getMessage()); + } + } +} 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 new file mode 100644 index 0000000000..7e81eefb18 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/SshCommandTriggerApiController.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.SshCommandService; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * ssh 脚本触发器 + * + * @author bwcx_jzy + * @since 2022/7/25 + */ +@RestController +@NotLogin +@Slf4j +public class SshCommandTriggerApiController extends BaseJpomController { + + private final SshCommandService sshCommandService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public SshCommandTriggerApiController(SshCommandService sshCommandService, + TriggerTokenLogServer triggerTokenLogServer) { + this.sshCommandService = sshCommandService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 执行脚本 + * + * @param id 构建ID + * @param token 构建的token + * @return json + */ + @RequestMapping(value = ServerOpenApi.SSH_COMMAND_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token, HttpServletRequest request) { + CommandModel item = sshCommandService.getByKey(id); + Assert.notNull(item, I18nMessageUtil.get("i18n.no_data_found.4ffb")); + Assert.state(StrUtil.equals(token, item.getTriggerToken()), I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976")); + // + Assert.hasText(item.getSshIds(), I18nMessageUtil.get("i18n.script_not_bound_to_ssh_node.3459")); + UserModel userModel = triggerTokenLogServer.getUserByToken(token, sshCommandService.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.trigger_token_error_or_expired_with_code.393b")); + // 解析参数 + Map paramMap = ServletUtil.getParamMap(request); + Map newParamMap = new HashMap<>(10); + for (Map.Entry entry : paramMap.entrySet()) { + String key = StrUtil.format("trigger_{}", entry.getKey()); + key = StrUtil.toUnderlineCase(key); + newParamMap.put(key, entry.getValue()); + } + String batchId; + try { + BaseServerController.resetInfo(userModel); + batchId = sshCommandService.executeBatch(item, item.getDefParams(), item.getSshIds(), 2, newParamMap); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_ssh_command_template_exception.7451"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("batchId", batchId); + return JsonMessage.success(I18nMessageUtil.get("i18n.start_execution.00d7"), jsonObject); + } + + + /** + * 构建触发器 + *

+ * 参数 [ + * { + * "id":"1", + * "token":"a" + * } + * ] + *

+ * 响应 [ + * { + * "id":"1", + * "token":"a", + * "batchId":"1", + * "msg":"没有对应数据", + * } + * ] + * + * @return json + */ + @PostMapping(value = ServerOpenApi.SSH_COMMAND_TRIGGER_BATCH, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> triggerBatch(HttpServletRequest request) { + try { + String body = ServletUtil.getBody(request); + JSONArray jsonArray = JSONArray.parseArray(body); + List collect = jsonArray.stream().peek(o -> { + JSONObject jsonObject = (JSONObject) o; + String id = jsonObject.getString("id"); + String token = jsonObject.getString("token"); + CommandModel item = sshCommandService.getByKey(id); + if (item == null) { + String value = I18nMessageUtil.get("i18n.no_data_found.4ffb"); + jsonObject.put("msg", value); + return; + } + UserModel userModel = triggerTokenLogServer.getUserByToken(token, sshCommandService.typeName()); + if (userModel == null) { + String value = I18nMessageUtil.get("i18n.user_not_exist_trigger_invalid.f375"); + jsonObject.put("msg", value); + return; + } + // + if (!StrUtil.equals(token, item.getTriggerToken())) { + String value = I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"); + jsonObject.put("msg", value); + return; + } + BaseServerController.resetInfo(userModel); + String batchId = null; + try { + batchId = sshCommandService.executeBatch(item, item.getDefParams(), item.getSshIds(), 2); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_command_template_exception.4e01"), e); + jsonObject.put("msg", I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } + jsonObject.put("batchId", batchId); + // + }).collect(Collectors.toList()); + return JsonMessage.success(I18nMessageUtil.get("i18n.trigger_success.f9d1"), collect); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.ssh_script_batch_trigger_exception.70e1"), e); + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.trigger_exception.d624") + 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 0000000000..bec42f55f3 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/StaticFileStorageApiController.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.i18n.I18nMessageUtil; +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, I18nMessageUtil.get("i18n.file_not_found.d952")); + + Assert.state(StrUtil.equals(token, storageModel.getTriggerToken()), I18nMessageUtil.get("i18n.invalid_or_expired_token.bc43")); + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, staticFileStorageService.typeName()); + // + Assert.notNull(userModel, I18nMessageUtil.get("i18n.token_invalid_or_expired.cb96")); + 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/func/openapi/controller/WorkspaceEnvVarApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/WorkspaceEnvVarApiController.java new file mode 100644 index 0000000000..299a95dca4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/WorkspaceEnvVarApiController.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.openapi.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseJpomController; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.model.data.WorkspaceEnvVarModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author bwcx_jzy + * @since 23/12/19 019 + */ +@RestController +@NotLogin +@Slf4j +public class WorkspaceEnvVarApiController extends BaseJpomController { + + private final WorkspaceEnvVarService workspaceEnvVarService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public WorkspaceEnvVarApiController(WorkspaceEnvVarService workspaceEnvVarService, + TriggerTokenLogServer triggerTokenLogServer) { + this.workspaceEnvVarService = workspaceEnvVarService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 参数获取并验证变量 + * + * @param id 变量id + * @param token token + * @param response 响应 + * @return data + */ + private WorkspaceEnvVarModel get(String id, String token, HttpServletResponse response) { + WorkspaceEnvVarModel item = workspaceEnvVarService.getByKey(id); + if (item == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + ServletUtil.write(response, I18nMessageUtil.get("i18n.no_data_found.4ffb"), MediaType.TEXT_PLAIN_VALUE); + return null; + } + if (!StrUtil.equals(token, item.getTriggerToken())) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + ServletUtil.write(response, I18nMessageUtil.get("i18n.trigger_token_error_or_expired.8976"), MediaType.TEXT_PLAIN_VALUE); + return null; + } + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, workspaceEnvVarService.typeName()); + if (userModel == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + ServletUtil.write(response, I18nMessageUtil.get("i18n.trigger_token_error_or_expired_with_code.393b"), MediaType.TEXT_PLAIN_VALUE); + return null; + } + Integer privacy = item.getPrivacy(); + if (privacy == null || privacy != 0) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + ServletUtil.write(response, I18nMessageUtil.get("i18n.non_plaintext_variable_cannot_view.50ca"), MediaType.TEXT_PLAIN_VALUE); + return null; + } + return item; + } + + + /** + * 获取变量值 + * + * @param id 变量ID + * @param token 变量的token + */ + @GetMapping(value = ServerOpenApi.SERVER_ENV_VAR_TRIGGER_URL, produces = MediaType.TEXT_PLAIN_VALUE) + public void trigger(@PathVariable String id, @PathVariable String token, HttpServletResponse response) { + WorkspaceEnvVarModel item = this.get(id, token, response); + if (item != null) { + ServletUtil.write(response, item.getValue(), MediaType.TEXT_PLAIN_VALUE); + } + } + + /** + * 修改变量值 + * + * @param id 变量ID + * @param token 变量的token + */ + @PostMapping(value = ServerOpenApi.SERVER_ENV_VAR_TRIGGER_URL, produces = MediaType.TEXT_PLAIN_VALUE) + public void trigger(@PathVariable String id, @PathVariable String token, String value, HttpServletResponse response, HttpServletRequest request) { + this.update(id, token, value, response); + } + + /** + * 修改变量值 + * + * @param id 变量ID + * @param token 变量的token + */ + @PutMapping(value = ServerOpenApi.SERVER_ENV_VAR_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) + public void triggerPut(@PathVariable String id, @PathVariable String token, HttpServletResponse response, HttpServletRequest request) { + String value = ServletUtil.getBody(request); + this.update(id, token, value, response); + } + + /** + * 修改变量操作 + * + * @param id 变量id + * @param token 变量token + * @param value 变量值 + * @param response 响应 + */ + private void update(String id, String token, String value, HttpServletResponse response) { + if (StrUtil.isEmpty(value)) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + ServletUtil.write(response, I18nMessageUtil.get("i18n.modified_value_is_empty.e4fa"), MediaType.TEXT_PLAIN_VALUE); + return; + } + WorkspaceEnvVarModel item = this.get(id, token, response); + if (item != null) { + WorkspaceEnvVarModel update = new WorkspaceEnvVarModel(); + update.setId(item.getId()); + update.setValue(value); + workspaceEnvVarService.updateById(update); + ServletUtil.write(response, "success", MediaType.TEXT_PLAIN_VALUE); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/system/controller/CacheManageController.java b/modules/server/src/main/java/org/dromara/jpom/func/system/controller/CacheManageController.java new file mode 100644 index 0000000000..dc26c236fe --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/system/controller/CacheManageController.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.system.controller; + +import cn.hutool.cache.impl.CacheObj; +import cn.hutool.cache.impl.LFUCache; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.FileUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.event.ICacheTask; +import cn.keepbx.jpom.event.ISystemTask; +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.BuildExecuteManage; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.configuration.ClusterConfig; +import org.dromara.jpom.configuration.SystemConfig; +import org.dromara.jpom.controller.LoginControl; +import org.dromara.jpom.cron.CronUtils; +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.plugin.PluginFactory; +import org.dromara.jpom.socket.ServiceFileTailWatcher; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.system.db.DataInitEvent; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.SyncFinisherUtil; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +/** + * 缓存管理 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@RestController +@RequestMapping(value = "system") +@Feature(cls = ClassFeature.SYSTEM_CACHE) +@SystemPermission +@Slf4j +public class CacheManageController extends BaseServerController implements ICacheTask, ISystemTask { + + private long dataSize; + private long oldJarsSize; + private long tempFileSize; + + private final JpomApplication jpomApplication; + private final DataInitEvent dataInitEvent; + private final ClusterConfig clusterConfig; + private final SystemConfig systemConfig; + /** + * 标记是否正在刷新缓存 + */ + private boolean refreshCacheIng = false; + + public CacheManageController(JpomApplication jpomApplication, + DataInitEvent dataInitEvent, + ServerConfig serverConfig) { + this.jpomApplication = jpomApplication; + this.dataInitEvent = dataInitEvent; + this.clusterConfig = serverConfig.getCluster(); + this.systemConfig = serverConfig.getSystem(); + } + + /** + * get server's cache data + * 获取 Server 的缓存数据 + * + * @return json + * @author Hotstrip + */ + @PostMapping(value = "server-cache", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> serverCache() { + Map map = new HashMap<>(10); + map.put("cacheFileSize", this.tempFileSize); + map.put("dataSize", this.dataSize); + map.put("oldJarsSize", this.oldJarsSize); + { + LFUCache lfuCache = LoginControl.LFU_CACHE; + List> list = CollUtil.newArrayList(lfuCache.cacheObjIterator()); + map.put("errorIp", list); + } + int oneLineCount = ServiceFileTailWatcher.getOneLineCount(); + map.put("readFileOnLineCount", oneLineCount); + map.put("cacheBuildFileSize", BuildUtil.buildCacheSize); + map.put("taskList", CronUtils.list()); + map.put("pluginSize", PluginFactory.size()); + map.put("shardingSize", BaseServerController.SHARDING_IDS.size()); + map.put("buildKeys", BuildExecuteManage.buildKeys()); + map.put("syncFinisKeys", SyncFinisherUtil.keys()); + map.put("dateTime", DateTime.now().toString()); + map.put("timeZoneId", TimeZone.getDefault().getID()); + map.put("errorWorkspace", dataInitEvent.getErrorWorkspaceTable()); + map.put("clusterId", clusterConfig.getId()); + JpomManifest jpomManifest = JpomManifest.getInstance(); + map.put("installId", jpomManifest.getInstallId()); + map.put("tempPath", jpomApplication.getTempPath().getAbsolutePath()); + map.put("dataPath", jpomApplication.getDataPath()); + map.put("buildPath", BuildUtil.getBuildDataDir()); + map.put("timerMatchSecond", systemConfig.isTimerMatchSecond()); + // + return JsonMessage.success("", map); + } + + /** + * 获取节点中的缓存 + * + * @return json + */ + @RequestMapping(value = "node_cache.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage nodeCache(HttpServletRequest request, @ValidatorItem String machineId) { + return this.tryRequestMachine(machineId, request, NodeUrl.Cache); + +// return Optional.ofNullable(message).orElseGet(() -> { +// List data = DirTreeUtil.getTreeData(LogbackConfig.getPath()); +// return JsonMessage.success("", data); +// }); +// return NodeForward.request(getNode(), request, NodeUrl.Cache).toString(); + } + + /** + * 清空缓存 + * + * @param type 类型 + * @return json + */ + @RequestMapping(value = "clearCache.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage clearCache(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.type_error.395f") String type, + String machineId, + HttpServletRequest request) { + switch (type) { + case "serviceCacheFileSize": { + File tempPath = JpomApplication.getInstance().getTempPath(); + boolean clean = CommandUtil.systemFastDel(tempPath); + Assert.state(!clean, I18nMessageUtil.get("i18n.clear_file_cache_failed.5cd1")); + break; + } + case "serviceIpSize": + LoginControl.LFU_CACHE.clear(); + break; + case "serviceOldJarsSize": { + File oldJarsPath = JpomManifest.getOldJarsPath(); + boolean clean = CommandUtil.systemFastDel(oldJarsPath); + Assert.state(!clean, I18nMessageUtil.get("i18n.clear_old_version_package_failed.021c")); + break; + } + default: + return this.tryRequestMachine(machineId, request, NodeUrl.ClearCache); + } + return JsonMessage.success(I18nMessageUtil.get("i18n.clear_success.2685")); + } + + /** + * 清理错误的工作空间数据 + * + * @param tableName 类型 + * @return json + */ + @GetMapping(value = "clear-error-workspace", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage clearErrorWorkspace(@ValidatorItem String tableName) { + dataInitEvent.clearErrorWorkspace(tableName); + return JsonMessage.success(I18nMessageUtil.get("i18n.cleanup_succeeded.02ea")); + } + + @GetMapping(value = "async-refresh-cache", produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage refresh() { + Assert.state(!this.refreshCacheIng, I18nMessageUtil.get("i18n.refreshing_cache.c969")); + I18nThreadUtil.execute(() -> { + try { + this.refreshCacheIng = true; + this.executeTask(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.manual_cache_refresh_exception.9d91"), e); + } finally { + this.refreshCacheIng = false; + } + }); + return JsonMessage.success(I18nMessageUtil.get("i18n.async_refresh_in_progress.5550")); + } + + @Override + public void refreshCache() { + File file = jpomApplication.getTempPath(); + this.tempFileSize = FileUtil.size(file); + File oldJarsPath = JpomManifest.getOldJarsPath(); + this.oldJarsSize = FileUtil.size(oldJarsPath); + } + + @Override + public void executeTask() { + this.dataSize = jpomApplication.dataSize(); + BuildUtil.reloadCacheSize(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/system/controller/ClusterInfoController.java b/modules/server/src/main/java/org/dromara/jpom/func/system/controller/ClusterInfoController.java new file mode 100644 index 0000000000..df93fe0b14 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/system/controller/ClusterInfoController.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.system.controller; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.stream.CollectorUtil; +import cn.hutool.core.util.StrUtil; +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.i18n.I18nMessageUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.func.system.model.ClusterInfoModel; +import org.dromara.jpom.func.system.service.ClusterInfoService; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.WorkspaceModel; +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.WorkspaceService; +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.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author bwcx_jzy + * @since 2023/8/20 + */ +@RestController +@RequestMapping(value = "/cluster/") +@Feature(cls = ClassFeature.CLUSTER_INFO) +@SystemPermission() +@Slf4j +public class ClusterInfoController { + + private final ClusterInfoService clusterInfoService; + private final WorkspaceService workspaceService; + + public ClusterInfoController(ClusterInfoService clusterInfoService, + WorkspaceService workspaceService) { + this.clusterInfoService = clusterInfoService; + this.workspaceService = workspaceService; + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto listPage = clusterInfoService.listPage(request); + return JsonMessage.success("", listPage); + } + + @GetMapping(value = "list-all", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listAll() { + List list = clusterInfoService.list(); + return JsonMessage.success("", list); + } + + /** + * 查询所有可以管理的分组名 + * + * @return json + */ + @GetMapping(value = "list-link-groups") + @Feature(method = MethodFeature.LIST) + public IJsonMessage listLinkGroups() { + // + List all = clusterInfoService.listLinkGroups(); + // 查询集群已经绑定的分组 + List list = clusterInfoService.list(); + Map> map = list.stream() + .map(clusterInfoModel -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", clusterInfoModel.getName()); + jsonObject.put("id", clusterInfoModel.getId()); + jsonObject.put("linkGroup", clusterInfoModel.getLinkGroup()); + return jsonObject; + }) + .flatMap((Function>) jsonObject -> { + String string = jsonObject.getString("linkGroup"); + List list1 = StrUtil.splitTrim(string, StrUtil.COMMA); + return list1.stream() + .map(s -> { + JSONObject clone = jsonObject.clone(); + clone.remove("linkGroup"); + clone.put("group", s); + return clone; + }); + }) + .collect(CollectorUtil.groupingBy(o -> o.getString("group"), Collectors.toList())); + // + JSONObject jsonObject = new JSONObject(); + jsonObject.put("linkGroups", all); + jsonObject.put("groupMap", map); + + return JsonMessage.success("", jsonObject); + } + + /** + * 修改集群 + * + * @param id ID + * @return json + */ + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@ValidatorItem(msg = "i18n.data_id_cannot_be_empty.403b") String id, + @ValidatorItem(msg = "i18n.cluster_name_required.5ca6") String name, + String url, + @ValidatorItem(msg = "i18n.associated_group_required.5889") String linkGroup) { + Opt.ofBlankAble(url).ifPresent(s -> Validator.validateUrl(s, I18nMessageUtil.get("i18n.correct_url_required.67a3"))); + // + List list = StrUtil.splitTrim(linkGroup, StrUtil.COMMA); + Assert.notEmpty(list, I18nMessageUtil.get("i18n.associated_group2_required.bd05")); + // + ClusterInfoModel infoModel = new ClusterInfoModel(); + infoModel.setId(id); + infoModel.setName(name); + infoModel.setLinkGroup(linkGroup); + infoModel.setUrl(url); + clusterInfoService.updateById(infoModel); + return JsonMessage.success(I18nMessageUtil.get("i18n.modify_success.69be")); + } + + + /** + * 删除集群 + * + * @param id ID + * @return json + */ + @GetMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + @SystemPermission(superUser = true) + public IJsonMessage delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "i18n.data_id_cannot_be_empty.403b") String id) { + // + ClusterInfoModel infoModel = clusterInfoService.getByKey(id); + Assert.notNull(infoModel, I18nMessageUtil.get("i18n.cluster_not_exist.4098")); + Assert.state(!clusterInfoService.online(infoModel), I18nMessageUtil.get("i18n.cannot_delete_online_cluster.11ad")); + // 如果还有工作空间绑定,不能删除集群 + WorkspaceModel workspaceModel = new WorkspaceModel(); + workspaceModel.setClusterInfoId(infoModel.getId()); + long count = workspaceService.count(workspaceModel); + Assert.state(count == 0, I18nMessageUtil.get("i18n.current_cluster_is_bound_to_workspace_cannot_be_deleted_directly.94c2")); + // + clusterInfoService.delByKey(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/system/controller/TriggerTokenController.java b/modules/server/src/main/java/org/dromara/jpom/func/system/controller/TriggerTokenController.java new file mode 100644 index 0000000000..6031fd6ccd --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/system/controller/TriggerTokenController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.system.controller; + +import cn.hutool.core.bean.BeanUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.BaseIdModel; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.TriggerTokenLogBean; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 24/1/17 017 + */ +@RestController +@RequestMapping(value = "system/trigger") +@Feature(cls = ClassFeature.SYSTEM_CACHE) +@SystemPermission +public class TriggerTokenController { + + private final TriggerTokenLogServer triggerTokenLogServer; + + public TriggerTokenController(TriggerTokenLogServer triggerTokenLogServer) { + this.triggerTokenLogServer = triggerTokenLogServer; + } + + @GetMapping(value = "all-type", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> allType() { + List jsonObjects = triggerTokenLogServer.allType(); + return JsonMessage.success("", jsonObjects); + } + + @GetMapping(value = "delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage delete(String id) { + triggerTokenLogServer.delete(id); + return JsonMessage.success(I18nMessageUtil.get("i18n.delete_success.0007")); + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto listPage = triggerTokenLogServer.listPage(request); + listPage.each(triggerTokenLogBean -> { + String type = triggerTokenLogBean.getType(); + ITriggerToken byType = triggerTokenLogServer.getByType(type); + if (byType == null) { + triggerTokenLogBean.setDataName(I18nMessageUtil.get("i18n.type_not_exist_error.09de") + type); + } else { + BaseIdModel byKey = byType.getByKey(triggerTokenLogBean.getDataId()); + if (byKey == null) { + triggerTokenLogBean.setDataName(I18nMessageUtil.get("i18n.associated_data_lost_error.becb")); + } else { + Object name = BeanUtil.getProperty(byKey, "name"); + if (name == null) { + triggerTokenLogBean.setDataName(I18nMessageUtil.get("i18n.associated_data_name_not_exist_error.583e")); + } else { + triggerTokenLogBean.setDataName(name.toString()); + } + } + } + }); + return JsonMessage.success("", listPage); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/system/model/ClusterInfoModel.java b/modules/server/src/main/java/org/dromara/jpom/func/system/model/ClusterInfoModel.java new file mode 100644 index 0000000000..3e8f08bbb2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/system/model/ClusterInfoModel.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.system.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; + +/** + * @author bwcx_jzy + * @since 2023/8/19 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "CLUSTER_INFO", + nameKey = "i18n.cluster_info.32e0") +@Data +public class ClusterInfoModel extends BaseUserModifyDbModel { + /** + * 集群Id + */ + private String clusterId; + /** + * 集群名称 + */ + private String name; + /** + * 集群地址 + */ + private String url; + /** + * 集群关联的分组 + */ + private String linkGroup; + /** + * 最后心跳时间 + */ + private Long lastHeartbeat; + /** + * 主机名 + */ + private String localHostName; + /** + * jpom 版本 + */ + private String jpomVersion; + /** + * 集群地址状态消息 + */ + private String statusMsg; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/system/service/ClusterInfoService.java b/modules/server/src/main/java/org/dromara/jpom/func/system/service/ClusterInfoService.java new file mode 100644 index 0000000000..d186f40bfa --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/system/service/ClusterInfoService.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.system.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.Method; +import cn.keepbx.jpom.event.IAsyncLoad; +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.common.JpomManifest; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.ClusterConfig; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.assets.server.MachineNodeServer; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.func.system.model.ClusterInfoModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/8/19 + */ +@Service +@Slf4j +public class ClusterInfoService extends BaseDbService implements IAsyncLoad, Runnable { + + + private final ClusterConfig clusterConfig; + private static final String TASK_ID = "system_monitor_cluster"; + + private final WorkspaceService workspaceService; + /** + * 是否为多集群 + */ + private boolean multiServer = false; + + public ClusterInfoService(ServerConfig serverConfig, + WorkspaceService workspaceService) { + this.clusterConfig = serverConfig.getCluster(); + this.workspaceService = workspaceService; + } + + /** + * 获取当前集群 + * + * @return 集群信息 + */ + public ClusterInfoModel getCurrent() { + ClusterInfoModel clusterInfoModel = this.getByKey(JpomManifest.getInstance().getInstallId()); + Assert.notNull(clusterInfoModel, I18nMessageUtil.get("i18n.cluster_does_not_exist.97a4")); + return clusterInfoModel; + } + + /** + * 是否为多服务,集群模式 + * + * @return true + */ + public boolean isMultiServer() { + return multiServer; + } + + @Override + public void startLoad() { + // 启动心跳检测 + int heartSecond = clusterConfig.getHeartSecond(); + ScheduledExecutorService scheduler = JpomApplication.getScheduledExecutorService(); + scheduler.scheduleWithFixedDelay(this, 0, heartSecond, TimeUnit.SECONDS); + // 判断是否为多集群模式 + this.multiServer = this.count() > 1; + } + + @Override + public void run() { + int heartSecond = clusterConfig.getHeartSecond(); + try { + CronUtils.TaskStat taskStat = CronUtils.getTaskStat(TASK_ID, StrUtil.format(I18nMessageUtil.get("i18n.execution_frequency.d014"), heartSecond)); + taskStat.onStart(); + // 判断是否为多集群模式 + this.multiServer = this.count() > 1; + // + JpomManifest jpomManifest = JpomManifest.getInstance(); + String installId = jpomManifest.getInstallId(); + ClusterInfoModel byKey = this.getByKey(installId); + if (byKey == null) { + // 初始安装 + this.insert(this.createDefault(installId)); + // 自动绑定默认数据 + this.bindDefault(installId); + return; + } + // 更新数据 + ClusterInfoModel clusterInfoModel = new ClusterInfoModel(); + clusterInfoModel.setId(byKey.getId()); + if (!StrUtil.equals(byKey.getClusterId(), clusterConfig.getId())) { + log.warn(I18nMessageUtil.get("i18n.cluster_id_changed.6e49"), byKey.getClusterId(), clusterConfig.getId()); + clusterInfoModel.setClusterId(clusterConfig.getId()); + } + clusterInfoModel.setLocalHostName(NetUtil.getLocalHostName()); + clusterInfoModel.setLastHeartbeat(SystemClock.now()); + clusterInfoModel.setJpomVersion(jpomManifest.getVersion()); + // 测试 + try { + String url = byKey.getUrl(); + if (StrUtil.isEmpty(url)) { + clusterInfoModel.setStatusMsg(I18nMessageUtil.get("i18n.address_not_configured.f2eb")); + } else { + this.testUrl(url); + clusterInfoModel.setStatusMsg("OK"); + } + } catch (Exception e) { + clusterInfoModel.setStatusMsg(e.getMessage()); + } + this.updateById(clusterInfoModel); + // 检查是否重复 + Entity entity = Entity.create(); + entity.set("clusterId", clusterConfig.getId()); + entity.set("id", StrUtil.format(" <> {}", installId)); + List clusterInfoModels = this.listByEntity(entity); + if (CollUtil.isNotEmpty(clusterInfoModels)) { + for (ClusterInfoModel infoModel : clusterInfoModels) { + log.error(I18nMessageUtil.get("i18n.cluster_id_conflict.45b7"), clusterConfig.getId(), infoModel.getId(), infoModel.getName()); + } + } + // 通知任务结束 + taskStat.onSucceeded(); + } catch (Throwable throwable) { + CronUtils.TaskStat taskStat = CronUtils.getTaskStat(TASK_ID, StrUtil.format(I18nMessageUtil.get("i18n.execution_frequency.d014"), heartSecond)); + taskStat.onFailed(TASK_ID, throwable); + } + } + + private void testUrl(String url) { + // + UrlBuilder urlBuilder = UrlBuilder.ofHttp(url); + urlBuilder.addPath(ServerConst.CHECK_SYSTEM); + HttpRequest httpRequest = HttpRequest.of(urlBuilder).timeout(30 * 1000).method(Method.GET); + try { + JSONObject jsonObject = httpRequest.thenFunction(httpResponse -> { + String body = httpResponse.body(); + return JSONObject.parseObject(body); + }); + int code = jsonObject.getIntValue(JsonMessage.CODE); + Assert.state(code == JsonMessage.DEFAULT_SUCCESS_CODE, () -> { + String msg = jsonObject.getString(JsonMessage.MSG); + msg = StrUtil.emptyToDefault(msg, jsonObject.toString()); + return StrUtil.format(I18nMessageUtil.get("i18n.cluster_status_code_exception.9d89"), code, msg); + }); + // + JSONObject data = jsonObject.getJSONObject("data"); + Assert.notNull(data, I18nMessageUtil.get("i18n.cluster_response_incorrect.c08a")); + boolean expression = data.containsKey("routerBase") && data.containsKey("extendPlugins"); + Assert.state(expression, I18nMessageUtil.get("i18n.incorrect_cluster_address.893f")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.check_cluster_info_exception.7b0c"), e); + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.cluster_address_check_exception.cd92") + e.getMessage()); + } + } + + /** + * 自动帮忙集群相关的默认数据 + * + * @param installId 安装Id + */ + private void bindDefault(String installId) { + long count = this.count(); + if (count != 1) { + log.debug(I18nMessageUtil.get("i18n.multiple_clusters_exist.196b")); + return; + } + // 所以工作空间自动绑定集群Id + String sql = "update " + workspaceService.getTableName() + " set clusterInfoId=?"; + workspaceService.execute(sql, installId); + // 获取所有的资产分组 + List list = this.listLinkGroups(); + String join = CollUtil.join(list, StrUtil.COMMA); + // + ClusterInfoModel clusterInfoModel = new ClusterInfoModel(); + clusterInfoModel.setId(installId); + clusterInfoModel.setLinkGroup(join); + this.updateById(clusterInfoModel); + } + + /** + * 查询集群可以管理的分组名 + * + * @return list + */ + public List listLinkGroups() { + MachineDockerServer machineDockerServer = SpringUtil.getBean(MachineDockerServer.class); + MachineNodeServer machineNodeServer = SpringUtil.getBean(MachineNodeServer.class); + MachineSshServer machineSshServer = SpringUtil.getBean(MachineSshServer.class); + List nodeGroup = machineNodeServer.listGroupName(); + List sshGroup = machineSshServer.listGroupName(); + List dockerGroup = machineDockerServer.listGroupName(); + // + List all = new ArrayList<>(); + CollUtil.addAll(all, nodeGroup); + CollUtil.addAll(all, sshGroup); + CollUtil.addAll(all, dockerGroup); + // + all = all.stream() + .distinct() + .collect(Collectors.toList()); + return all; + } + + /** + * 创建默认的集群数据 + * + * @param installId 系统安装 id + * @return 默认数据 + */ + private ClusterInfoModel createDefault(String installId) { + ClusterInfoModel clusterInfoModel = new ClusterInfoModel(); + clusterInfoModel.setId(installId); + clusterInfoModel.setName(I18nMessageUtil.get("i18n.default_cluster.38cf")); + clusterInfoModel.setCreateUser(UserModel.SYSTEM_ADMIN); + clusterInfoModel.setClusterId(clusterConfig.getId()); + clusterInfoModel.setLastHeartbeat(SystemClock.now()); + return clusterInfoModel; + } + + /** + * 判断集群是否在线 + * + * @param clusterInfoModel 集群信息 + * @return true 在线 + */ + public boolean online(ClusterInfoModel clusterInfoModel) { + if (clusterInfoModel == null) { + return false; + } + Long lastHeartbeat = clusterInfoModel.getLastHeartbeat(); + if (lastHeartbeat == null) { + return false; + } + long millis = TimeUnit.SECONDS.toMillis(clusterConfig.getHeartSecond()); + return lastHeartbeat > SystemClock.now() - millis; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/user/controller/UserLoginLogController.java b/modules/server/src/main/java/org/dromara/jpom/func/user/controller/UserLoginLogController.java new file mode 100644 index 0000000000..4882c40518 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/user/controller/UserLoginLogController.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.user.controller; + +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.func.user.model.UserLoginLogModel; +import org.dromara.jpom.func.user.server.UserLoginLogServer; +import org.dromara.jpom.model.PageResultDto; +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.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; + +/** + * @author bwcx_jzy + * @since 2023/3/9 + */ +@RestController +@RequestMapping(value = "/user/login-log") +@Feature(cls = ClassFeature.USER_LOGIN_LOG) +@SystemPermission +public class UserLoginLogController extends BaseServerController { + + private final UserLoginLogServer userLoginLogServer; + + public UserLoginLogController(UserLoginLogServer userLoginLogServer) { + this.userLoginLogServer = userLoginLogServer; + } + + /** + * 登录日志列表 + * + * @return json + */ + @RequestMapping(value = "list-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResult = userLoginLogServer.listPage(request); + return JsonMessage.success("", pageResult); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/user/controller/UserNotificationController.java b/modules/server/src/main/java/org/dromara/jpom/func/user/controller/UserNotificationController.java new file mode 100644 index 0000000000..ec07a81c09 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/user/controller/UserNotificationController.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.user.controller; + +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.user.dto.UserNotificationDto; +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.SystemParametersServer; +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; + +/** + * @author bwcx_jzy1 + * @since 2024/4/20 + */ +@RestController +@RequestMapping(value = "/user/notification") +@Feature(cls = ClassFeature.USER) +@SystemPermission +public class UserNotificationController { + + public static final String KEY = "SYSTEM-USER-NOTIFICATION"; + + private final SystemParametersServer systemParametersServer; + + public UserNotificationController(SystemParametersServer systemParametersServer) { + this.systemParametersServer = systemParametersServer; + } + + /** + * 获取通知 + * + * @return json + */ + @GetMapping(value = "get", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage getNotification() { + UserNotificationDto notificationDto = systemParametersServer.getConfigDefNewInstance(KEY, UserNotificationDto.class); + return JsonMessage.success("", notificationDto); + } + + + /** + * 保存通知 + * + * @return json + */ + @PostMapping(value = "save", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage saveNotification(HttpServletRequest request) { + UserNotificationDto userNotification = ServletUtil.toBean(request, UserNotificationDto.class, true); + Assert.notNull(userNotification, I18nMessageUtil.get("i18n.configure_user_notification.250d")); + userNotification.verify(); + systemParametersServer.upsert(KEY, userNotification, ""); + return JsonMessage.success(I18nMessageUtil.get("i18n.save_succeeded.3b10")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/user/dto/UserNotificationDto.java b/modules/server/src/main/java/org/dromara/jpom/func/user/dto/UserNotificationDto.java new file mode 100644 index 0000000000..adcb9abd71 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/user/dto/UserNotificationDto.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.user.dto; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy1 + * @since 2024/4/20 + */ +@Data +public class UserNotificationDto { + /** + * 是否开启公告 + */ + private Boolean enabled; + /** + * 是否可以关闭 + */ + private Boolean closable; + /** + * 公告级别 + */ + private Level level; + /** + * 公告标题 + */ + private String title; + /** + * 公告内容 + */ + private String content; + + public enum Level { + info, warning, error + } + + public void verify() { + if (this.enabled != null && this.enabled) { + Assert.state(!StrUtil.isAllBlank(this.title, this.content), I18nMessageUtil.get("i18n.configure_announcement_title_or_content.7894")); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/user/model/UserLoginLogModel.java b/modules/server/src/main/java/org/dromara/jpom/func/user/model/UserLoginLogModel.java new file mode 100644 index 0000000000..f74a8d4828 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/user/model/UserLoginLogModel.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.user.model; + +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 2023/3/9 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "USER_LOGIN_LOG", + nameKey = "i18n.user_login_log.0c00") +@Data +@NoArgsConstructor +public class UserLoginLogModel extends BaseUserModifyDbModel { + + /** + * 操作ip + */ + private String ip; + + /** + * 用户名称 + */ + private String username; + + /** + * 浏览器标识 + */ + private String userAgent; + + /** + * 是否使用 mfa + */ + private Boolean useMfa; + + /** + * 是否成功 + */ + private Boolean success; + + /** + * 错误原因 + *

+ * 0 正常登录 + * 1 密码错误 + * 2 被锁定 + * 3 续期 + * 4 账号被禁用 + * 5 登录成功,但是需要 mfa 验证 + * 6 oauth2 登录成功 + */ + private Integer operateCode; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/user/server/UserLoginLogServer.java b/modules/server/src/main/java/org/dromara/jpom/func/user/server/UserLoginLogServer.java new file mode 100644 index 0000000000..55c2708261 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/user/server/UserLoginLogServer.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.func.user.server; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.Header; +import org.dromara.jpom.func.user.model.UserLoginLogModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2023/3/9 + */ +@Service +public class UserLoginLogServer extends BaseDbService { + + + /** + * 查询指定用户的登录日志 + * + * @param request 请求信息 + * @param userId 用户id + * @return page + */ + public PageResultDto listPageByUserId(HttpServletRequest request, String userId) { + Map paramMap = ServletUtil.getParamMap(request); + paramMap.put("modifyUser", userId); + return super.listPage(paramMap); + } + + /** + * 记录登录日志 + * + * @param userModel 用户 + * @param success 是否成功 + * @param useMfa 是否使用 mfa + * @param request 请求信息 + */ + public void log(UserModel userModel, boolean success, boolean useMfa, int operateCode, HttpServletRequest request) { + UserLoginLogModel userLoginLogModel = new UserLoginLogModel(); + userLoginLogModel.setModifyUser(userModel.getId()); + userLoginLogModel.setUsername(userModel.getName()); + userLoginLogModel.setSuccess(success); + userLoginLogModel.setUseMfa(useMfa); + userLoginLogModel.setOperateCode(operateCode); + userLoginLogModel.setIp(ServletUtil.getClientIP(request)); + userLoginLogModel.setUserAgent(ServletUtil.getHeader(request, Header.USER_AGENT.getValue(), CharsetUtil.CHARSET_UTF_8)); + this.insert(userLoginLogModel); + } + + /** + * 记录登录日志 + * + * @param userModel 用户 + * @param useMfa 是否使用 mfa + * @param request 请求信息 + */ + public void success(UserModel userModel, int code, boolean useMfa, HttpServletRequest request) { + this.log(userModel, true, useMfa, code, request); + } + + /** + * 记录登录日志 + * + * @param userModel 用户 + * @param useMfa 是否使用 mfa + * @param code 错误码 + * @param request 请求信息 + */ + public void fail(UserModel userModel, int code, boolean useMfa, HttpServletRequest request) { + this.log(userModel, false, useMfa, code, request); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseGroupModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseGroupModel.java new file mode 100644 index 0000000000..434ed0dce1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseGroupModel.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2022/1/5 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseGroupModel extends BaseWorkspaceModel { + + /** + * 分组 + */ + private String group; + + @Override + public String toString() { + return super.toString(); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseGroupNameModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseGroupNameModel.java new file mode 100644 index 0000000000..e4ecd0be42 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseGroupNameModel.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2023/2/25 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseGroupNameModel extends BaseUserModifyDbModel { + /** + * 名称 + */ + private String name; + /** + * 分组名称 + */ + private String groupName; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseMachineModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseMachineModel.java new file mode 100644 index 0000000000..2e3fccc4d4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseMachineModel.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2023/2/18 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseMachineModel extends BaseGroupModel { + + /** + * 机器id + */ + private String machineId; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseNodeGroupModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseNodeGroupModel.java new file mode 100644 index 0000000000..bb480a20ed --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseNodeGroupModel.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2023/2/8 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseNodeGroupModel extends BaseNodeModel { + + /** + * 分组 + */ + private String group; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseNodeModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseNodeModel.java new file mode 100644 index 0000000000..ab3d734ebf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseNodeModel.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.crypto.SecureUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.model.data.NodeModel; +import org.springframework.util.Assert; + +/** + * 节点 数据 + * + * @author bwcx_jzy + * @since 2021/12/05 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseNodeModel extends BaseWorkspaceModel { + + /** + * 节点Id + * + * @see NodeModel + */ + private String nodeId; + /** + * 节点名称 + */ + private String nodeName; + /** + * 工作空间名称 + */ + private String workspaceName; + + @Override + public String toString() { + return super.toString(); + } + + public String fullId() { + String workspaceId = this.getWorkspaceId(); + + String nodeId = this.getNodeId(); + + String dataId = this.dataId(); + + return BaseNodeModel.fullId(workspaceId, nodeId, dataId); + } + + public static String fullId(String workspaceId, String nodeId, String dataId) { + + Assert.hasText(workspaceId, "workspaceId"); + + Assert.hasText(workspaceId, "nodeId"); + + Assert.hasText(workspaceId, "dataId"); + return SecureUtil.sha1(workspaceId + nodeId + dataId); + } + + /** + * 获取数据ID + * + * @return 数据ID + */ + public abstract String dataId(); + + /** + * 设置数据ID + * + * @param id 数据ID + */ + public abstract void dataId(String id); +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseUserModifyDbModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseUserModifyDbModel.java new file mode 100644 index 0000000000..1b7ee54250 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseUserModifyDbModel.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 带最后修改人字段 数据表实体 + * + * @author bwcx_jzy + * @since 2021/8/24 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseUserModifyDbModel extends BaseDbModel { + /** + * 修改人 + */ + private String modifyUser; + /** + * 创建人 + */ + private String createUser; + + @Override + public String toString() { + return super.toString(); + } + + public void setCreateUser(String createUser) { + if (this.hasCreateUser()) { + this.createUser = createUser; + } + } + + /** + * 是否开启创建人字段 + * + * @return true 开启 + */ + protected boolean hasCreateUser() { + return false; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/BaseWorkspaceModel.java b/modules/server/src/main/java/org/dromara/jpom/model/BaseWorkspaceModel.java new file mode 100644 index 0000000000..a72def263b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/BaseWorkspaceModel.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.data.WorkspaceModel; + +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 工作空间 数据 + * + * @author bwcx_jzy + * @since 2021/12/04 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseWorkspaceModel extends BaseUserModifyDbModel { + + /** + * 工作空间ID + * + * @see WorkspaceModel + * @see Const#WORKSPACE_ID_REQ_HEADER + */ + private String workspaceId; + + @Override + public String toString() { + return super.toString(); + } + + public boolean global() { + return StrUtil.equals(this.workspaceId, ServerConst.WORKSPACE_GLOBAL); + } + + /** + * 所有实现过的 class + * + * @return set + */ + public static Set> allClass() { + return ClassUtil.scanPackageBySuper("org.dromara.jpom", BaseWorkspaceModel.class); + } + + /** + * 所有实现过的 class + * + * @return set + */ + public static Set> allTableClass() { + Set> classes1 = allClass(); + return classes1.stream() + .filter(aClass -> aClass.isAnnotationPresent(TableName.class)) + .collect(Collectors.toSet()); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/BackupInfoModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/BackupInfoModel.java new file mode 100644 index 0000000000..fac376359a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/BackupInfoModel.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; + +/** + * @author Hotstrip + * @since 2021-11-18 + * Backup info with H2 database + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "BACKUP_INFO", + nameKey = "i18n.data_backup.9e26", modes = DbExtConfig.Mode.H2) +@Data +public class BackupInfoModel extends BaseUserModifyDbModel { + + /** + * 备份名称 + */ + private String name; + /** + * 文件地址,绝对路径 + */ + private String filePath; + /** + * 备份类型{0: 全量, 1: 部分, 2: 导入, 3 自动} + */ + private Integer backupType; + /** + * 文件大小 + */ + private Long fileSize; + /** + * SHA1SUM + */ + private String sha1Sum; + + /** + * 状态{0: 默认; 1: 成功; 2: 失败} + */ + private Integer status; + + /** + * 服务端版本 + */ + private String version; + + /** + * 打包时间 + */ + private Long baleTimeStamp; + /** + * 文件是否存在 + */ + @PropIgnore + private Boolean fileExist; + +} 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 new file mode 100644 index 0000000000..1b7dc7ad25 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/BuildInfoModel.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Tolerate; +import org.dromara.jpom.build.BuildExtraModule; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseGroupModel; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.log.BuildHistoryLog; +import org.dromara.jpom.util.StringUtil; + +/** + * new BuildModel class, for replace old BuildModel + * + * @author Hotstrip + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "BUILD_INFO", + nameKey = "i18n.build_info.224a") +@Data +@Builder +public class BuildInfoModel extends BaseGroupModel { + + /** + * 仓库 ID + */ + private String repositoryId; + /** + * 名称 + */ + private String name; + /** + * 构建 ID + * + * @see BuildHistoryLog#getBuildNumberId() + */ + private Integer buildId; + /** + * 分支 + */ + private String branchName; + /** + * 标签 + */ + private String branchTagName; + /** + * 构建命令 + */ + private String script; + /** + * 构建产物目录 + */ + private String resultDirFile; + /** + * 发布方法{0: 不发布, 1: 节点分发, 2: 分发项目, 3: SSH} + */ + private Integer releaseMethod; + /** + * 发布方法执行数据关联字段 + */ + private String releaseMethodDataId; + /** + * 状态 + * + * @see BuildStatus + */ + private Integer status; + /** + * 状态消息 + */ + private String statusMsg; + /** + * 触发器token + */ + private String triggerToken; + /** + * 额外信息,JSON 字符串格式 + * + * @see BuildExtraModule + */ + private String extraData; + /** + * 构建 webhook + */ + private String webhook; + /** + * 定时构建表达式 + */ + private String autoBuildCron; + /** + * 源码目录是否存在 + */ + @PropIgnore + private Boolean sourceDirExist; + /** + * 产物文件是否存在 + */ + @PropIgnore + private Boolean resultHasFile; + /** + * 构建方式 0 本地构建 1 docker 构建 + */ + private Integer buildMode; + /** + * 仓库代码最后一次变动信息(ID,git 为 commit hash, svn 最后的版本号) + */ + private String repositoryLastCommitId; + /** + * 排序 + */ + private Float sortValue; + /** + * 构建环境变量 + */ + private String buildEnvParameter; + /** + * 别名码 + */ + private String aliasCode; + /** + * 产物保留天数 + */ + private Integer resultKeepDay; + + @Tolerate + public BuildInfoModel() { + } + + /** + * 获取构建的扩展数据 + * + * @return extraData + */ + public BuildExtraModule extraData() { + BuildExtraModule buildExtraModule = StringUtil.jsonConvert(this.getExtraData(), BuildExtraModule.class); + if (buildExtraModule != null) { + if (this.releaseMethodDataId != null) { + // 兼容数据迁移 + buildExtraModule.setReleaseMethodDataId(this.releaseMethodDataId); + } + } + return buildExtraModule; + } + + public static String getBuildIdStr(int buildId) { + return String.format("#%s", buildId); + } + + @Override + protected boolean hasCreateUser() { + return true; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/CommandExecLogModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/CommandExecLogModel.java new file mode 100644 index 0000000000..042addcffd --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/CommandExecLogModel.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.annotation.PropIgnore; +import cn.hutool.core.io.FileUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.BaseWorkspaceModel; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2021/12/22 + */ +@TableName(value = "COMMAND_EXEC_LOG", + nameKey = "i18n.command_execution_record.56d5", parents = CommandModel.class) +@Data +@EqualsAndHashCode(callSuper = true) +public class CommandExecLogModel extends BaseWorkspaceModel { + + /** + * 命令ID + */ + private String commandId; + + /** + * 批次ID + */ + private String batchId; + + /** + * ssh Id + */ + private String sshId; + + /** + * @see Status + */ + private Integer status; + + /** + * 命令名称 + */ + private String commandName; + + /** + * ssh 名称 + */ + private String sshName; + + /** + * 参数 + */ + private String params; + + /** + * 触发类型 {0,手动,1 自动触发} + */ + private Integer triggerExecType; + + /** + * 日志文件是否存在 + */ + @PropIgnore + private Boolean hasLog; + + /** + * 退出码 + */ + private Integer exitCode; + + public File logFile() { + return FileUtil.file(CommandExecLogModel.logFileDir(this.getCommandId()), batchId, this.getId() + ".log"); + } + + /** + * log 存储目录 + * + * @param commandId 命令ID + * @return 文件 + */ + public static File logFileDir(String commandId) { + return FileUtil.file(JpomApplication.getInstance().getDataPath(), "command_log", commandId); + } + + @Getter + public enum Status implements BaseEnum { + /** + * + */ + ING(0, "执行中"), + DONE(1, "执行结束"), + ERROR(2, "执行错误"), + SESSION_ERROR(3, "会话异常"), + ; + private final int code; + private final String desc; + + Status(int code, String desc) { + this.code = code; + this.desc = desc; + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/CommandModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/CommandModel.java new file mode 100644 index 0000000000..6141f72e21 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/CommandModel.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; + +/** + * 指令信息 + * + * @author : Arno + * @since : 2021/12/4 18:38 + */ +@TableName(value = "COMMAND_INFO", + nameKey = "i18n.command_management.621f") +@Data +@EqualsAndHashCode(callSuper = true) +public class CommandModel extends BaseWorkspaceModel { + /** + * 命令名称 + */ + private String name; + /** + * 命令描述 + */ + private String desc; + /** + * 指令内容 + */ + private String command; + /** + * 命令默认参数 + */ + private String defParams; + /** + * 默认关联大 ssh id + */ + private String sshIds; + /** + * 自动执行的 cron + */ + private String autoExecCron; + /** + * 触发器 token + */ + private String triggerToken; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/MailAccountModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/MailAccountModel.java new file mode 100644 index 0000000000..f5f5e9858d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/MailAccountModel.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.util.ObjectUtil; +import cn.keepbx.jpom.model.BaseJsonModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 系统邮箱配置 + * + * @author bwcx_jzy + * @since 2019/7/16 + **/ +@EqualsAndHashCode(callSuper = true) +@Data +public class MailAccountModel extends BaseJsonModel { + + public static final String ID = "MAIL_CONFIG"; + + /** + * SMTP服务器域名 + */ + private String host; + /** + * SMTP服务端口 + */ + private Integer port; + /** + * 用户名 + */ + private String user; + /** + * 密码 + */ + private String pass; + /** + * 发送方,遵循RFC-822标准 + */ + private String from; + /** + * 使用 SSL安全连接 + */ + private Boolean sslEnable; + /** + * 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口 + */ + @Deprecated + private Integer socketFactoryPort; + + /** + * 超时时间 + */ + private Integer timeout; + + /** + * 兼容端口 + * + * @return port + */ + public Integer getPort() { + if (sslEnable != null && sslEnable) { + if (socketFactoryPort != null) { + return socketFactoryPort; + } + } + return ObjectUtil.defaultIfNull(port, socketFactoryPort); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/MonitorModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/MonitorModel.java new file mode 100644 index 0000000000..aef7c20748 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/MonitorModel.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.model.BaseJsonModel; +import com.alibaba.fastjson2.JSON; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.util.StringUtil; + +import java.util.List; + +/** + * 监控管理实体 + * + * @author Arno + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "MONITOR_INFO", + nameKey = "i18n.monitor_info.f299") +@Data +public class MonitorModel extends BaseWorkspaceModel { + + private String name; + /** + * 监控的项目 + */ + private String projects; + /** + * 报警联系人 + */ + private String notifyUser; + /** + * 异常后是否自动重启 + */ + private Boolean autoRestart; + /** + * 监控周期 + * + * @see io.jpom.model.Cycle + */ + @Deprecated + private Integer cycle; + /** + * 监控定时周期 + */ + private String execCron; + /** + * 监控开启状态 + */ + private Boolean status; + /** + * 报警状态 + */ + private Boolean alarm; + /** + * webhook + */ + private String webhook; + + public String getExecCron() { + if (execCron == null) { + // 兼容旧版本 + if (cycle != null) { + return String.format("0 0/%s * * * ?", cycle); + } + } + return execCron; + } + + public boolean autoRestart() { + return autoRestart != null && autoRestart; + } + + /** + * 开启状态 + * + * @return true 启用 + */ + public boolean status(String autoExecCron) { + return status != null && status && StrUtil.isNotEmpty(autoExecCron); + } + + public List projects() { + return StringUtil.jsonConvertArray(projects, NodeProject.class); + } + + + public String getProjects() { + List projects = projects(); + return projects == null ? null : JSON.toJSONString(projects); + } + + public void projects(List projects) { + if (projects == null) { + this.projects = null; + } else { + this.projects = JSON.toJSONString(projects); + } + } + + public List notifyUser() { + return StringUtil.jsonConvertArray(notifyUser, String.class); + } + + public String getNotifyUser() { + List object = notifyUser(); + return object == null ? null : JSON.toJSONString(object); + } + + public void notifyUser(List notifyUser) { + if (notifyUser == null) { + this.notifyUser = null; + } else { + this.notifyUser = JSON.toJSONString(notifyUser); + } + } + + public boolean checkNodeProject(String nodeId, String projectId) { + List projects = projects(); + if (projects == null) { + return false; + } + for (NodeProject project : projects) { + if (project.getNode().equals(nodeId)) { + List projects1 = project.getProjects(); + if (projects1 == null) { + return false; + } + for (String s : projects1) { + if (projectId.equals(s)) { + return true; + } + } + } + } + return false; + } + + @Getter + public enum NotifyType implements BaseEnum { + /** + * 通知方式 + */ + dingding(0, "钉钉"), + mail(1, "邮箱"), + workWx(2, "企业微信"), + webhook(3, "webhook"), + ; + + private final int code; + private final String desc; + + NotifyType(int code, String desc) { + this.code = code; + this.desc = desc; + } + } + + /** + * 通知 + */ + public static class Notify extends BaseJsonModel { + private int style; + private String value; + + public Notify() { + } + + public Notify(NotifyType style, String value) { + this.style = style.getCode(); + this.value = value; + } + + public int getStyle() { + return style; + } + + public void setStyle(int style) { + this.style = style; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public static class NodeProject extends BaseJsonModel { + /** + * 节点 ID + */ + private String node; + /** + * 被监控的项目ID + */ + private List projects; + + public String getNode() { + return node; + } + + public void setNode(String node) { + this.node = node; + } + + public List getProjects() { + return projects; + } + + public void setProjects(List projects) { + this.projects = projects; + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/MonitorUserOptModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/MonitorUserOptModel.java new file mode 100644 index 0000000000..f7fb46fe58 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/MonitorUserOptModel.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import com.alibaba.fastjson2.JSON; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.util.StringUtil; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 监控用户操作实体 + * + * @author Arno + */ +@TableName(value = "MONITOR_USER_OPT", + nameKey = "i18n.monitoring_user_actions.f2d5") +public class MonitorUserOptModel extends BaseWorkspaceModel { + /** + * + */ + private String name; + /** + * 监控的人员 + */ + private String monitorUser; + /** + * 监控的功能 + * + * @see ClassFeature + */ + private String monitorFeature; + /** + * 监控的操作 + * + * @see MethodFeature + */ + private String monitorOpt; + /** + * 报警联系人 + */ + private String notifyUser; + + /** + * 监控开启状态 + */ + private Boolean status; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setMonitorUser(String monitorUser) { + this.monitorUser = monitorUser; + } + + public String getMonitorFeature() { + List object = monitorFeature(); + return object == null ? null : JSON.toJSONString(object); + } + + public List monitorFeature() { + return StringUtil.jsonConvertArray(monitorFeature, ClassFeature.class); + } + + public void setMonitorFeature(String monitorFeature) { + this.monitorFeature = monitorFeature; + } + + public void monitorFeature(List monitorFeature) { + if (monitorFeature == null) { + this.monitorFeature = null; + } else { + this.monitorFeature = JSON.toJSONString(monitorFeature.stream().map(Enum::name).collect(Collectors.toList())); + } + } + + public String getMonitorOpt() { + List object = monitorOpt(); + return object == null ? null : JSON.toJSONString(object); + } + + + public List monitorOpt() { + return StringUtil.jsonConvertArray(monitorOpt, MethodFeature.class); + } + + public void setMonitorOpt(String monitorOpt) { + this.monitorOpt = monitorOpt; + } + + public void monitorOpt(List monitorOpt) { + if (monitorOpt == null) { + this.monitorOpt = null; + } else { + this.monitorOpt = JSON.toJSONString(monitorOpt.stream().map(Enum::name).collect(Collectors.toList())); + } + } + + public void setNotifyUser(String notifyUser) { + this.notifyUser = notifyUser; + } + + public Boolean getStatus() { + return status; + } + + public String getMonitorUser() { + List object = monitorUser(); + return object == null ? null : JSON.toJSONString(object); + } + + public String getNotifyUser() { + List object = notifyUser(); + return object == null ? null : JSON.toJSONString(object); + + } + + public List monitorUser() { + return StringUtil.jsonConvertArray(monitorUser, String.class); + } + + public void monitorUser(List monitorUser) { + if (monitorUser == null) { + this.monitorUser = null; + } else { + this.monitorUser = JSON.toJSONString(monitorUser); + } + } + + public List notifyUser() { + return StringUtil.jsonConvertArray(notifyUser, String.class); + } + + public void notifyUser(List notifyUser) { + if (notifyUser == null) { + this.notifyUser = null; + } else { + this.notifyUser = JSON.toJSONString(notifyUser); + } + } + + + public boolean isStatus() { + return status != null && status; + } + + public void setStatus(Boolean status) { + this.status = status; + } +} 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 new file mode 100644 index 0000000000..23cc6d2b83 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/NodeModel.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.model.BaseMachineModel; + +/** + * 节点实体 + * + * @author bwcx_jzy + * @see MachineNodeModel + * @since 2019/4/16 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "NODE_INFO", + nameKey = "i18n.node_info.2dcf") +@Data +@NoArgsConstructor +public class NodeModel extends BaseMachineModel { + + @Deprecated + private String url; + @Deprecated + private String loginName; + @Deprecated + private String loginPwd; + private String name; + + /** + * 节点协议 + */ + @Deprecated + private String protocol; + /** + * 开启状态,如果关闭状态就暂停使用节点 1 启用 + */ + private Integer openStatus; + /** + * 节点超时时间 + */ + @Deprecated + private Integer timeOut; + /** + * 绑定的sshId + */ + private String sshId; + + /** + * http 代理 + */ + @Deprecated + private String httpProxy; + /** + * https 代理 类型 + */ + @Deprecated + private String httpProxyType; + /** + * 排序 + */ + private Float sortValue; + + @PropIgnore + private MachineNodeModel machineNodeData; + + @PropIgnore + private WorkspaceModel workspace; + /** + * jpom 项目数 + */ + private Integer jpomProjectCount; + /** + * jpom 脚本数据 + */ + private Integer jpomScriptCount; + + public boolean isOpenStatus() { + return openStatus != null && openStatus == 1; + } + + public NodeModel(String id) { + this.setId(id); + } + + public NodeModel(String id, String workspaceId) { + this.setId(id); + this.setWorkspaceId(workspaceId); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/RepositoryModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/RepositoryModel.java new file mode 100644 index 0000000000..93347b1867 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/RepositoryModel.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.BaseGroupModel; +import org.dromara.jpom.model.enums.GitProtocolEnum; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Hotstrip + * 仓库地址实体类 + */ +@TableName(value = "REPOSITORY", + nameKey = "i18n.repository_info.22cd") +@Data +@EqualsAndHashCode(callSuper = true) +public class RepositoryModel extends BaseGroupModel { + /** + * 名称 + */ + private String name; + /** + * 仓库地址 + */ + private String gitUrl; + /** + * 仓库类型{0: GIT, 1: SVN} + */ + private Integer repoType; + /** + * 拉取代码的协议{0: http, 1: ssh} + * + * @see GitProtocolEnum + */ + private Integer protocol; + /** + * 登录用户 + */ + private String userName; + /** + * 登录密码 + */ + private String password; + /** + * SSH RSA 公钥 + */ + @Deprecated + private String rsaPub; + /** + * SSH RSA 私钥 + */ + private String rsaPrv; + /** + * 排序 + */ + private Float sortValue; + /** + * 仓库连接超时时间 + */ + private Integer timeout; + + /** + * 返回协议类型,如果为 null 会尝试识别 http + * + * @return 枚举的值(1/0) + * @see GitProtocolEnum + */ + public Integer getProtocol() { + if (protocol != null) { + return protocol; + } + String gitUrl = this.getGitUrl(); + if (StrUtil.isEmpty(gitUrl)) { + return null; + } + if (HttpUtil.isHttps(gitUrl) || HttpUtil.isHttp(gitUrl)) { + return GitProtocolEnum.HTTP.getCode(); + } + return null; + } + + /** + * 转换为 map + * + * @return map + */ + public Map toMap() { + // + Map map = new HashMap<>(10); + map.put("url", this.getGitUrl()); + map.put("protocol", this.getProtocol()); + Integer protocolCode = this.getProtocol(); + GitProtocolEnum protocol = EnumUtil.likeValueOf(GitProtocolEnum.class, protocolCode); + if (protocol != null) { + map.put("protocolStr", protocol.name()); + } + map.put("username", this.getUserName()); + map.put("password", this.getPassword()); + map.put(Const.WORKSPACE_ID_REQ_HEADER, this.getWorkspaceId()); + map.put("rsaFile", BuildUtil.getRepositoryRsaFile(this)); + map.put("timeout", this.getTimeout()); + return map; + } + + /** + * 仓库类型 + */ + @Getter + public enum RepoType implements BaseEnum { + /** + * git + */ + Git(0, "Git"), + Svn(1, "Svn"), + ; + private final int code; + private final String desc; + + RepoType(int code, String desc) { + this.code = code; + this.desc = desc; + } + } + + @Override + protected boolean hasCreateUser() { + return true; + } +} 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 new file mode 100644 index 0000000000..0ad2a5f75d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/ServerWhitelist.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.dromara.jpom.common.i18n.I18nMessageUtil; +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 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ServerWhitelist extends BaseJsonModel { + + public static final String ID = "OUTGIVING_WHITELIST"; + + /** + * 不同工作空间的 ID + * + * @param workspaceId 工作空间ID + * @return id + */ + public static String workspaceId(String workspaceId) { + return ServerWhitelist.ID + StrUtil.DASHED + workspaceId; + } + + /** + * 项目的授权 + */ + private List outGiving; + + /** + * 允许远程下载的 host + */ + private Set allowRemoteDownloadHost; + + /** + * 静态目录 + */ + 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, I18nMessageUtil.get("i18n.no_current_static_directory_permission.ed70")); + } + + /** + * 判断指定 url 是否在授权范围 + * + * @param url url 地址 + */ + public void checkAllowRemoteDownloadHost(String url) { + Set allowRemoteDownloadHost = this.getAllowRemoteDownloadHost(); + Assert.state(CollUtil.isNotEmpty(allowRemoteDownloadHost), I18nMessageUtil.get("i18n.remote_addresses_not_configured.275e")); + List collect = allowRemoteDownloadHost.stream() + .filter(s -> StrUtil.startWith(url, s)) + .collect(Collectors.toList()); + Assert.state(CollUtil.isNotEmpty(collect), I18nMessageUtil.get("i18n.disallowed_download.06a3")); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/SshModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/SshModel.java new file mode 100644 index 0000000000..06085a7dc2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/SshModel.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.hutool.core.annotation.PropIgnore; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONArray; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.func.assets.controller.BaseSshFileController; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.model.BaseGroupModel; +import org.dromara.jpom.util.StringUtil; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * ssh 信息 + * + * @author bwcx_jzy + * @since 2019/8/9 + */ +@TableName(value = "SSH_INFO", + nameKey = "i18n.ssh_info.ebe6") +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class SshModel extends BaseGroupModel implements BaseSshFileController.ItemConfig { + + private String name; + @Deprecated + private String host; + @Deprecated + private Integer port; + @Deprecated + private String user; + @Deprecated + private String password; + /** + * 编码格式 + */ + @Deprecated + private String charset; + + /** + * ssh 私钥 + */ + @Deprecated + private String privateKey; + @Deprecated + private String connectType; + /** + * 文件目录 + */ + private String fileDirs; + /** + * 不允许执行的命令 + */ + private String notAllowedCommand; + /** + * 允许编辑的后缀文件 + */ + private String allowEditSuffix; + /** + * 节点超时时间 + */ + @Deprecated + private Integer timeout; + + /** + * ssh id + */ + private String machineSshId; + + @PropIgnore + private MachineSshModel machineSsh; + + @PropIgnore + private NodeModel linkNode; + + @PropIgnore + private WorkspaceModel workspace; + + public SshModel(String id) { + this.setId(id); + } + + + @Override + public List fileDirs() { + List strings = StringUtil.jsonConvertArray(this.fileDirs, String.class); + return Optional.ofNullable(strings) + .map(strings1 -> strings1.stream() + .map(s -> FileUtil.normalize(StrUtil.SLASH + s + StrUtil.SLASH)) + .collect(Collectors.toList())) + .orElse(null); + } + + public void fileDirs(List fileDirs) { + if (fileDirs != null) { + for (int i = fileDirs.size() - 1; i >= 0; i--) { + String s = fileDirs.get(i); + fileDirs.set(i, FileUtil.normalize(s)); + } + this.fileDirs = JSONArray.toJSONString(fileDirs); + } else { + this.fileDirs = StrUtil.EMPTY; + } + } + + + @Override + public List allowEditSuffix() { + return StringUtil.jsonConvertArray(this.allowEditSuffix, String.class); + } + + public void allowEditSuffix(List allowEditSuffix) { + if (allowEditSuffix == null) { + this.allowEditSuffix = null; + } else { + this.allowEditSuffix = JSONArray.toJSONString(allowEditSuffix); + } + } + + /** + * 检查是否包含禁止命令 + * + * @param sshItem 实体 + * @param inputItem 输入的命令 + * @return false 存在禁止输入的命令 + */ + public static boolean checkInputItem(SshModel sshItem, String inputItem) { + // 检查禁止执行的命令 + String notAllowedCommand = StrUtil.emptyToDefault(sshItem.getNotAllowedCommand(), StrUtil.EMPTY).toLowerCase(); + if (StrUtil.isEmpty(notAllowedCommand)) { + return true; + } + List split = StrUtil.splitTrim(notAllowedCommand, StrUtil.COMMA); + inputItem = inputItem.toLowerCase(); + List commands = StrUtil.splitTrim(inputItem, StrUtil.CR); + commands.addAll(StrUtil.split(inputItem, "&")); + for (String s : split) { + // + boolean anyMatch = commands.stream().anyMatch(item -> StrUtil.startWithAny(item, s + StrUtil.SPACE, ("&" + s + StrUtil.SPACE), StrUtil.SPACE + s + StrUtil.SPACE)); + if (anyMatch) { + return false; + } + // + anyMatch = commands.stream().anyMatch(item -> StrUtil.equals(item, s)); + if (anyMatch) { + return false; + } + } + return true; + } +} 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 new file mode 100644 index 0000000000..fad8d325e6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/SystemIpConfigModel.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import cn.keepbx.jpom.model.BaseJsonModel; + +/** + * @author bwcx_jzy + * @since 2021/4/18 + */ +public class SystemIpConfigModel extends BaseJsonModel { + + public static final String ID = "IP_CONFIG"; + + /** + * ip 授权 允许访问 + */ + private String allowed; + + /** + * 禁止 + */ + private String prohibited; + + public String getAllowed() { + return allowed; + } + + public void setAllowed(String allowed) { + this.allowed = allowed; + } + + public String getProhibited() { + return prohibited; + } + + public void setProhibited(String prohibited) { + this.prohibited = prohibited; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/SystemParametersModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/SystemParametersModel.java new file mode 100644 index 0000000000..99624c233e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/SystemParametersModel.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; +import org.dromara.jpom.util.StringUtil; + +import java.util.List; + +/** + * 系统参数 + * + * @author bwcx_jzy + * @since 2021/12/2 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "SYSTEM_PARAMETERS", + nameKey = "i18n.system_parameters.c7b0") +@Data +public class SystemParametersModel extends BaseUserModifyDbModel { + + /** + * 参数值 + */ + private String value; + /** + * 参数描述 + */ + private String description; + + public T jsonToBean(Class cls) { + return StringUtil.jsonConvert(this.getValue(), cls); + } + + public List jsonToBeanList(Class cls) { + return StringUtil.jsonConvertArray(this.getValue(), cls); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/WorkspaceEnvVarModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/WorkspaceEnvVarModel.java new file mode 100644 index 0000000000..c0dfe3286b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/WorkspaceEnvVarModel.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; + +/** + * 工作空间环境变量 + * + * @author bwcx_jzy + * @since 2021/12/10 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "WORKSPACE_ENV_VAR", + nameKey = "i18n.workspace_env_vars.f7e8", workspaceBind = 2) +@Data +public class WorkspaceEnvVarModel extends BaseWorkspaceModel { + + /** + * 名称 + */ + private String name; + /** + * 值 + */ + private String value; + /** + * 描述 + */ + private String description; + /** + * 节点ID + */ + private String nodeIds; + /** + * 隐私变量{1,隐私变量,0 非隐私变量(明文回显)} + */ + private Integer privacy; + + /** + * 触发器 token + */ + private String triggerToken; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/WorkspaceModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/WorkspaceModel.java new file mode 100644 index 0000000000..f7a1ff4e5d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/WorkspaceModel.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.data; + +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 2021/12/3 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "WORKSPACE", + nameKey = "i18n.workspace_label.98d6") +@Data +@NoArgsConstructor +public class WorkspaceModel extends BaseUserModifyDbModel { + + /** + * 名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + /** + * 分组 + */ + private String group; + /** + * 集群信息Id + * + * @see org.dromara.jpom.func.system.model.ClusterInfoModel + */ + private String clusterInfoId; + + public WorkspaceModel(String id) { + this.setId(id); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/docker/DockerInfoModel.java b/modules/server/src/main/java/org/dromara/jpom/model/docker/DockerInfoModel.java new file mode 100644 index 0000000000..d46f488351 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/docker/DockerInfoModel.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.docker; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.WorkspaceModel; + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName(value = "DOCKER_INFO", + nameKey = "i18n.docker_info.00d2") +public class DockerInfoModel extends BaseWorkspaceModel { + /** + * 名称 + */ + private String name; + /** + * 地址 + */ + @Deprecated + private String host; + /** + * 开启 tls 验证 + */ + @Deprecated + private Boolean tlsVerify; + /** + * 证书路径 + */ + @PropIgnore + private Boolean certExist; + /** + * 集群节点ID + */ + @Deprecated + private String swarmNodeId; + /** + * 最后心跳时间 + */ + @Deprecated + private Long lastHeartbeatTime; + /** + * 超时时间,单位 秒 + */ + @Deprecated + private Integer heartbeatTimeout; + /** + * 标签 + */ + private String tags; + /** + * 集群ID + */ + @Deprecated + private String swarmId; + + /** + * 仓库账号 + */ + @Deprecated + private String registryUsername; + + /** + * 仓库密码 + */ + @Deprecated + private String registryPassword; + + /** + * 仓库邮箱 + */ + @Deprecated + private String registryEmail; + + /** + * 仓库地址 + */ + @Deprecated + private String registryUrl; + + /** + * 机器 docker id + */ + private String machineDockerId; + + @PropIgnore + private MachineDockerModel machineDocker; + + @PropIgnore + private WorkspaceModel workspace; + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/docker/DockerSwarmInfoMode.java b/modules/server/src/main/java/org/dromara/jpom/model/docker/DockerSwarmInfoMode.java new file mode 100644 index 0000000000..3299b267ad --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/docker/DockerSwarmInfoMode.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.docker; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.WorkspaceModel; + +/** + * @author bwcx_jzy + * @since 2022/2/13 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName(value = "DOCKER_SWARM_INFO", + nameKey = "i18n.docker_cluster_info.a2eb") +public class DockerSwarmInfoMode extends BaseWorkspaceModel { + /** + * 集群名称 + */ + private String name; + /** + * 集群ID + */ + private String swarmId; + + /** + * 集群容器标签 + */ + private String tag; + + @PropIgnore + private MachineDockerModel machineDocker; + + @PropIgnore + private WorkspaceModel workspace; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/dto/UserLoginDto.java b/modules/server/src/main/java/org/dromara/jpom/model/dto/UserLoginDto.java new file mode 100644 index 0000000000..6a444a0f39 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/dto/UserLoginDto.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.dto; + +import lombok.Data; +import org.dromara.jpom.controller.user.UserWorkspaceModel; + +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2020/11/2 + */ +@Data +public class UserLoginDto { + + private String token; + + private String longTermToken; + + private List bindWorkspaceModels; + + + public UserLoginDto() { + } + + public UserLoginDto(String token, String jwtId) { + this.setLongTermToken(jwtId); + this.setToken(token); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/enums/BackupStatusEnum.java b/modules/server/src/main/java/org/dromara/jpom/model/enums/BackupStatusEnum.java new file mode 100644 index 0000000000..44ac17f0a6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/enums/BackupStatusEnum.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.enums; + +import org.dromara.jpom.model.BaseEnum; + +/** + * backup type + * + * @author Hotstrip + * @since 2021-11-27 + */ +public enum BackupStatusEnum implements BaseEnum { + /** + * 状态{0: 处理中; 1: 成功; 2: 失败} + */ + DEFAULT(0, "处理中"), + SUCCESS(1, "备份成功"), + FAILED(2, "备份失败"), + ; + + BackupStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + final int code; + final String desc; + + @Override + public int getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/enums/BackupTypeEnum.java b/modules/server/src/main/java/org/dromara/jpom/model/enums/BackupTypeEnum.java new file mode 100644 index 0000000000..adcb9f5baa --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/enums/BackupTypeEnum.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.enums; + +import org.dromara.jpom.model.BaseEnum; + +/** + * backup type + * + * @author Hotstrip + * @since 2021-11-24 + */ +public enum BackupTypeEnum implements BaseEnum { + /** + * 备份类型{0: 全量, 1: 部分} + */ + ALL(0, "全量备份"), + PART(1, "部分备份"), + IMPORT(2, "导入备份"), + AUTO(3, "自动备份"), + ; + + BackupTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + final int code; + final String desc; + + @Override + public int getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/enums/BuildReleaseMethod.java b/modules/server/src/main/java/org/dromara/jpom/model/enums/BuildReleaseMethod.java new file mode 100644 index 0000000000..e2e2091898 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/enums/BuildReleaseMethod.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.enums; + +import org.dromara.jpom.model.BaseEnum; + +/** + * @author bwcx_jzy + * @since 2021/8/27 + */ +public enum BuildReleaseMethod implements BaseEnum { + /** + * 发布 + */ + No(0, "不发布"), + Outgiving(1, "节点分发"), + Project(2, "项目"), + Ssh(3, "SSH"), + LocalCommand(4, "本地命令行"), + DockerImage(5, "Docker镜像"), + ; + private final int code; + private final String desc; + + BuildReleaseMethod(int code, String desc) { + this.code = code; + this.desc = desc; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/enums/BuildStatus.java b/modules/server/src/main/java/org/dromara/jpom/model/enums/BuildStatus.java new file mode 100644 index 0000000000..dd393d0e90 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/enums/BuildStatus.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.enums; + +import lombok.Getter; +import org.dromara.jpom.model.BaseEnum; + +/** + * @author bwcx_jzy + * @since 2021/8/27 + */ +@Getter +public enum BuildStatus implements BaseEnum { + /** + * + */ + No(0, "未构建"), + Ing(1, "构建中", true), + Success(2, "构建结束"), + Error(3, "构建失败"), + PubIng(4, "发布中", true), + PubSuccess(5, "发布成功"), + PubError(6, "发布失败"), + Cancel(7, "取消构建"), + Interrupt(8, "构建中断"), + WaitExec(9, "队列等待", true), + AbnormalShutdown(10, "异常关闭"), + ; + + private final int code; + private final String desc; + private final boolean progress; + + BuildStatus(int code, String desc) { + this.code = code; + this.desc = desc; + this.progress = false; + } + + BuildStatus(int code, String desc, boolean progress) { + this.code = code; + this.desc = desc; + this.progress = progress; + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/enums/GitProtocolEnum.java b/modules/server/src/main/java/org/dromara/jpom/model/enums/GitProtocolEnum.java new file mode 100644 index 0000000000..6cb18d9519 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/enums/GitProtocolEnum.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.enums; + +import org.dromara.jpom.model.BaseEnum; + +/** + * Git protocol + * + * @author Hotstrip + * @since 2021-08-26 + */ +public enum GitProtocolEnum implements BaseEnum { + /** + * http + */ + HTTP(0, "HTTP(S)"), + SSH(1, "SSH"), + ; + + GitProtocolEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + final int code; + final String desc; + + @Override + public int getCode() { + return code; + } + + @Override + public String getDesc() { + return desc; + } + +} 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 new file mode 100644 index 0000000000..4b01549cdf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/BuildHistoryLog.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.log; + +import cn.hutool.core.annotation.PropIgnore; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.build.BuildExtraModule; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.enums.BuildStatus; + +import java.util.Map; + +/** + * 构建历史记录 + * + * @author bwcx_jzy + * @see BuildExtraModule + * @since 2019/7/17 + **/ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "BUILDHISTORYLOG", + nameKey = "i18n.build_history.a05c", parents = BuildInfoModel.class) +@Data +public class BuildHistoryLog extends BaseWorkspaceModel { + /** + * 发布方式 + * + * @see BuildReleaseMethod + * @see BuildInfoModel#getReleaseMethod() + */ + private Integer releaseMethod; + + /** + * 构建产物目录 + */ + private String resultDirFile; + + /** + * 触发构建类型 触发类型{0,手动,1 触发器,2 自动触发,3 手动回滚} + */ + private Integer triggerBuildType; + + /** + * 关联的构建id + * + * @see BuildInfoModel#getId() + */ + private String buildDataId; + /** + * 构建名称 + */ + private String buildName; + /** + * 构建编号 + * + * @see BuildInfoModel#getBuildId() + */ + private Integer buildNumberId; + /** + * 来自的构建编号,回滚时存在 + */ + private Integer fromBuildNumberId; + /** + * 状态 + * + * @see BuildStatus + */ + private Integer status; + /** + * 状态消息 + */ + private String statusMsg; + /** + * 开始时间 + */ + private Long startTime; + /** + * 结束时间 + */ + private Long endTime; + /** + * 构建备注 + */ + private String buildRemark; + /** + * 构建其他信息 + * + * @see BuildExtraModule + */ + private String extraData; + /** + * 是否存在构建产物 + */ + @PropIgnore + private Boolean hasFile; + /** + * 是否存在日志 + */ + @PropIgnore + private Boolean hasLog; + /** + * 构建环境变量缓存 + */ + private String buildEnvCache; + /** + * 产物文件大小 + */ + private Long resultFileSize; + + /** + * 构建日志文件大小 + */ + private Long buildLogFileSize; + + public void setBuildRemark(String buildRemark) { + this.buildRemark = StrUtil.maxLength(buildRemark, 240); + } + + public EnvironmentMapBuilder toEnvironmentMapBuilder() { + String buildEnvCache = this.getBuildEnvCache(); + JSONObject jsonObject = Opt.ofBlankAble(buildEnvCache).map(JSONObject::parseObject).orElse(new JSONObject()); + Map map = jsonObject.to(new TypeReference>() { + }); + return EnvironmentMapBuilder.builder(map); + } + +// public void fillLogValue(BuildExtraModule buildExtraModule) { +// // +// this.setAfterOpt(ObjectUtil.defaultIfNull(buildExtraModule.getAfterOpt(), AfterOpt.No.getCode())); +// this.setReleaseMethod(buildExtraModule.getReleaseMethod()); +// this.setReleaseCommand(buildExtraModule.getReleaseCommand()); +// this.setReleasePath(buildExtraModule.getReleasePath()); +// this.setReleaseMethodDataId(buildExtraModule.getReleaseMethodDataId()); +// this.setClearOld(buildExtraModule.isClearOld()); +// this.setResultDirFile(buildExtraModule.getResultDirFile()); +// this.setDiffSync(buildExtraModule.isDiffSync()); +// } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/log/MonitorNotifyLog.java b/modules/server/src/main/java/org/dromara/jpom/model/log/MonitorNotifyLog.java new file mode 100644 index 0000000000..e1c359de41 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/MonitorNotifyLog.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.log; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.MonitorModel; + +/** + * 监控日志 + * + * @author bwcx_jzy + * @since 2019/7/13 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "MONITORNOTIFYLOG", + nameKey = "i18n.monitoring_notifications.de94", parents = MonitorModel.class) +@Data +public class MonitorNotifyLog extends BaseWorkspaceModel { + + + private String nodeId; + private String projectId; + /** + * 异常发生时间 + */ + private Long createTime; + private String title; + private String content; + /** + * 项目状态状态 + */ + private Boolean status; + /** + * 通知方式 + * + * @see MonitorModel.NotifyType + */ + private Integer notifyStyle; + /** + * 通知发送状态 + */ + private Boolean notifyStatus; + /** + * 监控id + */ + private String monitorId; + /** + * 通知对象 + */ + private String notifyObject; + /** + * 通知异常消息 + */ + private String notifyError; + + public boolean status() { + return ObjectUtil.defaultIfNull(status, false); + } +} 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 new file mode 100644 index 0000000000..5a3a6fc483 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/OutGivingLog.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.log; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; + +/** + * 项目分发日志 + * + * @author bwcx_jzy + * @since 2019/7/19 + **/ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "OUTGIVINGLOG", + nameKey = "i18n.distribute_log.c612", parents = OutGivingModel.class, workspaceBind = 3) +@Data +public class OutGivingLog extends BaseWorkspaceModel { + /** + * 分发id + */ + private String outGivingId; + /** + * 状态 + * + * @see OutGivingNodeProject.Status + */ + private Integer status; + /** + * 开始时间 + */ + private Long startTime; + /** + * 结束时间 + */ + private Long endTime; + /** + * 处理消息 + */ + private String result; + /** + * 节点id + */ + private String nodeId; + /** + * 项目id + */ + private String projectId; + + /** + * 文件大小 + */ + private Long fileSize; + + /** + * 进度信息 + */ + 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/log/SshTerminalExecuteLog.java b/modules/server/src/main/java/org/dromara/jpom/model/log/SshTerminalExecuteLog.java new file mode 100644 index 0000000000..dcf65f5a65 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/SshTerminalExecuteLog.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.log; + +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.BaseWorkspaceModel; +import org.dromara.jpom.model.data.SshModel; + +/** + * ssh 终端执行日志 + * + * @author bwcx_jzy + * @since 2021/08/04 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "SSHTERMINALEXECUTELOG", + nameKey = "i18n.ssh_terminal_execution_log.58f1", parents = SshModel.class) +@Data +@NoArgsConstructor +public class SshTerminalExecuteLog extends BaseWorkspaceModel { + /** + * 操作ip + */ + private String ip; + /** + * 用户ip + */ + private String userId; + /** + * sshid + */ + private String sshId; + /** + * 名称 + */ + private String sshName; + /** + * 执行的命令 + */ + private String commands; + /** + * 浏览器标识 + */ + private String userAgent; + + /** + * 是否拒绝执行,true 运行执行,false 拒绝执行 + */ + private Boolean refuse; + + private String machineSshId; + + private String machineSshName; + + public void setUserAgent(String userAgent) { + this.userAgent = StrUtil.maxLength(userAgent, 280); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/log/UserOperateLogV1.java b/modules/server/src/main/java/org/dromara/jpom/model/log/UserOperateLogV1.java new file mode 100644 index 0000000000..f063acc20e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/UserOperateLogV1.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.log; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; + +/** + * 用户操作日志 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "USEROPERATELOGV1", + nameKey = "i18n.user_operation_log.2233", workspaceBind = 2) +@Data +public class UserOperateLogV1 extends BaseWorkspaceModel { + /** + * 操作ip + */ + private String ip; + /** + * 用户ip + */ + private String userId; + /** + * 节点id + */ + private String nodeId; + /** + * 操作时间 + */ + private Long optTime; + /** + * 操作状态,业务状态码 + */ + private Integer optStatus; + /** + * 完整消息 + */ + private String resultMsg; + /** + * 请求参数 + */ + private String reqData; + /** + * 数据id + */ + private String dataId; + /** + * 数据名称 + */ + private String dataName; + /** + * 浏览器标识 + */ + private String userAgent; + + private String classFeature; + private String methodFeature; + /** + * 工作空间名称 + */ + private String workspaceName; + + private String username; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/node/NodeScriptCacheModel.java b/modules/server/src/main/java/org/dromara/jpom/model/node/NodeScriptCacheModel.java new file mode 100644 index 0000000000..d05f2af931 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/node/NodeScriptCacheModel.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseNodeModel; +import org.dromara.jpom.script.CommandParam; + +/** + * 脚本模版实体 + * + * @author bwcx_jzy + * @since 2021/12/12 + **/ +@TableName(value = "SCRIPT_INFO", + nameKey = "i18n.node_script_template_title.4e74") +@Data +@EqualsAndHashCode(callSuper = true) +public class NodeScriptCacheModel extends BaseNodeModel { + /** + * 脚本ID + */ + private String scriptId; + /** + * 模版名称 + */ + private String name; + /** + * 最后执行人员 + */ + private String lastRunUser; + /** + * 定时执行 + */ + private String autoExecCron; + /** + * 默认参数 + */ + private String defArgs; + /** + * 描述 + */ + private String description; + /** + * 脚本类型 + */ + private String scriptType; + /** + * 触发器 token + */ + private String triggerToken; + + @Override + public String dataId() { + return getScriptId(); + } + + @Override + public void dataId(String id) { + setScriptId(id); + } + + public void setDefArgs(String defArgs) { + this.defArgs = CommandParam.convertToParam(defArgs); + } + + @Override + protected boolean hasCreateUser() { + return true; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/node/NodeScriptExecuteLogCacheModel.java b/modules/server/src/main/java/org/dromara/jpom/model/node/NodeScriptExecuteLogCacheModel.java new file mode 100644 index 0000000000..72d08e94a7 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/node/NodeScriptExecuteLogCacheModel.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.node; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseNodeModel; + +/** + * @author bwcx_jzy + * @since 2021/12/12 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "SCRIPT_EXECUTE_LOG", + nameKey = "i18n.node_script_template_execution_record.704a", parents = NodeScriptCacheModel.class) +@Data +public class NodeScriptExecuteLogCacheModel extends BaseNodeModel { + + /** + * + */ + @PropIgnore + private String name; + /** + * 脚本ID + */ + private String scriptId; + /** + * 脚本名称 + */ + private String scriptName; + /** + * 触发类型 {0,手动,1 自动触发} + */ + private Integer triggerExecType; + + @Override + public String fullId() { + throw new IllegalStateException("NO implements"); + } + + public void setName(String name) { + this.name = name; + this.scriptName = name; + } + + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + this.name = scriptName; + } + + @Override + public String dataId() { + return getScriptId(); + } + + @Override + public void dataId(String id) { + setScriptId(id); + } +} 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 new file mode 100644 index 0000000000..eb5e35bd19 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/node/ProjectInfoCacheModel.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseNodeGroupModel; + +/** + * @author bwcx_jzy + * @since 2021/12/5 + */ +@TableName(value = "PROJECT_INFO", + nameKey = "i18n.project_info.6674") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectInfoCacheModel extends BaseNodeGroupModel { + + private String projectId; + /** + * 项目自动启动 + */ + private Boolean autoStart; + + private String name; + + private String mainClass; + private String lib; + /** + * 授权目录 + */ + private String whitelistDirectory; + /** + * 日志目录 + */ + private String logPath; + /** + * jvm 参数 + */ + private String jvm; + /** + * java main 方法参数 + */ + private String args; + /** + * 副本 + */ + private String javaCopyItemList; + /** + * WebHooks + */ + private String token; + + private String runMode; + /** + * 节点分发项目,不允许在项目管理中编辑 + */ + private Boolean outGivingProject; + /** + * -Djava.ext.dirs=lib -cp conf:run.jar + * 填写【lib:conf】 + */ + private String javaExtDirsCp; + /** + * DSL 内容 + */ + private String dslContent; + /** + * 排序 + */ + private Float sortValue; + + /** + * 触发器 token + */ + private String triggerToken; + + @Override + public String dataId() { + return getProjectId(); + } + + @Override + public void dataId(String id) { + setProjectId(id); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/outgiving/BaseNodeProject.java b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/BaseNodeProject.java new file mode 100644 index 0000000000..b3b8bc6de0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/BaseNodeProject.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.outgiving; + +import cn.keepbx.jpom.model.BaseJsonModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2022/5/15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public abstract class BaseNodeProject extends BaseJsonModel { + + private String nodeId; + private String projectId; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/outgiving/LogReadModel.java b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/LogReadModel.java new file mode 100644 index 0000000000..d4ca786084 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/LogReadModel.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.outgiving; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.util.StringUtil; + +import java.util.List; + +/** + * 日志阅读 + * + * @author bwcx_jzy + * @since 2022/5/15 + */ +@TableName(value = "LOG_READ", + nameKey = "i18n.log_reading.a4c8") +@Data +@EqualsAndHashCode(callSuper = true) +public class LogReadModel extends BaseWorkspaceModel { + + /** + * 名称 + */ + private String name; + /** + * 节点下的项目列表 + * + * @see Item + */ + private String nodeProject; + /** + * 缓存操作数据 + */ + private String cacheData; + + /** + * {"op":"showlog","projectId":"python", + * "search":true,"useProjectId":"python", + * "useNodeId":"localhost", + * "beforeCount":0,"afterCount":10, + * "head":0,"tail":100,"first":"false", + * "logFile":"/run.log"} + */ + @Data + public static class CacheDta { + /** + * 日志文件名称 + */ + private String logFile; + /** + * 显示关键词,后多少行 + */ + private Integer afterCount; + /** + * 显示关键词,前多少行 + */ + private Integer beforeCount; + private Integer head; + private Integer tail; + private Boolean first; + /** + * 搜索关键词 + */ + private String keyword; + /** + * 使用等节点ID + */ + private String useProjectId; + private String useNodeId; + } + + + public List nodeProjectList() { + return StringUtil.jsonConvertArray(nodeProject, Item.class); + } + + /** + * 判断是否包含某个项目id + * + * @param projectId 项目id + * @return true 包含 + */ + public boolean checkContains(String nodeId, String projectId) { + return getNodeProject(nodeId, projectId) != null; + } + + /** + * 获取节点的项目信息 + * + * @param nodeId 节点 + * @param projectId 项目 + * @return outGivingNodeProject + */ + public Item getNodeProject(String nodeId, String projectId) { + List thisPs = nodeProjectList(); + if (thisPs == null) { + return null; + } + for (Item outGivingNodeProject1 : thisPs) { + if (StrUtil.equalsIgnoreCase(outGivingNodeProject1.getProjectId(), projectId) && StrUtil.equalsIgnoreCase(outGivingNodeProject1.getNodeId(), nodeId)) { + return outGivingNodeProject1; + } + } + return null; + } + + + public static class Item extends BaseNodeProject { + + } +} 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 new file mode 100644 index 0000000000..7f7852c92f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingModel.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.outgiving; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.BaseGroupModel; +import org.dromara.jpom.model.node.ProjectInfoCacheModel; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.StringUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 分发实体 + * + * @author bwcx_jzy + * @since 2019/4/21 + */ +@TableName(value = "OUT_GIVING", + nameKey = "i18n.node_distribution.ae68") +@Data +@EqualsAndHashCode(callSuper = true) +public class OutGivingModel extends BaseGroupModel { + + /** + * @param group 分组 + * @see ProjectInfoCacheModel#setGroup(String) + */ + @Override + public void setGroup(String group) { + super.setGroup(group); + } + + /** + * 名称 + */ + private String name; + /** + * 分发间隔时间 + */ + private Integer intervalTime; + /** + * 节点下的项目列表 + */ + private String outGivingNodeProjectList; + /** + * 分发后的操作 + */ + private Integer afterOpt; + /** + * 是否清空旧包发布 + */ + private Boolean clearOld; + /** + * 是否为单独创建的分发项目 + */ + private Boolean outGivingProject; + + /** + * 状态 + */ + private Integer status; + + /** + * 二级目录 + */ + private String secondaryDirectory; + /** + * 保存项目文件前先关闭 + */ + private Boolean uploadCloseFirst; + /** + * 状态消息 + */ + private String statusMsg; + + /** + * 构建发布状态通知 + */ + private String webhook; + /** + * 分发方式 + * upload: "手动上传", + * download: "远程下载", + * "build-trigger": "构建触发", + * "use-build": "构建产物", + * "file-storage":"文件中心" + * "static-file-storage“ 静态文件 + */ + private String mode; + private String modeData; + + public boolean clearOld() { + return clearOld != null && clearOld; + } + + public boolean outGivingProject() { + return outGivingProject != null && outGivingProject; + } + + public void setSecondaryDirectory(String secondaryDirectory) { + this.secondaryDirectory = Opt.ofBlankAble(secondaryDirectory).map(s -> { + FileUtils.checkSlip(s, e -> new IllegalArgumentException(I18nMessageUtil.get("i18n.second_level_directory_cannot_skip_levels.c9fb") + e.getMessage())); + return s; + }).orElse(StrUtil.EMPTY); + } + + public List outGivingNodeProjectList() { + return outGivingNodeProjectList(StrUtil.EMPTY); + } + + public List outGivingNodeProjectList(String select) { + List outGivingNodeProjects = StringUtil.jsonConvertArray(outGivingNodeProjectList, OutGivingNodeProject.class); + if (outGivingNodeProjects != null) { + // 排序 + for (int i = 0; i < outGivingNodeProjects.size(); i++) { + OutGivingNodeProject outGivingNodeProject = outGivingNodeProjects.get(i); + if (outGivingNodeProject.getSortValue() != null) { + outGivingNodeProject.setSortValue(ObjectUtil.defaultIfNull(outGivingNodeProject.getSortValue(), i)); + } + } + List list = StrUtil.splitTrim(select, StrUtil.COMMA); + outGivingNodeProjects = outGivingNodeProjects.stream() + .filter(nodeProject -> { + if (CollUtil.isEmpty(list)) { + return true; + } + return list.stream() + .anyMatch(s -> StrUtil.equals(s, StrUtil.format("{}@{}", nodeProject.getProjectId(), nodeProject.getNodeId()) + )); + }) + .sorted((o1, o2) -> CompareUtil.compare(o1.getSortValue(), o2.getSortValue())) + .collect(Collectors.toList()); + } + return outGivingNodeProjects; + } + + public void outGivingNodeProjectList(List outGivingNodeProjectList) { + if (outGivingNodeProjectList == null) { + this.outGivingNodeProjectList = null; + } else { + this.outGivingNodeProjectList = JSON.toJSONString(outGivingNodeProjectList); + } + } + + /** + * 判断是否包含某个项目id + * + * @param projectId 项目id + * @return true 包含 + */ + public boolean checkContains(String nodeId, String projectId) { + return getNodeProject(nodeId, projectId) != null; + } + + /** + * 获取节点的项目信息 + * + * @param nodeId 节点 + * @param projectId 项目 + * @return outGivingNodeProject + */ + public OutGivingNodeProject getNodeProject(String nodeId, String projectId) { + List thisPs = outGivingNodeProjectList(); + return getNodeProject(thisPs, nodeId, projectId); + } + + /** + * 从指定数组中获取对应信息 + * + * @param outGivingNodeProjects 节点项目列表 + * @param nodeId 节点id + * @param projectId 项目id + * @return 实体 + */ + public static OutGivingNodeProject getNodeProject(List outGivingNodeProjects, String nodeId, String projectId) { + if (outGivingNodeProjects == null) { + return null; + } + for (OutGivingNodeProject outGivingNodeProject1 : outGivingNodeProjects) { + if (StrUtil.equalsIgnoreCase(outGivingNodeProject1.getProjectId(), projectId) && StrUtil.equalsIgnoreCase(outGivingNodeProject1.getNodeId(), nodeId)) { + return outGivingNodeProject1; + } + } + return null; + } + + /** + * 获取已经删除的节点项目 + * + * @param newsProject 要比较的分发项目 + * @return 已经删除过的 + */ + public List getDelete(List newsProject) { + List old = outGivingNodeProjectList(); + if (old == null || old.isEmpty()) { + return null; + } + List delete = new ArrayList<>(); + old.forEach(outGivingNodeProject -> { + if (getNodeProject(newsProject, outGivingNodeProject.getNodeId(), outGivingNodeProject.getProjectId()) != null) { + return; + } + delete.add(outGivingNodeProject); + }); + return delete; + } + + /** + * 状态 + */ + @Getter + public enum Status implements BaseEnum { + /** + * + */ + NO(0, "未分发"), + ING(1, "分发中"), + DONE(2, "分发结束"), + CANCEL(3, "已取消"), + FAIL(4, "分发失败"), + ; + private final int code; + private final String desc; + + Status(int code, String desc) { + this.code = code; + this.desc = desc; + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingNodeProject.java b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingNodeProject.java new file mode 100644 index 0000000000..e7ebc5c1fc --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingNodeProject.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.outgiving; + +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.dromara.jpom.model.BaseEnum; + +/** + * 节点项目 + * + * @author bwcx_jzy + * @since 2019/4/22 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OutGivingNodeProject extends BaseNodeProject { + + /** + * 排序值 + */ + private Integer sortValue; + + /** + * 是否禁用 + */ + private Boolean disabled; + + public Boolean getDisabled() { + return ObjectUtil.defaultIfNull(disabled, false); + } + + /** + * 状态 + */ + @Getter + public enum Status implements BaseEnum { + /** + * + */ + No(0, "未分发"), + Ing(1, "分发中"), + Ok(2, "分发成功"), + Fail(3, "分发失败"), + Cancel(4, "系统取消分发"), + Prepare(5, "准备分发"), + ArtificialCancel(6, "手动取消分发"), + ; + private final int code; + private final String desc; + + Status(int code, String desc) { + this.code = code; + this.desc = desc; + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptExecuteLogModel.java b/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptExecuteLogModel.java new file mode 100644 index 0000000000..f2da32fb1e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptExecuteLogModel.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.script; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.CommandExecLogModel; + +/** + * @author bwcx_jzy + * @since 2022/1/19 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "SERVER_SCRIPT_EXECUTE_LOG", + nameKey = "i18n.script_template_execution_record.374b", parents = ScriptModel.class) +@Data +public class ScriptExecuteLogModel extends BaseWorkspaceModel { + + /** + * 脚本ID + */ + private String scriptId; + /** + * 脚本名称 + */ + private String scriptName; + /** + * 触发类型 {0,手动,1 自动触发,2 触发器,3 构建事件} + */ + private Integer triggerExecType; + + /** + * 退出码 + */ + private Integer exitCode; + + /** + * @see CommandExecLogModel.Status + */ + private Integer status; +} 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 new file mode 100644 index 0000000000..f82e0472e6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptModel.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.script; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.script.CommandParam; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2022/1/19 + */ +@TableName(value = "SERVER_SCRIPT_INFO", + nameKey = "i18n.script_template.1f77") +@Data +@EqualsAndHashCode(callSuper = true) +public class ScriptModel extends BaseWorkspaceModel { + /** + * 模版名称 + */ + private String name; + /** + * 最后执行人员 + */ + private String lastRunUser; + /** + * 定时执行 + */ + private String autoExecCron; + /** + * 默认参数 + */ + private String defArgs; + /** + * 描述 + */ + private String description; + + private String context; + /** + * 节点ID + */ + private String nodeIds; + /** + * 触发器 token + */ + private String triggerToken; + + public void setDefArgs(String defArgs) { + this.defArgs = CommandParam.convertToParam(defArgs); + } + + public File scriptPath() { + return scriptPath(getId()); + } + + public static File scriptPath(String id) { + if (StrUtil.isEmpty(id)) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.id_is_empty.3bbf")); + } + File path = JpomApplication.getInstance().getScriptPath(); + return FileUtil.file(path, id); + } + + public File logFile(String executeId) { + //File path = this.scriptPath(); + //return FileUtil.file(path, "log", executeId + ".log"); + return logFile(getId(), executeId); + } + + public static File logFile(String id, String executeId) { + File path = scriptPath(id); + return FileUtil.file(path, "log", executeId + ".log"); + } + + @Override + protected boolean hasCreateUser() { + return true; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/user/TriggerTokenLogBean.java b/modules/server/src/main/java/org/dromara/jpom/model/user/TriggerTokenLogBean.java new file mode 100644 index 0000000000..5d98ef615f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/user/TriggerTokenLogBean.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.user; + +import cn.hutool.core.annotation.PropIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseDbModel; +import org.dromara.jpom.service.ITriggerToken; + +/** + * id 为 triggerToken + * + * @author bwcx_jzy + * @since 2022/7/22 + */ +@TableName(value = "TRIGGER_TOKEN_LOG", + nameKey = "i18n.trigger_token.abe6") +@Data +@EqualsAndHashCode(callSuper = true) +public class TriggerTokenLogBean extends BaseDbModel { + /** + * 为了兼容旧数据(因为旧数据字段长度大于 50 ) + *

+ * 198fc5b944a3978b839506eb9d534c6f2b200dcda4e1d378fe30d2e8dbd7335bf4a + */ + private String triggerToken; + /** + * 类型 + * + * @see ITriggerToken#typeName() + */ + private String type; + + /** + * 关联数据ID + */ + private String dataId; + /** + * 关联数据名称 + */ + @PropIgnore + private String dataName; + + /** + * 用户ID + * + * @see UserModel#getId() + */ + private String userId; + /** + * 触发次数 + */ + private Integer triggerCount; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/user/UserBindWorkspaceModel.java b/modules/server/src/main/java/org/dromara/jpom/model/user/UserBindWorkspaceModel.java new file mode 100644 index 0000000000..cdeafe13f1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/user/UserBindWorkspaceModel.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.user; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseDbModel; + +/** + * @author bwcx_jzy + * @since 2021/12/4 + */ +@TableName(value = "USER_BIND_WORKSPACE", + nameKey = "i18n.user_workspace_relation_table.851e") +@Data +@EqualsAndHashCode(callSuper = true) +public class UserBindWorkspaceModel extends BaseDbModel { + + /** + * 权限组ID + * + * @see UserPermissionGroupBean#getId() + * 兼容旧数据 + * @see UserModel#getId() + */ + private String userId; + + private String workspaceId; + + /** + * 生产绑定关系表 主键 ID + * + * @param userId 用户ID + * @param workspaceId 工作空间ID + * @return id + */ + public static String getId(String userId, String workspaceId) { + return SecureUtil.sha1(userId + workspaceId); + } + + @Builder + public static class PermissionResult { + /** + * 结果 + */ + private PermissionResultEnum state; + /** + * 不能执行的原因 + */ + private String msg; + + public boolean isSuccess() { + return state == PermissionResultEnum.SUCCESS; + } + + public String errorMsg(String... pars) { + String errorMsg = StrUtil.emptyToDefault(msg, I18nMessageUtil.get("i18n.no_permission.e343")); + return StrUtil.format("{} {}", ArrayUtil.join(pars, StrUtil.SPACE), errorMsg); + } + } + + public enum PermissionResultEnum { + /** + * 允许执行 + */ + SUCCESS, + /** + * 没有权限 + */ + FAIL, + /** + * 当前禁止执行 + */ + MISS_PROHIBIT, + /** + * 不在计划允许时间段 + */ + MISS_PERIOD, + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/user/UserModel.java b/modules/server/src/main/java/org/dromara/jpom/model/user/UserModel.java new file mode 100644 index 0000000000..26683b3c6f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/user/UserModel.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.user; + +import cn.hutool.core.date.DateUtil; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * 用户实体 + * + * @author bwcx_jzy + * @since 2019/1/16 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "USER_INFO", + nameKey = "i18n.user_account.cbf7") +@Data +@NoArgsConstructor +public class UserModel extends BaseUserModifyDbModel { + /** + * 系统管理员 + */ + public final static String SYSTEM_ADMIN = "sys"; + /** + * demo 演示账号、系统预设 + */ + public final static String DEMO_USER = "demo"; + /** + * + */ + public static final UserModel EMPTY = new UserModel(UserModel.SYSTEM_ADMIN); + /** + * 系统占用名 + */ + public static final Supplier SYSTEM_OCCUPY_NAME = () -> I18nMessageUtil.get("i18n.system_administrator.181f"); + /** + * 用户名限制 + */ + public static final int USER_NAME_MIN_LEN = 3; + /** + * 盐值长度 + */ + public static int SALT_LEN = 8; + /** + * 昵称 + */ + private String name; + /** + * 密码 + */ + private String password; + /** + * 密码盐值 + */ + private String salt; + /** + * 两步验证 key + */ + private String twoFactorAuthKey; + /** + * 创建此用户的人 + */ + private String parent; + /** + * 系统管理员 + */ + private Integer systemUser; + /** + * 连续登录失败次数 + */ + private Integer pwdErrorCount; + /** + * 最后失败时间 + */ + private Long lastPwdErrorTime; + /** + * 账号被锁定的时长 + */ + private Long lockTime; + /** + * 邮箱 + */ + private String email; + /** + * 钉钉 + */ + private String dingDing; + /** + * 企业微信 + */ + private String workWx; + /** + * 状态 0 禁用 null、1 启用 + */ + private Integer status; + + /** + * 权限组 + */ + private String permissionGroup; + /** + * 账号来源 + */ + private String source; + + public UserModel(String id) { + this.setId(id); + } + + public void setSystemUser(Integer systemUser) { + this.systemUser = ObjectUtil.defaultIfNull(systemUser, 0) == 1 ? systemUser : 0; + } + + /** + * 解锁 + */ + public static UserModel unLock(String id) { + UserModel newModel = new UserModel(id); + newModel.setPwdErrorCount(0); + newModel.setLockTime(0L); + newModel.setLastPwdErrorTime(0L); + return newModel; + } + + /** + * 剩余解锁时间 + * + * @return 0是未锁定 + */ + public long overLockTime(int alwaysLoginError) { + if (alwaysLoginError <= 0) { + return 0; + } + // 不限制演示账号的登录 + if (isDemoUser()) { + return 0; + } + // 最后一次失败时间 + Long lastTime = getLastPwdErrorTime(); + if (lastTime == null || lastTime <= 0) { + return 0; + } + // 当前锁定时间 + Long lockTime = getLockTime(); + if (lockTime == null || lockTime <= 0) { + return 0; + } + // 解锁时间 + lastTime += lockTime; + long nowTime = DateUtil.currentSeconds(); + // 剩余解锁时间 + lastTime -= nowTime; + if (lastTime > 0) { + return lastTime; + } + return 0; + } + + /** + * 登录失败,重新计算锁定时间 + * + * @param alwaysLoginError 运行登录失败次数 + * @return 返回的信息需要更新到数据库 + */ + public UserModel errorLock(int alwaysLoginError) { + // 未开启锁定功能 + if (alwaysLoginError <= 0) { + return null; + } + UserModel newModel = new UserModel(this.getId()); + newModel.setPwdErrorCount(ObjectUtil.defaultIfNull(this.getPwdErrorCount(), 0) + 1); + int count = newModel.getPwdErrorCount(); + // 记录错误时间 + newModel.setLastPwdErrorTime(DateUtil.currentSeconds()); + if (count < alwaysLoginError) { + // 还未达到锁定条件 + return newModel; + } + int level = count / alwaysLoginError; + switch (level) { + case 1: + // 在错误倍数 为1 锁定 30分钟 + newModel.setLockTime(TimeUnit.MINUTES.toSeconds(30)); + break; + case 2: + // 在错误倍数 为2 锁定 1小时 + newModel.setLockTime(TimeUnit.HOURS.toSeconds(1)); + break; + default: + // 其他情况 10小时 + newModel.setLockTime(TimeUnit.HOURS.toSeconds(10)); + break; + } + return newModel; + } + + public boolean isSystemUser() { + return systemUser != null && systemUser == 1; + } + + /** + * 是否为超级管理员 + * + * @return true 是 + */ + public boolean isSuperSystemUser() { + return StrUtil.equals(getParent(), SYSTEM_ADMIN); + } + + /** + * demo 登录名默认为系统演示账号 + * + * @return true + */ + public boolean isDemoUser() { + // demo 账号 和他创建的账号都是 demo + return isRealDemoUser() || UserModel.DEMO_USER.equals(getParent()); + } + + /** + * demo 登录名默认为系统演示账号 + * + * @return true + */ + public boolean isRealDemoUser() { + return UserModel.DEMO_USER.equals(getId()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/user/UserPermissionGroupBean.java b/modules/server/src/main/java/org/dromara/jpom/model/user/UserPermissionGroupBean.java new file mode 100644 index 0000000000..41431a19bf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/model/user/UserPermissionGroupBean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model.user; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; + +/** + * @author bwcx_jzy + * @since 2022/8/3 + */ +@TableName(value = "USER_PERMISSION_GROUP", + nameKey = "i18n.user_permission_group.52a4") +@Data +@EqualsAndHashCode(callSuper = true) +public class UserPermissionGroupBean extends BaseUserModifyDbModel { + + /** + * 名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 禁止执行时间段,优先判断禁止执行 + *

+     * [{
+     *     "startTime": 1,
+     *     "endTime": 1,
+     *     "reason": ""
+     * }]
+     * 
+ */ + private String prohibitExecute; + + /** + * 允许执行的时间段 + *
+     * [{
+     *     "week": [1,2],
+     *     "startTime":"08:00:00"
+     *     "endTime": "18:00:00"
+     * }]
+     * 
+ */ + private String allowExecute; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/monitor/EmailUtil.java b/modules/server/src/main/java/org/dromara/jpom/monitor/EmailUtil.java new file mode 100644 index 0000000000..722a1ed650 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/monitor/EmailUtil.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.monitor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.MailAccountModel; +import org.dromara.jpom.model.data.MonitorModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.system.SystemParametersServer; + +import java.util.HashMap; +import java.util.Map; + +/** + * 邮件工具 + * + * @author Arno + */ +@Slf4j +public class EmailUtil implements INotify { + + private static SystemParametersServer systemParametersServer; + private static MailAccountModel config; + + @Override + public void send(MonitorModel.Notify notify, String title, String context) throws Exception { + String value = notify.getValue(); + EmailUtil.send(value, title, context); + } + + private static void init() { + if (systemParametersServer == null) { + systemParametersServer = SpringUtil.getBean(SystemParametersServer.class); + } + } + + /** + * 加载配置信息 + */ + public static void refreshConfig() { + if (config == null) { + init(); + } + config = systemParametersServer.getConfig(MailAccountModel.ID, MailAccountModel.class); + } + + + /** + * 发送邮箱 + * + * @param email 收件人 + * @param title 标题 + * @param context 内容 + */ + public static void send(String email, String title, String context) throws Exception { + if (config == null) { + // 没有数据才加载 + refreshConfig(); + } + if (config == null || StrUtil.isEmpty(config.getHost())) { + log.error(I18nMessageUtil.get("i18n.email_service_not_configured.3180"), email, title); + return; + } + // + Map mailMap = new HashMap<>(10); + mailMap.put("toEmail", email); + mailMap.put("title", title); + mailMap.put("context", context); + // + IPlugin plugin = PluginFactory.getPlugin("email"); + plugin.execute(JSON.toJSON(config), mailMap); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/monitor/INotify.java b/modules/server/src/main/java/org/dromara/jpom/monitor/INotify.java new file mode 100644 index 0000000000..61a6bcf0b8 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/monitor/INotify.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.monitor; + +import org.dromara.jpom.model.data.MonitorModel; + +/** + * 通知接口 + * + * @author bwcx_jzy + * @since 2019/7/13 + */ +public interface INotify { + + /** + * 发送通知 + * + * @param notify 通知方式 + * @param title 标题 + * @param context 内容 + */ + void send(MonitorModel.Notify notify, String title, String context) throws Exception; +} 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 new file mode 100644 index 0000000000..df8fcb4184 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/monitor/MonitorItem.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.monitor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.db.sql.Direction; +import cn.hutool.db.sql.Order; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.MonitorModel; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.log.MonitorNotifyLog; +import org.dromara.jpom.model.node.ProjectInfoCacheModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.dblog.DbMonitorNotifyLogService; +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; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 监控执行器 + * + * @author bwcx_jzy + * @since 2021/12/14 + */ +@Slf4j +public class MonitorItem implements Task { + + + private final DbMonitorNotifyLogService dbMonitorNotifyLogService; + private final UserService userService; + private final MonitorService monitorService; + private final ProjectInfoCacheService projectInfoCacheService; + private final NodeService nodeService; + private final String monitorId; + private MonitorModel monitorModel; + + public MonitorItem(String id) { + this.dbMonitorNotifyLogService = SpringUtil.getBean(DbMonitorNotifyLogService.class); + this.userService = SpringUtil.getBean(UserService.class); + this.monitorService = SpringUtil.getBean(MonitorService.class); + this.nodeService = SpringUtil.getBean(NodeService.class); + this.projectInfoCacheService = SpringUtil.getBean(ProjectInfoCacheService.class); + this.monitorId = id; + } + + @Override + public void execute() { + // 重新查询 + this.monitorModel = monitorService.getByKey(monitorId); + List nodeProjects = monitorModel.projects(); + // + List collect = nodeProjects.stream().map(nodeProject -> { + String nodeId = nodeProject.getNode(); + NodeModel nodeModel = nodeService.getByKey(nodeId); + if (nodeModel == null) { + return true; + } + return this.reqNodeStatus(nodeModel, nodeProject.getProjects()); + }).filter(aBoolean -> !aBoolean).collect(Collectors.toList()); + boolean allRun = CollUtil.isEmpty(collect); + // 报警状态 + monitorService.setAlarm(monitorModel.getId(), !allRun); + } + + /** + * 检查节点节点对信息 + * + * @param nodeModel 节点 + * @param projects 项目 + * @return true 所有项目都正常 + */ + private boolean reqNodeStatus(NodeModel nodeModel, List projects) { + if (projects == null || projects.isEmpty()) { + return true; + } + List collect = projects.stream().map(id -> { + // + String title; + String context; + try { + //查询项目运行状态 + JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.Manage_GetProjectStatus, "id", id); + if (jsonMessage.success()) { + JSONObject jsonObject = jsonMessage.getData(); + int pid = jsonObject.getIntValue("pId"); + String statusMsg = jsonObject.getString("statusMsg"); + boolean runStatus = this.checkNotify(monitorModel, nodeModel, id, pid > 0, statusMsg); + // 检查副本 + List booleanList = null; + JSONArray copys = jsonObject.getJSONArray("copys"); + if (CollUtil.isNotEmpty(copys)) { + booleanList = copys.stream() + .map(o -> { + JSONObject jsonObject1 = (JSONObject) o; + + boolean status = jsonObject1.getBooleanValue("status"); + return MonitorItem.this.checkNotify(monitorModel, nodeModel, id, status, StrUtil.EMPTY); + }) + .filter(aBoolean -> !aBoolean) + .collect(Collectors.toList()); + } + return runStatus && CollUtil.isEmpty(booleanList); + } else { + title = StrUtil.format(I18nMessageUtil.get("i18n.node_status_code_abnormal.4d22"), nodeModel.getName(), jsonMessage.getCode()); + context = jsonMessage.toString(); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.monitor_node_exception.6ff1"), nodeModel.getName(), e.getMessage()); + // + title = StrUtil.format(I18nMessageUtil.get("i18n.node_running_status_abnormal.3160"), nodeModel.getName()); + context = ExceptionUtil.stacktraceToString(e); + } + // 获取上次状态 + boolean pre = this.getPreStatus(monitorModel.getId(), nodeModel.getId(), id); + if (pre) { + // 上次正常 + MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); + monitorNotifyLog.setStatus(false); + monitorNotifyLog.setTitle(title); + monitorNotifyLog.setContent(context); + monitorNotifyLog.setCreateTime(System.currentTimeMillis()); + monitorNotifyLog.setNodeId(nodeModel.getId()); + monitorNotifyLog.setProjectId(id); + monitorNotifyLog.setMonitorId(monitorModel.getId()); + // + this.notifyMsg(nodeModel, monitorNotifyLog); + } + return false; + }).filter(aBoolean -> !aBoolean).collect(Collectors.toList()); + return CollUtil.isEmpty(collect); + } + + /** + * 检查状态 + * + * @param monitorModel 监控信息 + * @param nodeModel 节点信息 + * @param id 项目id + * @param runStatus 当前运行状态 + */ + private boolean checkNotify(MonitorModel monitorModel, NodeModel nodeModel, String id, boolean runStatus, String statusMsg) { + // 获取上次状态 + String copyMsg = StrUtil.EMPTY; + boolean pre = this.getPreStatus(monitorModel.getId(), nodeModel.getId(), id); + String title = null; + String context = null; + //查询项目运行状态 + if (runStatus) { + if (!pre) { + // 上次是异常状态 + title = StrUtil.format(I18nMessageUtil.get("i18n.node_service_resumed_normal_operation.2cbd"), nodeModel.getName(), id, copyMsg); + context = ""; + } + } else { + // + if (monitorModel.autoRestart()) { + // 执行重启 + try { + JsonMessage reJson = NodeForward.request(nodeModel, NodeUrl.Manage_Operate, "id", id, "opt", "restart"); + if (reJson.success()) { + // 重启成功 + runStatus = true; + title = StrUtil.format(I18nMessageUtil.get("i18n.node_service_stopped_successful_restart.603b"), nodeModel.getName(), id, copyMsg); + } else { + title = StrUtil.format(I18nMessageUtil.get("i18n.node_service_stopped_failed_restart.4307"), nodeModel.getName(), id, copyMsg); + } + context = I18nMessageUtil.get("i18n.restart_result.253f") + reJson; + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.restart_operation.5e3a"), e); + title = StrUtil.format(I18nMessageUtil.get("i18n.node_service_stopped_abnormal_restart.a5c0"), nodeModel.getName(), id, copyMsg); + context = ExceptionUtil.stacktraceToString(e); + } + } else { + title = StrUtil.format(I18nMessageUtil.get("i18n.node_service_not_running.ad89"), nodeModel.getName(), id, copyMsg); + context = I18nMessageUtil.get("i18n.please_check_in_time.3b4f"); + } + } + if (!pre && !runStatus) { + // 上一次是异常,并且当前还是异常 + return false; + } + MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); + monitorNotifyLog.setStatus(runStatus); + monitorNotifyLog.setTitle(title); + monitorNotifyLog.setContent(StrUtil.format(I18nMessageUtil.get("i18n.alert_content_and_status.6ed1"), context, statusMsg)); + monitorNotifyLog.setCreateTime(System.currentTimeMillis()); + monitorNotifyLog.setNodeId(nodeModel.getId()); + monitorNotifyLog.setProjectId(id); + monitorNotifyLog.setMonitorId(monitorModel.getId()); + // + this.notifyMsg(nodeModel, monitorNotifyLog); + return runStatus; + } + + /** + * 获取上次是否也为异常状态 + * + * @param monitorId 监控id + * @param nodeId 节点id + * @param projectId 项目id + * @return true 为正常状态,false 异常状态 + */ + private boolean getPreStatus(String monitorId, String nodeId, String projectId) { + // 检查是否已经触发通知 + + MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); + monitorNotifyLog.setNodeId(nodeId); + monitorNotifyLog.setProjectId(projectId); + monitorNotifyLog.setMonitorId(monitorId); + + List queryList = dbMonitorNotifyLogService.queryList(monitorNotifyLog, 1, new Order("createTime", Direction.DESC)); + MonitorNotifyLog entity1 = CollUtil.getFirst(queryList); + return entity1 == null || entity1.status(); + } + + private void notifyMsg(NodeModel nodeModel, MonitorNotifyLog monitorNotifyLog) { + List notify = monitorModel.notifyUser(); + // 发送通知 + if (monitorNotifyLog.getTitle() == null) { + return; + } + ProjectInfoCacheModel projectInfoCacheModel = projectInfoCacheService.getData(nodeModel.getId(), monitorNotifyLog.getProjectId()); + monitorNotifyLog.setWorkspaceId(projectInfoCacheModel.getWorkspaceId()); + // + notify.forEach(notifyUser -> this.sendNotifyMsgToUser(monitorNotifyLog, notifyUser)); + // + this.sendNotifyMsgToWebhook(monitorNotifyLog, nodeModel, projectInfoCacheModel, monitorModel.getWebhook()); + } + + private void sendNotifyMsgToWebhook(MonitorNotifyLog monitorNotifyLog, NodeModel nodeModel, ProjectInfoCacheModel projectInfoCacheModel, String webhook) { + if (StrUtil.isEmpty(webhook)) { + return; + } + 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()); + map.put("nodeName", nodeModel.getName()); + map.put("runStatus", monitorNotifyLog.getStatus()); + map.put("projectId", monitorNotifyLog.getProjectId()); + if (projectInfoCacheModel != null) { + map.put("projectName", projectInfoCacheModel.getName()); + } + map.put("title", monitorNotifyLog.getTitle()); + map.put("content", monitorNotifyLog.getContent()); + // + monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); + monitorNotifyLog.setNotifyStyle(MonitorModel.NotifyType.webhook.getCode()); + monitorNotifyLog.setNotifyObject(webhook); + // + dbMonitorNotifyLogService.insert(monitorNotifyLog); + String logId = monitorNotifyLog.getId(); + ThreadUtil.execute(() -> { + try { + plugin.execute(webhook, map); + dbMonitorNotifyLogService.updateStatus(logId, true, null); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.webhooks_invocation_error.9792"), e); + dbMonitorNotifyLogService.updateStatus(logId, false, ExceptionUtil.stacktraceToString(e)); + } + }); + } + + private void sendNotifyMsgToUser(MonitorNotifyLog monitorNotifyLog, String notifyUser) { + UserModel item = userService.getByKey(notifyUser); + boolean success = false; + if (item != null) { + // 邮箱 + String email = item.getEmail(); + if (StrUtil.isNotEmpty(email)) { + monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); + MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.mail, email); + monitorNotifyLog.setNotifyStyle(notify1.getStyle()); + monitorNotifyLog.setNotifyObject(notify1.getValue()); + // + dbMonitorNotifyLogService.insert(monitorNotifyLog); + this.send(notify1, monitorNotifyLog.getId(), monitorNotifyLog.getTitle(), monitorNotifyLog.getContent()); + success = true; + } + // dingding + String dingDing = item.getDingDing(); + if (StrUtil.isNotEmpty(dingDing)) { + monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); + MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.dingding, dingDing); + monitorNotifyLog.setNotifyStyle(notify1.getStyle()); + monitorNotifyLog.setNotifyObject(notify1.getValue()); + // + dbMonitorNotifyLogService.insert(monitorNotifyLog); + this.send(notify1, monitorNotifyLog.getId(), monitorNotifyLog.getTitle(), monitorNotifyLog.getContent()); + success = true; + } + // 企业微信 + String workWx = item.getWorkWx(); + if (StrUtil.isNotEmpty(workWx)) { + monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); + MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.workWx, workWx); + monitorNotifyLog.setNotifyStyle(notify1.getStyle()); + monitorNotifyLog.setNotifyObject(notify1.getValue()); + // + dbMonitorNotifyLogService.insert(monitorNotifyLog); + this.send(notify1, monitorNotifyLog.getId(), monitorNotifyLog.getTitle(), monitorNotifyLog.getContent()); + success = true; + } + } + if (success) { + return; + } + monitorNotifyLog.setId(IdUtil.fastSimpleUUID()); + monitorNotifyLog.setNotifyObject(I18nMessageUtil.get("i18n.alert_contact_exception.2cec")); + monitorNotifyLog.setNotifyStyle(MonitorModel.NotifyType.mail.getCode()); + monitorNotifyLog.setNotifyStatus(false); + String userNotFound = I18nMessageUtil.get("i18n.contact_does_not_exist.3369"); + String notifyError = I18nMessageUtil.get("i18n.alert_contact_exception_message.1072") + (item == null ? userNotFound : ""); + monitorNotifyLog.setNotifyError(notifyError); + dbMonitorNotifyLogService.insert(monitorNotifyLog); + } + + private void send(MonitorModel.Notify notify, String logId, String title, String context) { + // 异常发送 + ThreadUtil.execute(() -> { + try { + NotifyUtil.send(notify, title, context); + dbMonitorNotifyLogService.updateStatus(logId, true, null); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_alert_notification_exception.6788"), e); + dbMonitorNotifyLogService.updateStatus(logId, false, ExceptionUtil.stacktraceToString(e)); + } + }); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/monitor/NotifyUtil.java b/modules/server/src/main/java/org/dromara/jpom/monitor/NotifyUtil.java new file mode 100644 index 0000000000..c0b79f197a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/monitor/NotifyUtil.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.monitor; + +import cn.hutool.core.map.SafeConcurrentHashMap; +import org.dromara.jpom.model.BaseEnum; +import org.dromara.jpom.model.data.MonitorModel; + +import java.util.Map; +import java.util.Objects; + +/** + * 通知util + * + * @author bwcx_jzy + * @since 2019/7/13 + */ +public class NotifyUtil { + + private static final Map NOTIFY_MAP = new SafeConcurrentHashMap<>(); + + static { + NOTIFY_MAP.put(MonitorModel.NotifyType.dingding, new WebHookUtil()); + NOTIFY_MAP.put(MonitorModel.NotifyType.mail, new EmailUtil()); + NOTIFY_MAP.put(MonitorModel.NotifyType.workWx, new WebHookUtil()); + } + + /** + * 发送报警消息 + * + * @param notify 通知方式 + * @param title 描述 + * @param context 内容 + */ + public static void send(MonitorModel.Notify notify, String title, String context) throws Exception { + int style = notify.getStyle(); + MonitorModel.NotifyType notifyType = BaseEnum.getEnum(MonitorModel.NotifyType.class, style); + Objects.requireNonNull(notifyType); + // + INotify iNotify = NOTIFY_MAP.get(notifyType); + Objects.requireNonNull(iNotify); + iNotify.send(notify, title, context); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/monitor/WebHookUtil.java b/modules/server/src/main/java/org/dromara/jpom/monitor/WebHookUtil.java new file mode 100644 index 0000000000..0e6ae29968 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/monitor/WebHookUtil.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.monitor; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.model.data.MonitorModel; +import org.springframework.http.MediaType; + +/** + * 钉钉工具 + * + * @author Arno + */ +public class WebHookUtil implements INotify { + + /** + * 发送钉钉群自定义机器人消息 + * + * @param notify 通知对象 + * @param title 描述标签 + * @param context 消息内容 + */ + @Override + public void send(MonitorModel.Notify notify, String title, String context) { + JSONObject text = new JSONObject(); + JSONObject param = new JSONObject(); + //消息内容 + text.put("content", title + "\n" + context); + param.put("msgtype", "text"); + param.put("text", text); + HttpRequest request = HttpUtil. + createPost(notify.getValue()). + contentType(MediaType.APPLICATION_JSON_VALUE). + body(param.toJSONString()); + request.execute(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/AuthOauth2MaxKeyRequest.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/AuthOauth2MaxKeyRequest.java new file mode 100644 index 0000000000..1a00245620 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/AuthOauth2MaxKeyRequest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2; + +import com.alibaba.fastjson2.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthDefaultRequest; + +/** + * @author MaxKey + */ +public class AuthOauth2MaxKeyRequest extends AuthDefaultRequest { + + public AuthOauth2MaxKeyRequest(AuthConfig config, AuthSource source) { + super(config, source); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + String body = doPostAuthorizationCode(authCallback.getCode()); + JSONObject object = JSONObject.parseObject(body); + return AuthToken.builder() + .accessToken(object.getString("access_token")) + .refreshToken(object.getString("refresh_token")) + .idToken(object.getString("id_token")) + .tokenType(object.getString("token_type")) + .scope(object.getString("scope")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String body = doGetUserInfo(authToken); + JSONObject object = JSONObject.parseObject(body); + return AuthUser.builder() + .uuid(object.getString("id")) + .username(object.getString("username")) + .nickname(object.getString("name")) + .company(object.getString("organization")) + .email(object.getString("email")) + .token(authToken) + .source("jpom") + .build(); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/BaseOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/BaseOauth2Config.java new file mode 100644 index 0000000000..2b06a8a57a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/BaseOauth2Config.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2; + +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; +import lombok.Data; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +/** + * @author bwcx_jzy + * @since 2023/3/30 + */ +@Data +public abstract class BaseOauth2Config { + + public static final Map DB_KEYS = new HashMap<>(); + + static { + Set> classes = ClassUtil.scanPackageBySuper(BaseOauth2Config.class.getPackage().getName(), BaseOauth2Config.class); + for (Class aClass : classes) { + Field field = ReflectUtil.getField(aClass, "KEY"); + Assert.notNull(field, I18nMessageUtil.get("i18n.key_field_not_configured.7b22") + aClass.getName()); + String staticFieldValue = (String) ReflectUtil.getStaticFieldValue(field); + BaseOauth2Config baseOauth2Config = (BaseOauth2Config) ReflectUtil.newInstanceIfPossible(aClass); + DB_KEYS.put(baseOauth2Config.provide(), new Tuple(staticFieldValue, aClass)); + } + } + + /** + * 数据库存储的 key + * + * @param provide 平台名 + * @return 配置对象 + */ + public static Tuple getDbKey(String provide) { + return DB_KEYS.get(provide); + } + + protected Boolean enabled; + protected String clientId; + protected String clientSecret; + protected String redirectUri; + /** + * 是否自动创建用户 + */ + protected Boolean autoCreteUser; + protected Boolean ignoreCheckState; + /** + * 创建用户后,自动关联权限组 + */ + protected String permissionGroup; + + + /** + * 是否开启 + * + * @return true 开启 + */ + public boolean enabled() { + return enabled != null && enabled; + } + + /** + * 是否自动创建用户 + * + * @return true 开启 + */ + public boolean autoCreteUser() { + return autoCreteUser != null && autoCreteUser; + } + + + /** + * 验证数据 + */ + public void check() { + Assert.hasText(this.clientId, I18nMessageUtil.get("i18n.client_id_not_configured.ab8e")); + Assert.hasText(this.clientSecret, I18nMessageUtil.get("i18n.client_secret_not_configured.6923")); + Validator.validateMatchRegex(RegexPool.URL_HTTP, this.redirectUri, I18nMessageUtil.get("i18n.configure_correct_redirect_url.058e")); + } + + /** + * 供应商 + * + * @return 返回供应商 + */ + public abstract String provide(); + + /** + * oauth2 请求对象 + * + * @return AuthRequest + */ + public abstract AuthRequest authRequest(); + + public T getValue(Function function, T defaultValue) { + return Optional.ofNullable(function.apply(this)).orElse(defaultValue); + } + + public AuthConfig authConfig() { + return AuthConfig.builder() + .clientId(this.clientId) + .clientSecret(this.clientSecret) + .redirectUri(this.redirectUri) + .ignoreCheckState(this.getValue(BaseOauth2Config::getIgnoreCheckState, false)) + .build(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/MyAuthGitlabRequest.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/MyAuthGitlabRequest.java new file mode 100644 index 0000000000..f873aa2530 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/MyAuthGitlabRequest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2; + +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.enums.scope.AuthGitlabScope; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthDefaultRequest; +import me.zhyd.oauth.request.AuthGitlabRequest; +import me.zhyd.oauth.utils.AuthScopeUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + * 自建 gtilab + * + * @author bwcx_jzy + * @see AuthDefaultSource#GITLAB + * @see AuthGitlabRequest + * @since 2024/04/07 + */ +public class MyAuthGitlabRequest extends AuthDefaultRequest { + + public MyAuthGitlabRequest(AuthConfig config, AuthSource authSource) { + super(config, authSource); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + String response = doPostAuthorizationCode(authCallback.getCode()); + JSONObject object = JSONObject.parseObject(response); + + this.checkResponse(object); + + return AuthToken.builder() + .accessToken(object.getString("access_token")) + .refreshToken(object.getString("refresh_token")) + .idToken(object.getString("id_token")) + .tokenType(object.getString("token_type")) + .scope(object.getString("scope")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String response = doGetUserInfo(authToken); + JSONObject object = JSONObject.parseObject(response); + + this.checkResponse(object); + + return AuthUser.builder() + .rawUserInfo(object) + .uuid(object.getString("id")) + .username(object.getString("username")) + .nickname(object.getString("name")) + .avatar(object.getString("avatar_url")) + .blog(object.getString("web_url")) + .company(object.getString("organization")) + .location(object.getString("location")) + .email(object.getString("email")) + .remark(object.getString("bio")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(source.toString()) + .build(); + } + + private void checkResponse(JSONObject object) { + // oauth/token 验证异常 + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); + } + // user 验证异常 + if (object.containsKey("message")) { + throw new AuthException(object.getString("message")); + } + } + + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + * @since 1.11.0 + */ + @Override + public String authorize(String state) { + return UrlBuilder.fromBaseUrl(super.authorize(state)) + .queryParam("scope", this.getScopes("+", false, AuthScopeUtils.getDefaultScopes(AuthGitlabScope.values()))) + .build(); + } +} + diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/Oauth2Factory.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/Oauth2Factory.java new file mode 100644 index 0000000000..5bffd039d0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/Oauth2Factory.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2; + +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.map.SafeConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; + +import java.util.Collection; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2023/3/30 + */ +@Configuration +@Slf4j +public class Oauth2Factory implements ILoadEvent { + + private static final Map AUTH_REQUEST = new SafeConcurrentHashMap<>(); + private static final Map AUTH_CONFIG = new SafeConcurrentHashMap<>(); + + private final SystemParametersServer systemParametersServer; + + public Oauth2Factory(SystemParametersServer systemParametersServer) { + this.systemParametersServer = systemParametersServer; + } + + /** + * 查询已经开启的平台 + * + * @return 平台名称 + */ + public static Collection provides() { + return AUTH_CONFIG.keySet(); + } + + /** + * 更新配置 + * + * @param oauth2Config 配置 + */ + public static void put(BaseOauth2Config oauth2Config) { + if (oauth2Config.enabled()) { + AUTH_REQUEST.put(oauth2Config.provide(), oauth2Config.authRequest()); + AUTH_CONFIG.put(oauth2Config.provide(), oauth2Config); + } else { + AUTH_REQUEST.remove(oauth2Config.provide()); + AUTH_CONFIG.remove(oauth2Config.provide()); + } + } + + /** + * 获取配置 + * + * @param provide 平台 + * @return config + */ + public static BaseOauth2Config getConfig(String provide) { + BaseOauth2Config oauth2Config = AUTH_CONFIG.get(provide); + Assert.notNull(oauth2Config, I18nMessageUtil.get("i18n.no_oauth2_found.ea74") + provide); + return oauth2Config; + } + + public static AuthRequest get(String provide) { + AuthRequest authRequest = AUTH_REQUEST.get(provide); + Assert.notNull(authRequest, I18nMessageUtil.get("i18n.no_oauth2_found.ea74") + provide); + return authRequest; + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + for (Map.Entry entry : BaseOauth2Config.DB_KEYS.entrySet()) { + Tuple value = entry.getValue(); + String dbKey = value.get(0); + BaseOauth2Config baseOauth2Config = systemParametersServer.getConfigDefNewInstance(dbKey, value.get(1)); + put(baseOauth2Config); + log.debug(I18nMessageUtil.get("i18n.load_oauth2_config.da42"), entry.getKey(), dbKey); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/Oauth2MaxKeyAuthSource.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/Oauth2MaxKeyAuthSource.java new file mode 100644 index 0000000000..a87ba253ee --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/Oauth2MaxKeyAuthSource.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2; + +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.request.AuthDefaultRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.platform.MaxKeyOauth2Config; +import org.springframework.util.Assert; + +/** + * @author MaxKey + */ +public class Oauth2MaxKeyAuthSource implements AuthSource { + + private final MaxKeyOauth2Config oauthConfig; + + public Oauth2MaxKeyAuthSource(MaxKeyOauth2Config oauthConfig) { + this.oauthConfig = oauthConfig; + } + + @Override + public String authorize() { + Assert.notNull(oauthConfig, I18nMessageUtil.get("i18n.oauth2_not_configured.9c85")); + return oauthConfig.getAuthorizationUri(); + } + + @Override + public String accessToken() { + Assert.notNull(oauthConfig, I18nMessageUtil.get("i18n.oauth2_not_configured.9c85")); + return oauthConfig.getAccessTokenUri(); + } + + @Override + public String userInfo() { + Assert.notNull(oauthConfig, I18nMessageUtil.get("i18n.oauth2_not_configured.9c85")); + return oauthConfig.getUserInfoUri(); + } + + + @Override + public Class getTargetClass() { + // TODO Auto-generated method stub + return AuthOauth2MaxKeyRequest.class; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/DingTalkOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/DingTalkOauth2Config.java new file mode 100644 index 0000000000..0acd5c1adb --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/DingTalkOauth2Config.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.request.AuthDingTalkRequest; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @see AuthDefaultSource#DINGTALK + * @since 2024/04/05 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class DingTalkOauth2Config extends BaseOauth2Config { + + public static final String KEY = "OAUTH_CONFIG_DINGTALK_OAUTH2"; + + @Override + public String provide() { + return "dingtalk"; + } + + @Override + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + return new AuthDingTalkRequest(this.authConfig()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/FeishuOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/FeishuOauth2Config.java new file mode 100644 index 0000000000..475c90d526 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/FeishuOauth2Config.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.request.AuthFeishuRequest; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @see AuthDefaultSource#FEISHU + * @since 2024/04/07 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class FeishuOauth2Config extends BaseOauth2Config { + + public static final String KEY = "OAUTH_CONFIG_FEISHU_OAUTH2"; + + @Override + public String provide() { + return "feishu"; + } + + @Override + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + return new AuthFeishuRequest(this.authConfig()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/GiteeOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/GiteeOauth2Config.java new file mode 100644 index 0000000000..0592d49b53 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/GiteeOauth2Config.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.request.AuthGiteeRequest; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @since 2023/3/30 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GiteeOauth2Config extends BaseOauth2Config { + public static final String KEY = "OAUTH_CONFIG_GITEE_OAUTH2"; + + @Override + public String provide() { + return "gitee"; + } + + @Override + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + return new AuthGiteeRequest(this.authConfig()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/GithubOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/GithubOauth2Config.java new file mode 100644 index 0000000000..a2b3f8ade5 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/GithubOauth2Config.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.request.AuthGithubRequest; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @since 2023/3/30 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GithubOauth2Config extends BaseOauth2Config { + + public static final String KEY = "OAUTH_CONFIG_GITHUB_OAUTH2"; + + @Override + public String provide() { + return "github"; + } + + @Override + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + return new AuthGithubRequest(this.authConfig()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/MaxKeyOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/MaxKeyOauth2Config.java new file mode 100644 index 0000000000..b16a0bdc41 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/MaxKeyOauth2Config.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.AuthOauth2MaxKeyRequest; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.dromara.jpom.oauth2.Oauth2MaxKeyAuthSource; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @since 2023/3/26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MaxKeyOauth2Config extends BaseOauth2Config { + public static final String KEY = "OAUTH_CONFIG_CUSTOM_OAUTH2"; + + private String authorizationUri; + private String accessTokenUri; + private String userInfoUri; + + + /** + * 验证数据 + */ + public void check() { + super.check(); + Validator.validateMatchRegex(RegexPool.URL_HTTP, this.authorizationUri, I18nMessageUtil.get("i18n.configure_correct_auth_url.22e7")); + Validator.validateMatchRegex(RegexPool.URL_HTTP, this.accessTokenUri, I18nMessageUtil.get("i18n.configure_correct_token_url.7bba")); + Validator.validateMatchRegex(RegexPool.URL_HTTP, this.userInfoUri, I18nMessageUtil.get("i18n.configure_correct_user_info_url.1276")); + } + + @Override + public String provide() { + return "maxkey"; + } + + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + Oauth2MaxKeyAuthSource oauth2MaxKeyAuthSource = new Oauth2MaxKeyAuthSource(this); + return new AuthOauth2MaxKeyRequest(this.authConfig(), oauth2MaxKeyAuthSource); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/MyGitlabOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/MyGitlabOauth2Config.java new file mode 100644 index 0000000000..96400f890e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/MyGitlabOauth2Config.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.request.AuthDefaultRequest; +import me.zhyd.oauth.request.AuthRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.dromara.jpom.oauth2.MyAuthGitlabRequest; +import org.springframework.util.Assert; + +/** + * 自建 Gitlab 配置 + * + * @author bwcx_jzy + * @see AuthDefaultSource#GITLAB + * @since 2024/04/07 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MyGitlabOauth2Config extends BaseOauth2Config implements AuthSource { + + public static final String KEY = "OAUTH_CONFIG_MYGITLAB_OAUTH2"; + + private String host; + + @Override + public String provide() { + return "mygitlab"; + } + + @Override + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + return new MyAuthGitlabRequest(this.authConfig(), this); + } + + @Override + public void check() { + super.check(); + Validator.validateMatchRegex(RegexPool.URL_HTTP, this.host, I18nMessageUtil.get("i18n.configure_correct_self_hosted_gitlab_address.ad50")); + } + + /** + * @return str + * @see AuthDefaultSource#GITLAB#authorize() + */ + @Override + public String authorize() { + return UrlBuilder.of(this.host).addPath("/oauth/authorize").build(); + } + + @Override + public String accessToken() { + // return "https://gitlab.com/oauth/token"; + return UrlBuilder.of(this.host).addPath("/oauth/token").build(); + } + + @Override + public String userInfo() { + // "https://gitlab.com/api/v4/user"; + return UrlBuilder.of(this.host).addPath("/api/v4/user").build(); + } + + @Override + public Class getTargetClass() { + return MyAuthGitlabRequest.class; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/WechatEnterpriseOauth2Config.java b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/WechatEnterpriseOauth2Config.java new file mode 100644 index 0000000000..7347c0083a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/oauth2/platform/WechatEnterpriseOauth2Config.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.oauth2.platform; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthDefaultSource; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.request.AuthWeChatEnterpriseQrcodeRequest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.oauth2.BaseOauth2Config; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @see AuthDefaultSource#WECHAT_ENTERPRISE + * @since 2024/04/07 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WechatEnterpriseOauth2Config extends BaseOauth2Config { + + public static final String KEY = "OAUTH_CONFIG_WECHAT_ENTERPRISE_OAUTH2"; + /** + * 企业微信,授权方的网页应用ID + * + * @since 1.10.0 + */ + private String agentId; + + @Override + public String provide() { + return "wechat_enterprise"; + } + + @Override + public AuthRequest authRequest() { + Assert.state(this.enabled(), StrUtil.format(I18nMessageUtil.get("i18n.oauth2_not_enabled.c8b7"), this.provide())); + return new AuthWeChatEnterpriseQrcodeRequest(this.authConfig()); + } + + @Override + public AuthConfig authConfig() { + AuthConfig authConfig = super.authConfig(); + authConfig.setAgentId(this.agentId); + return authConfig; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingItemRun.java b/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingItemRun.java new file mode 100644 index 0000000000..fb75eaf294 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingItemRun.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.outgiving; + +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.log.OutGivingLog; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.outgiving.DbOutGivingLogService; + +import java.io.File; +import java.util.concurrent.Callable; + +/** + * @author bwcx_jzy + * @since 2021/12/10 + */ +@Slf4j +@Setter +public class OutGivingItemRun implements Callable { + + private final String outGivingId; + private final OutGivingNodeProject outGivingNodeProject; + private final NodeModel nodeModel; + private final File file; + private final AfterOpt afterOpt; + private final boolean unzip; + private final boolean clearOld; + private final Integer sleepTime; + private final String secondaryDirectory; + private final Boolean closeFirst; + private int stripComponents; + + public OutGivingItemRun(OutGivingModel item, + OutGivingNodeProject outGivingNodeProject, + File file, + boolean unzip, + Integer sleepTime) { + this.outGivingId = item.getId(); + this.secondaryDirectory = item.getSecondaryDirectory(); + this.clearOld = item.clearOld(); + this.closeFirst = item.getUploadCloseFirst(); + this.unzip = unzip; + this.outGivingNodeProject = outGivingNodeProject; + this.file = file; + this.afterOpt = ObjectUtil.defaultIfNull(EnumUtil.likeValueOf(AfterOpt.class, item.getAfterOpt()), AfterOpt.No); + // + NodeService nodeService = SpringUtil.getBean(NodeService.class); + this.nodeModel = nodeService.getByKey(outGivingNodeProject.getNodeId()); + // + this.sleepTime = sleepTime; + } + + @Override + public OutGivingNodeProject.Status call() { + OutGivingNodeProject.Status result; + long time = SystemClock.now(); + String fileSize = FileUtil.readableFileSize(file); + try { + if (this.outGivingNodeProject.getDisabled() != null && this.outGivingNodeProject.getDisabled()) { + // 禁用 + this.updateStatus(this.outGivingId, OutGivingNodeProject.Status.Cancel, I18nMessageUtil.get("i18n.project_disabled.f8b3")); + return OutGivingNodeProject.Status.Cancel; + } + this.updateStatus(this.outGivingId, OutGivingNodeProject.Status.Ing, I18nMessageUtil.get("i18n.start_distribution.bce5")); + // + JsonMessage jsonMessage = OutGivingRun.fileUpload(file, this.secondaryDirectory, + this.outGivingNodeProject.getProjectId(), + unzip, + afterOpt, + this.nodeModel, this.clearOld, + this.sleepTime, this.closeFirst, this.stripComponents, (total, progressSize) -> { + + String logId = OutGivingRun.getLogId(outGivingId, outGivingNodeProject); + // + OutGivingLog outGivingLog = new OutGivingLog(); + outGivingLog.setId(logId); + outGivingLog.setFileSize(total); + outGivingLog.setProgressSize(progressSize); + // + DbOutGivingLogService dbOutGivingLogService = SpringUtil.getBean(DbOutGivingLogService.class); + dbOutGivingLogService.updateById(outGivingLog); + }); + result = jsonMessage.success() ? OutGivingNodeProject.Status.Ok : OutGivingNodeProject.Status.Fail; + + JSONObject jsonObject = jsonMessage.toJson(); + jsonObject.put("upload_duration", new BetweenFormatter(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND, 2).format()); + jsonObject.put("upload_file_size", fileSize); + this.updateStatus(this.outGivingId, result, jsonObject.toString()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.distribution_exception_saving.8285"), this.outGivingNodeProject.getNodeId(), this.outGivingNodeProject.getProjectId(), e); + result = OutGivingNodeProject.Status.Fail; + JSONObject jsonObject = JsonMessage.toJson(500, e.getMessage()); + jsonObject.put("upload_duration", new BetweenFormatter(SystemClock.now() - time, BetweenFormatter.Level.MILLISECOND, 2).format()); + jsonObject.put("upload_file_size", fileSize); + this.updateStatus(this.outGivingId, result, jsonObject.toString()); + } + return result; + } + + /** + * 更新状态 + * + * @param outGivingId 分发id + * @param status 状态 + * @param msg 消息描述 + */ + private void updateStatus(String outGivingId, OutGivingNodeProject.Status status, String msg) { + String logId = OutGivingRun.getLogId(outGivingId, outGivingNodeProject); + OutGivingLog outGivingLog = new OutGivingLog(); + outGivingLog.setId(logId); + outGivingLog.setStatus(status.getCode()); + outGivingLog.setResult(msg); + if (status == OutGivingNodeProject.Status.Ok || status == OutGivingNodeProject.Status.Fail) { + outGivingLog.setEndTime(SystemClock.now()); + } + DbOutGivingLogService dbOutGivingLogService = SpringUtil.getBean(DbOutGivingLogService.class); + dbOutGivingLogService.updateById(outGivingLog); + } +} 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 new file mode 100644 index 0000000000..a825021295 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingRun.java @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.outgiving; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.BaseIdModel; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.Builder; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.model.AfterOpt; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.log.OutGivingLog; +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.outgiving.DbOutGivingLogService; +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; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +/** + * 分发线程 + * + * @author bwcx_jzy + * @since 2019/7/18 + **/ +@Slf4j +@Builder +public class OutGivingRun { + + private static final Map> LOG_CACHE_MAP = new SafeConcurrentHashMap<>(); + +// * @param id 分发id +// * @param file 文件 +// * @param userModel 操作的用户 +// * @param stripComponents 剔除文件夹 +// * @param unzip 解压 +// * @param clearFile 是否清空文件 + + private String id; + private File file; + private UserModel userModel; + private boolean unzip; + private int stripComponents; + /** + * 分发方式 + * upload: "手动上传", + * download: "远程下载", + * "build-trigger": "构建触发", + * "use-build": "构建产物", + */ + private String mode; + private String modeData; + /** + * 是否删除发布文件 + */ + @Builder.Default + private boolean doneDeleteFile = true; + private String projectSecondaryDirectory; + private LogRecorder logRecorder; + + /** + * 取消分发 + * + * @param id 分发id + */ + public static void cancel(String id, UserModel userModel) { + SyncFinisherUtil.cancel("outgiving:" + id); + // + Map map = LOG_CACHE_MAP.remove(id); + Optional.ofNullable(map).ifPresent(map1 -> { + DbOutGivingLogService dbOutGivingLogService = SpringUtil.getBean(DbOutGivingLogService.class); + for (String logId : map1.values()) { + OutGivingLog outGivingLog = new OutGivingLog(); + outGivingLog.setId(logId); + outGivingLog.setStatus(OutGivingNodeProject.Status.ArtificialCancel.getCode()); + outGivingLog.setResult(I18nMessageUtil.get("i18n.manual_cancel_distribution.7bf6")); + dbOutGivingLogService.updateById(outGivingLog); + } + if (!map1.isEmpty()) { + // 更新分发数据 + updateStatus(id, OutGivingModel.Status.CANCEL, null, userModel); + } + }); + + } + + + public static String getLogId(String outId, OutGivingNodeProject nodeProject) { + Map map = LOG_CACHE_MAP.get(outId); + Assert.notNull(map, I18nMessageUtil.get("i18n.current_distribution_data_lost.f9f8")); + String dataId = StrUtil.format("{}_{}", nodeProject.getNodeId(), nodeProject.getProjectId()); + String logId = map.get(dataId); + Assert.hasText(logId, I18nMessageUtil.get("i18n.current_distribution_data_lost_record_id_not_exist.ca07")); + return logId; + } + + private void removeLogId(String outId, OutGivingNodeProject nodeProject) { + Map map = LOG_CACHE_MAP.get(outId); + Assert.notNull(map, I18nMessageUtil.get("i18n.current_distribution_data_lost.f9f8")); + String dataId = StrUtil.format("{}_{}", nodeProject.getNodeId(), nodeProject.getProjectId()); + map.remove(dataId); + } + + /** + * 标记系统取消 + * + * @param cancelList 需要取消的 list + * @param outGivingId 分发id + */ + private static void systemCancel(String outGivingId, List cancelList) { + if (CollUtil.isEmpty(cancelList)) { + return; + } + DbOutGivingLogService dbOutGivingLogService = SpringUtil.getBean(DbOutGivingLogService.class); + for (OutGivingNodeProject outGivingNodeProject : cancelList) { + String logId = OutGivingRun.getLogId(outGivingId, outGivingNodeProject); + OutGivingLog outGivingLog = new OutGivingLog(); + outGivingLog.setId(logId); + outGivingLog.setStatus(OutGivingNodeProject.Status.Cancel.getCode()); + outGivingLog.setResult(I18nMessageUtil.get("i18n.previous_node_distribution_failure.d556")); + dbOutGivingLogService.updateById(outGivingLog); + } + } + + + /** + * 开始异步执行分发任务 + * + * @param select 只发布指定项目 + */ + public Future startRun(String select) { + OutGivingServer outGivingServer = SpringUtil.getBean(OutGivingServer.class); + OutGivingModel item = outGivingServer.getByKey(id); + Objects.requireNonNull(item, I18nMessageUtil.get("i18n.no_distribution_exists.4425")); + // 更新二级目录 + Opt.ofBlankAble(this.projectSecondaryDirectory).ifPresent(item::setSecondaryDirectory); + // + AfterOpt afterOpt = ObjectUtil.defaultIfNull(EnumUtil.likeValueOf(AfterOpt.class, item.getAfterOpt()), AfterOpt.No); + StrictSyncFinisher syncFinisher; + // + List outGivingNodeProjects = item.outGivingNodeProjectList(select); + Assert.notEmpty(outGivingNodeProjects, I18nMessageUtil.get("i18n.no_distribution_project.d4d1")); + int projectSize = outGivingNodeProjects.size(); + final List statusList = new ArrayList<>(projectSize); + // 开启线程 + if (afterOpt == AfterOpt.Order_Restart || afterOpt == AfterOpt.Order_Must_Restart) { + syncFinisher = SyncFinisherUtil.create("outgiving:" + id, 1); + //new StrictSyncFinisher(1, 1); + syncFinisher.addWorker(() -> { + try { + // 截取睡眠时间 + int sleepTime = ObjectUtil.defaultIfNull(item.getIntervalTime(), 10); + // + int nowIndex; + for (nowIndex = 0; nowIndex < outGivingNodeProjects.size(); nowIndex++) { + final OutGivingNodeProject outGivingNodeProject = outGivingNodeProjects.get(nowIndex); + final OutGivingItemRun outGivingRun = new OutGivingItemRun(item, outGivingNodeProject, file, unzip, sleepTime); + outGivingRun.setStripComponents(stripComponents); + OutGivingNodeProject.Status status = outGivingRun.call(); + if (status != OutGivingNodeProject.Status.Ok) { + if (afterOpt == AfterOpt.Order_Must_Restart) { + // 完整重启,不再继续剩余的节点项目 + break; + } + } + statusList.add(status); + // 删除标记 log + removeLogId(id, outGivingNodeProject); + // 休眠x秒 等待之前项目正常启动 + ThreadUtil.sleep(sleepTime, TimeUnit.SECONDS); + } + // 取消后面的分发 + List cancelList = CollUtil.sub(outGivingNodeProjects, nowIndex + 1, outGivingNodeProjects.size()); + systemCancel(id, cancelList); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.distribute_exception_with_detail.28fe"), id, e); + } + }); + } else if (afterOpt == AfterOpt.Restart || afterOpt == AfterOpt.No) { + syncFinisher = SyncFinisherUtil.create("outgiving:" + id, projectSize); + + for (final OutGivingNodeProject outGivingNodeProject : outGivingNodeProjects) { + final OutGivingItemRun outGivingItemRun = new OutGivingItemRun(item, outGivingNodeProject, file, unzip, null); + outGivingItemRun.setStripComponents(stripComponents); + syncFinisher.addWorker(() -> { + try { + statusList.add(outGivingItemRun.call()); + // 删除标记 log + removeLogId(id, outGivingNodeProject); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.distribute_exception.da82"), e); + } + }); + } + } else { + // + throw new IllegalArgumentException("Not implemented " + afterOpt.getDesc()); + } + String userId = Optional.ofNullable(userModel).map(BaseIdModel::getId).orElse(Const.SYSTEM_ID); + // 更新维准备中 + allPrepare(userId, item, outGivingNodeProjects); + // 异步执行 + Callable callable = createRunnable(syncFinisher, statusList, projectSize); + return I18nThreadUtil.execAsync(callable); + } + + private Callable createRunnable(StrictSyncFinisher syncFinisher, + List statusList, int projectSize) { + return () -> { + OutGivingModel.Status status = null; + try { + // 阻塞执行 + Optional.ofNullable(logRecorder).ifPresent(logRecorder -> logRecorder.system(I18nMessageUtil.get("i18n.start_distribution_with_count.cdc7"), projectSize)); + syncFinisher.start(); + // 更新分发状态 + String msg; + if (statusList.size() != projectSize) { + // + status = OutGivingModel.Status.FAIL; + msg = StrUtil.format(I18nMessageUtil.get("i18n.completed_count_insufficient.02e9"), statusList.size(), projectSize); + } else { + int successCount = statusList.stream().mapToInt(value -> value == OutGivingNodeProject.Status.Ok ? 1 : 0).sum(); + if (successCount == projectSize) { + status = OutGivingModel.Status.DONE; + msg = I18nMessageUtil.get("i18n.distribute_success.c689") + successCount; + } else { + status = OutGivingModel.Status.FAIL; + msg = StrUtil.format(I18nMessageUtil.get("i18n.completed_and_successful_count_insufficient.92fa"), successCount, projectSize); + } + } + Optional.ofNullable(logRecorder).ifPresent(logRecorder -> logRecorder.system(msg)); + updateStatus(id, status, msg, userModel); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.distribute_thread_exception.9725"), e); + updateStatus(id, OutGivingModel.Status.FAIL, e.getMessage(), userModel); + } finally { + if (doneDeleteFile) { + // 删除分发的文件 + FileUtil.del(file); + } + // + SyncFinisherUtil.close("outgiving:" + id); + LOG_CACHE_MAP.remove(id); + } + return status; + }; + } + + /** + * 将所有数据更新维准备中 + * + * @param outGivingModel 分发 + * @param outGivingNodeProjects 要分发的项目信息 + * @param userId 用户id + */ + private void allPrepare(String userId, OutGivingModel outGivingModel, List outGivingNodeProjects) { + // + String outGivingId = outGivingModel.getId(); + List outGivingLogs = outGivingNodeProjects.stream() + .map(outGivingNodeProject -> { + OutGivingLog outGivingLog = new OutGivingLog(); + outGivingLog.setOutGivingId(outGivingId); + outGivingLog.setWorkspaceId(outGivingModel.getWorkspaceId()); + outGivingLog.setNodeId(outGivingNodeProject.getNodeId()); + outGivingLog.setProjectId(outGivingNodeProject.getProjectId()); + 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()); + DbOutGivingLogService dbOutGivingLogService = SpringUtil.getBean(DbOutGivingLogService.class); + dbOutGivingLogService.insert(outGivingLogs); + // + Map logIdMap = CollStreamUtil.toMap(outGivingLogs, outGivingLog -> StrUtil.format("{}_{}", outGivingLog.getNodeId(), outGivingLog.getProjectId()), BaseIdModel::getId); + + OutGivingRun.LOG_CACHE_MAP.put(outGivingId, logIdMap); + + // 更新分发数据 + updateStatus(outGivingId, OutGivingModel.Status.ING, null, userModel); + } + + /** + * 更新方法状态 + * + * @param outGivingId 分发id + * @param status 状态 + * @param msg 消息 + * @param userModel 操作人 + */ + private static void updateStatus(String outGivingId, OutGivingModel.Status status, String msg, UserModel userModel) { + OutGivingServer outGivingServer = SpringUtil.getBean(OutGivingServer.class); + OutGivingModel outGivingModel1 = new OutGivingModel(); + outGivingModel1.setId(outGivingId); + outGivingModel1.setStatus(status.getCode()); + outGivingModel1.setStatusMsg(msg); + outGivingServer.updateById(outGivingModel1); + // + OutGivingModel outGivingModel = outGivingServer.getByKey(outGivingId); + Opt.ofNullable(outGivingModel) + .map(outGivingModel2 -> + Opt.ofBlankAble(outGivingModel2.getWebhook()) + .orElse(null)) + .ifPresent(webhook -> + I18nThreadUtil.execute(() -> { + // outGivingId、outGivingName、status、statusMsg、executeTime + Map map = new HashMap<>(10); + map.put("outGivingId", outGivingId); + map.put("outGivingName", outGivingModel.getName()); + map.put("status", status.getCode()); + map.put("statusMsg", msg); + // 操作人 + String triggerUser = Optional.ofNullable(userModel).map(BaseIdModel::getId).orElse(UserModel.SYSTEM_ADMIN); + map.put("triggerUser", triggerUser); + 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(I18nMessageUtil.get("i18n.webhooks_invocation_error.9792"), e); + } + })); + } + + /** + * 上传项目文件 + * + * @param file 需要上传的文件 + * @param projectId 项目id + * @param unzip 是否需要解压 + * @param afterOpt 是否需要重启 + * @param nodeModel 节点 + * @param clearOld 清空发布 + * @param levelName 文件夹层级 + * @param closeFirst 保存项目文件前先关闭项目 + * @return json + */ + public static JsonMessage fileUpload(File file, String levelName, String projectId, + boolean unzip, + AfterOpt afterOpt, + NodeModel nodeModel, + boolean clearOld, + Boolean closeFirst, + BiConsumer streamProgress) { + return fileUpload(file, levelName, projectId, unzip, afterOpt, nodeModel, clearOld, null, closeFirst, streamProgress); + } + + /** + * 上传项目文件 + * + * @param file 需要上传的文件 + * @param projectId 项目id + * @param unzip 是否需要解压 + * @param afterOpt 是否需要重启 + * @param nodeModel 节点 + * @param clearOld 清空发布 + * @param levelName 文件夹层级 + * @param sleepTime 休眠时间 + * @param closeFirst 保存项目文件前先关闭项目 + * @return json + */ + public static JsonMessage fileUpload(File file, String levelName, String projectId, + boolean unzip, + AfterOpt afterOpt, + NodeModel nodeModel, + boolean clearOld, + Integer sleepTime, + Boolean closeFirst, + BiConsumer streamProgress) { + return fileUpload(file, levelName, projectId, unzip, afterOpt, nodeModel, clearOld, sleepTime, closeFirst, 0, streamProgress); + } + + /** + * 上传项目文件 + * + * @param file 需要上传的文件 + * @param projectId 项目id + * @param unzip 是否需要解压 + * @param afterOpt 是否需要重启 + * @param nodeModel 节点 + * @param clearOld 清空发布 + * @param levelName 文件夹层级 + * @param sleepTime 休眠时间 + * @param closeFirst 保存项目文件前先关闭项目 + * @return json + */ + public static JsonMessage fileUpload(File file, String levelName, String projectId, + boolean unzip, + AfterOpt afterOpt, + NodeModel nodeModel, + boolean clearOld, + Integer sleepTime, + Boolean closeFirst, int stripComponents, + BiConsumer streamProgress) { + JSONObject data = new JSONObject(); + // data.put("file", file); + data.put("id", projectId); + Opt.ofBlankAble(levelName).ifPresent(s -> data.put("levelName", s)); + Opt.ofNullable(sleepTime).ifPresent(integer -> data.put("sleepTime", integer)); + + if (unzip) { + // 解压 + data.put("type", "unzip"); + data.put("stripComponents", stripComponents); + } + if (clearOld) { + // 清空 + data.put("clearType", "clear"); + } + // 操作 + if (afterOpt != AfterOpt.No) { + data.put("after", afterOpt.getCode()); + } + data.put("closeFirst", closeFirst); + try { + return NodeForward.requestSharding(nodeModel, NodeUrl.Manage_File_Upload_Sharding, data, file, + sliceData -> { + sliceData.putAll(data); + return NodeForward.request(nodeModel, NodeUrl.Manage_File_Sharding_Merge, sliceData); + }, + streamProgress); + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + + //return NodeForward.request(nodeModel, NodeUrl.Manage_File_Upload, data); + } +} 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 new file mode 100644 index 0000000000..928c6278f7 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/permission/ClassFeature.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.permission; + +import lombok.Getter; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.assets.server.MachineNodeServer; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.func.assets.server.ScriptLibraryServer; +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.*; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.monitor.MonitorService; +import org.dromara.jpom.service.monitor.MonitorUserOptService; +import org.dromara.jpom.service.node.NodeService; +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.SshCommandService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.outgiving.DbOutGivingLogService; +import org.dromara.jpom.service.outgiving.LogReadServer; +import org.dromara.jpom.service.outgiving.OutGivingServer; +import org.dromara.jpom.service.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.service.user.UserPermissionGroupServer; +import org.dromara.jpom.service.user.UserService; + +import java.util.function.Supplier; + +/** + * 功能模块 + * + * @author bwcx_jzy + * @since 2019/8/13 + */ +@Getter +public enum ClassFeature { + /** + * 没有 + */ + NULL(() -> "", null, null), + NODE(() -> I18nMessageUtil.get("i18n.node_management.b26d"), NodeService.class), + NODE_STAT(() -> I18nMessageUtil.get("i18n.node_statistics.b4e1"), NodeService.class), + UPGRADE_NODE_LIST(() -> I18nMessageUtil.get("i18n.node_upgrade.3bf3"), NodeService.class), + SEARCH_PROJECT(() -> I18nMessageUtil.get("i18n.search_project.7e9b"), ProjectInfoCacheService.class), + SSH(() -> I18nMessageUtil.get("i18n.ssh_management.9e0f"), SshService.class), + SSH_FILE(() -> I18nMessageUtil.get("i18n.ssh_file_manager.1482"), SshService.class), + SSH_TERMINAL(() -> I18nMessageUtil.get("i18n.ssh_terminal.ec50"), SshService.class), + SSH_TERMINAL_LOG(() -> I18nMessageUtil.get("i18n.ssh_terminal_log.775f"), SshTerminalExecuteLogService.class), + SSH_COMMAND(() -> I18nMessageUtil.get("i18n.ssh_command_management.c40a"), SshCommandService.class), + SSH_COMMAND_LOG(() -> I18nMessageUtil.get("i18n.ssh_command_log.7fd1"), CommandExecLogService.class), + OUTGIVING(() -> I18nMessageUtil.get("i18n.distribute_management.3a2d"), OutGivingServer.class), + LOG_READ(() -> I18nMessageUtil.get("i18n.log_reading.a4c8"), LogReadServer.class), + OUTGIVING_LOG(() -> I18nMessageUtil.get("i18n.distribute_log.c612"), DbOutGivingLogService.class), + OUTGIVING_CONFIG_WHITELIST(() -> I18nMessageUtil.get("i18n.auth_config.3d48")), + MONITOR(() -> I18nMessageUtil.get("i18n.project_monitor.d2ff"), MonitorService.class), + MONITOR_LOG(() -> I18nMessageUtil.get("i18n.monitoring_logs.2217"), DbMonitorNotifyLogService.class), + OPT_MONITOR(() -> I18nMessageUtil.get("i18n.operation_monitoring.0cd5"), MonitorUserOptService.class), + DOCKER(() -> I18nMessageUtil.get("i18n.docker_management.e7e5"), DockerInfoService.class), + DOCKER_SWARM(() -> I18nMessageUtil.get("i18n.container_cluster.a5b4"), DockerSwarmInfoService.class), + /** + * ssh + */ + BUILD(() -> I18nMessageUtil.get("i18n.online_build.6f7a"), BuildInfoService.class), + BUILD_LOG(() -> I18nMessageUtil.get("i18n.build_log.7c0e"), DbBuildHistoryLogService.class), + BUILD_REPOSITORY(() -> I18nMessageUtil.get("i18n.repository_info.22cd"), RepositoryService.class), + USER(() -> I18nMessageUtil.get("i18n.user_management.7d94"), UserService.class), + USER_LOG(() -> I18nMessageUtil.get("i18n.operation_log.cda8"), DbUserOperateLogService.class), + USER_LOGIN_LOG(() -> I18nMessageUtil.get("i18n.login_log.3fb2"), UserLoginLogServer.class), + FILE_STORAGE(() -> I18nMessageUtil.get("i18n.file_storage_center.6acf"), FileStorageService.class), + STATIC_FILE_STORAGE(() -> I18nMessageUtil.get("i18n.static_file_storage.35f6"), StaticFileStorageService.class), + FILE_STORAGE_RELEASE(() -> I18nMessageUtil.get("i18n.file_published.d1d9"), FileReleaseTaskService.class), + CERTIFICATE_INFO(() -> I18nMessageUtil.get("i18n.certificate_management.4001"), CertificateInfoService.class), + USER_PERMISSION_GROUP(() -> I18nMessageUtil.get("i18n.permission_group.ea59"), UserPermissionGroupServer.class), + SYSTEM_EMAIL(() -> I18nMessageUtil.get("i18n.email_configuration.b3f7")), + OAUTH_CONFIG(() -> I18nMessageUtil.get("i18n.authentication_config.964c")), + SYSTEM_CACHE(() -> I18nMessageUtil.get("i18n.system_cache.c4a8")), + SYSTEM_LOG(() -> I18nMessageUtil.get("i18n.system_logs.84aa")), + SYSTEM_UPGRADE(() -> I18nMessageUtil.get("i18n.online_upgrade.da8c")), + SYSTEM_ASSETS_MACHINE(() -> I18nMessageUtil.get("i18n.machine_asset_management.36ea"), MachineNodeServer.class), + SYSTEM_ASSETS_MACHINE_SSH(() -> I18nMessageUtil.get("i18n.ssh_asset_management.3b6c"), MachineSshServer.class), + SYSTEM_ASSETS_MACHINE_DOCKER(() -> I18nMessageUtil.get("i18n.docker_asset_management.96d9"), MachineDockerServer.class), + SYSTEM_ASSETS_GLOBAL_SCRIPT(() -> I18nMessageUtil.get("i18n.script_library.aed1"), ScriptLibraryServer.class), + SYSTEM_CONFIG(() -> I18nMessageUtil.get("i18n.server_system_config.3181")), + SYSTEM_EXT_CONFIG(() -> I18nMessageUtil.get("i18n.system_configuration_directory.0f82")), + SYSTEM_CONFIG_IP(() -> I18nMessageUtil.get("i18n.system_IP_authorization.9c08")), + // SYSTEM_CONFIG_MENUS("系统菜单配置"), + SYSTEM_NODE_WHITELIST(() -> I18nMessageUtil.get("i18n.node_authorized_distribution.c5d7")), + SYSTEM_BACKUP(() -> I18nMessageUtil.get("i18n.database_backup_label.62d8"), BackupInfoService.class), + SYSTEM_WORKSPACE(() -> I18nMessageUtil.get("i18n.workspace_label.98d6"), WorkspaceService.class), + SYSTEM_WORKSPACE_ENV(() -> I18nMessageUtil.get("i18n.environment_variable.3867"), WorkspaceEnvVarService.class), + CLUSTER_INFO(() -> I18nMessageUtil.get("i18n.cluster_management.74ea"), ClusterInfoService.class), + + SCRIPT(() -> I18nMessageUtil.get("i18n.script_template.54f2"), ScriptServer.class), + SCRIPT_LOG(() -> I18nMessageUtil.get("i18n.script_template_log.30cb"), ScriptExecuteLogServer.class), + + //****************************************** 节点管理功能 + PROJECT(() -> I18nMessageUtil.get("i18n.project_management.4363"), ClassFeature.NODE, ProjectInfoCacheService.class), + PROJECT_FILE(() -> I18nMessageUtil.get("i18n.project_file_manager.c8cb"), ClassFeature.NODE, ProjectInfoCacheService.class), + PROJECT_LOG(() -> I18nMessageUtil.get("i18n.project_log.2926"), ClassFeature.NODE, ProjectInfoCacheService.class), + PROJECT_CONSOLE(() -> I18nMessageUtil.get("i18n.project_console.3a94"), ClassFeature.NODE, ProjectInfoCacheService.class), + // JDK_LIST("JDK管理", ClassFeature.NODE), + NODE_SCRIPT(() -> I18nMessageUtil.get("i18n.node_script_template.be6a"), ClassFeature.NODE, NodeScriptServer.class), + NODE_SCRIPT_LOG(() -> I18nMessageUtil.get("i18n.node_script_template_log.85e3"), ClassFeature.NODE, NodeScriptExecuteLogServer.class), + AGENT_LOG(() -> I18nMessageUtil.get("i18n.plugin_system_log.955c"), ClassFeature.NODE), + FREE_SCRIPT(() -> I18nMessageUtil.get("i18n.free_script.7760"), ClassFeature.NODE, MachineNodeServer.class), +// TOMCAT_FILE("Tomcat file", ClassFeature.NODE), +// TOMCAT_LOG("Tomcat log", ClassFeature.NODE), + + + NODE_CONFIG_WHITELIST(() -> I18nMessageUtil.get("i18n.node_authorized_config.f934"), ClassFeature.NODE), + NODE_CONFIG(() -> I18nMessageUtil.get("i18n.node_authorized_config.f934"), ClassFeature.NODE), + NODE_CACHE(() -> I18nMessageUtil.get("i18n.node_cache.d68c"), ClassFeature.NODE), + NODE_LOG(() -> I18nMessageUtil.get("i18n.node_system_logs.3ac9"), ClassFeature.NODE), + NODE_UPGRADE(() -> I18nMessageUtil.get("i18n.node_online_upgrade.f144"), ClassFeature.NODE), + + +// PROJECT_RECOVER("项目回收", ClassFeature.NODE), + + ; + + private final Supplier name; + private final ClassFeature parent; + private final Class> dbService; + + ClassFeature(Supplier name) { + this(name, null, null); + } + + ClassFeature(Supplier name, ClassFeature parent) { + this(name, parent, null); + } + + + ClassFeature(Supplier name, Class> dbService) { + this(name, null, dbService); + } + + ClassFeature(Supplier name, ClassFeature parent, Class> dbService) { + this.name = name; + this.parent = parent; + this.dbService = dbService; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/permission/Feature.java b/modules/server/src/main/java/org/dromara/jpom/permission/Feature.java new file mode 100644 index 0000000000..e7897f2a3e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/permission/Feature.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.permission; + +import java.lang.annotation.*; + +/** + * 功能 + * + * @author bwcx_jzy + * @since 2019/8/13 + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Feature { + + /** + * 类 + * + * @return ClassFeature + */ + ClassFeature cls() default ClassFeature.NULL; + + /** + * 方法 + * + * @return MethodFeature + */ + MethodFeature method() default MethodFeature.NULL; + + /** + * 是否记录响应 日志 + * + * @return 默认 记录 + */ + boolean logResponse() default true; + + /** + * 是否记录操作日志 + * + * @return false 不记录操作日志 + */ + boolean log() default true; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/permission/MethodFeature.java b/modules/server/src/main/java/org/dromara/jpom/permission/MethodFeature.java new file mode 100644 index 0000000000..aedaf41f20 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/permission/MethodFeature.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.permission; + +import lombok.Getter; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.util.function.Supplier; + +/** + * 功能方法 + * + * @author bwcx_jzy + * @since 2019/8/13 + */ +@Getter +public enum MethodFeature { + /** + * 没有 + */ + NULL(() -> ""), + EDIT(() -> I18nMessageUtil.get("i18n.modify_or_add_data.e1f0")), + DEL(() -> I18nMessageUtil.get("i18n.delete_data.40f8")), + LIST(() -> I18nMessageUtil.get("i18n.list_and_query.c783")), + DOWNLOAD(() -> I18nMessageUtil.get("i18n.download_action.f26e")), + UPLOAD(() -> I18nMessageUtil.get("i18n.upload_action.d5a7")), + EXECUTE(() -> I18nMessageUtil.get("i18n.execute.1a6a")), + REMOTE_DOWNLOAD(() -> I18nMessageUtil.get("i18n.download_remote_file.ae84")), + ; + + private final Supplier name; + + MethodFeature(Supplier name) { + this.name = name; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/permission/NodeDataPermission.java b/modules/server/src/main/java/org/dromara/jpom/permission/NodeDataPermission.java new file mode 100644 index 0000000000..d152ea2631 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/permission/NodeDataPermission.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.permission; + +import org.dromara.jpom.service.h2db.BaseNodeService; + +import java.lang.annotation.*; + +/** + * @author bwcx_jzy + * @since 2021/12/23 + */ + +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NodeDataPermission { + + /** + * 参数名 + * + * @return 默认ID + */ + String parameterName() default "id"; + + /** + * 数据 class + * + * @return cls + */ + Class> cls(); +} diff --git a/modules/server/src/main/java/org/dromara/jpom/permission/SystemPermission.java b/modules/server/src/main/java/org/dromara/jpom/permission/SystemPermission.java new file mode 100644 index 0000000000..815beb4258 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/permission/SystemPermission.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.permission; + +import org.dromara.jpom.model.user.UserModel; + +import java.lang.annotation.*; + +/** + * 系统管理的权限 + * + * @author bwcx_jzy + * @since 2019/8/17 + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SystemPermission { + + /** + * 超级管理员 + * + * @return true 超级管理员 + * @see UserModel#SYSTEM_ADMIN + */ + boolean superUser() default false; +} diff --git a/modules/server/src/main/java/org/dromara/jpom/plugin/DefaultWorkspaceEnvPlugin.java b/modules/server/src/main/java/org/dromara/jpom/plugin/DefaultWorkspaceEnvPlugin.java new file mode 100644 index 0000000000..81bdd44875 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/plugin/DefaultWorkspaceEnvPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.plugins.PluginConfig; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/8/30 + */ +@PluginConfig(name = IWorkspaceEnvPlugin.PLUGIN_NAME) +public class DefaultWorkspaceEnvPlugin implements IWorkspaceEnvPlugin { + + @Override + public Object execute(Object main, Map parameter) throws Exception { + WorkspaceEnvVarService workspaceEnvVarService = SpringUtil.getBean(WorkspaceEnvVarService.class); + String workspaceId = (String) parameter.get(Const.WORKSPACE_ID_REQ_HEADER); + String value = (String) parameter.get("value"); + return workspaceEnvVarService.convertRefEnvValue(workspaceId, value); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/IStatusRecover.java b/modules/server/src/main/java/org/dromara/jpom/service/IStatusRecover.java new file mode 100644 index 0000000000..1564f0716a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/IStatusRecover.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service; + +/** + * 状态恢复接口 + * + * @author bwcx_jzy + * @since 2021/12/24 + */ +public interface IStatusRecover { + + /** + * 状态恢复 + * + * @return 恢复条数 + */ + int statusRecover(); +} 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 new file mode 100644 index 0000000000..17f72b0655 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/ITriggerToken.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service; + +import cn.keepbx.jpom.model.BaseIdModel; + +/** + * 带有触发器 token 相关实现服务 + * + * @author bwcx_jzy + * @since 2022/7/22 + */ +public interface ITriggerToken { + + /** + * 类型 名称 + * + * @return 数据分类名称 + */ + String typeName(); + + /** + * 数据描述 + * + * @return 描述 + */ + String getDataDesc(); + + /** + * 判断是否存在 + * + * @param dataId 数据id + * @return true 存在 + */ + boolean exists(String dataId); + + BaseIdModel getByKey(String keyValue); +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/BackupInfoService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/BackupInfoService.java new file mode 100644 index 0000000000..28b38c90fc --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/BackupInfoService.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.*; +import cn.hutool.core.io.FileUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.sql.Direction; +import cn.hutool.db.sql.Order; +import cn.keepbx.jpom.event.ISystemTask; +import cn.keepbx.jpom.plugins.IPlugin; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.StorageServiceFactory; +import org.dromara.jpom.model.data.BackupInfoModel; +import org.dromara.jpom.model.enums.BackupStatusEnum; +import org.dromara.jpom.model.enums.BackupTypeEnum; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 备份数据库 service + * + * @author Hotstrip + * @since 2021-11-18 + **/ +@Service +@Slf4j +public class BackupInfoService extends BaseDbService implements ISystemTask { + + private final DbExtConfig dbExtConfig; + + public BackupInfoService(DbExtConfig dbExtConfig) { + this.dbExtConfig = dbExtConfig; + } + + /** + * 检查数据库备份 + */ + @Override + public void executeTask() { + if (dbExtConfig.getMode() != DbExtConfig.Mode.H2) { + return; + } + try { + BaseServerController.resetInfo(UserModel.EMPTY); + // 创建备份 + this.createAutoBackup(); + // 删除历史备份 + this.deleteAutoBackup(); + } finally { + BaseServerController.removeEmpty(); + } + } + + /** + * 删除历史 自动备份信息 + */ + private void deleteAutoBackup() { + Integer autoBackupReserveDay = dbExtConfig.getAutoBackupReserveDay(); + if (autoBackupReserveDay != null && autoBackupReserveDay > 0) { + // + Entity entity = Entity.create(); + entity.set("backupType", 3); + entity.set("createTimeMillis", " < " + (SystemClock.now() - TimeUnit.DAYS.toMillis(autoBackupReserveDay))); + List entities = super.queryList(entity); + if (entities != null) { + for (Entity entity1 : entities) { + String id = entity1.getStr("id"); + this.delByKey(id); + } + } + } + } + + /** + * 创建自动备份数据 + */ + private void createAutoBackup() { + // 自动备份 + Integer autoBackupIntervalDay = dbExtConfig.getAutoBackupIntervalDay(); + if (autoBackupIntervalDay != null && autoBackupIntervalDay > 0) { + BackupInfoModel backupInfoModel = new BackupInfoModel(); + backupInfoModel.setBackupType(3); + List infoModels = super.queryList(backupInfoModel, 1, new Order("createTimeMillis", Direction.DESC)); + BackupInfoModel first = CollUtil.getFirst(infoModels); + if (first != null) { + Long createTimeMillis = first.getCreateTimeMillis(); + long interval = SystemClock.now() - createTimeMillis; + if (interval < TimeUnit.DAYS.toMillis(autoBackupIntervalDay)) { + return; + } + } + this.autoBackup(); + } + } + + /** + * 自动备份 + */ + public Future autoBackup() { + if (dbExtConfig.getMode() != DbExtConfig.Mode.H2) { + return null; + } + // 执行数据库备份 + return this.backupToSql(null, BackupTypeEnum.AUTO); + } + + /** + * 备份数据库 SQL 文件 + * + * @param tableNameList 需要备份的表名称列表,如果是全库备份,则不需要 + */ + public Future backupToSql(final List tableNameList) { + // 判断备份类型 + BackupTypeEnum backupType = BackupTypeEnum.ALL; + if (!CollectionUtils.isEmpty(tableNameList)) { + backupType = BackupTypeEnum.PART; + } + return this.backupToSql(tableNameList, backupType); + } + + /** + * 备份数据库 SQL 文件 + * + * @param tableNameList 需要备份的表名称列表,如果是全库备份,则不需要 + */ + private Future backupToSql(final List tableNameList, BackupTypeEnum backupType) { + final String fileName = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), DatePattern.PURE_DATETIME_PATTERN); + + // 设置默认备份 SQL 的文件地址 + File file = FileUtil.file(StorageServiceFactory.dbLocalPath(), DbExtConfig.BACKUP_DIRECTORY_NAME, fileName + DbExtConfig.SQL_FILE_SUFFIX); + final String backupSqlPath = FileUtil.getAbsolutePath(file); + + // 数据源参数 + final String url = StorageServiceFactory.get().dbUrl(); + + final String user = dbExtConfig.userName(); + final String pass = dbExtConfig.userPwd(); + + JpomManifest instance = JpomManifest.getInstance(); + // 先构造备份信息插入数据库 + BackupInfoModel backupInfoModel = new BackupInfoModel(); + String timeStamp = instance.getTimeStamp(); + try { + DateTime parse = DateUtil.parse(timeStamp); + backupInfoModel.setBaleTimeStamp(parse.getTime()); + } catch (Exception ignored) { + } + backupInfoModel.setName(fileName); + backupInfoModel.setVersion(instance.getVersion()); + backupInfoModel.setBackupType(backupType.getCode()); + backupInfoModel.setFilePath(backupSqlPath); + this.insert(backupInfoModel); + // 开启一个子线程去执行任务,任务完成之后修改对应的数据库备份信息 + return I18nThreadUtil.execAsync(() -> { + // 修改用的实体类 + BackupInfoModel backupInfo = new BackupInfoModel(); + backupInfo.setId(backupInfoModel.getId()); + try { + log.debug("启动一个新线程来执行 H2 数据库备份...启动"); + StorageServiceFactory.get().backupSql(url, user, pass, backupSqlPath, tableNameList); + // 修改备份任务执行完成 + backupInfo.setFileSize(FileUtil.size(file)); + backupInfo.setSha1Sum(SecureUtil.sha1(file)); + backupInfo.setStatus(BackupStatusEnum.SUCCESS.getCode()); + this.updateById(backupInfo); + log.debug("启动一个新线程来执行 H2 数据库备份...成功"); + } catch (Exception e) { + // 记录错误日志信息,修改备份任务执行失败 + log.error("备份 h2 数据库异常", e); + backupInfo.setStatus(BackupStatusEnum.FAILED.getCode()); + this.updateById(backupInfo); + } + return backupInfo; + }); + } + + /** + * 根据 SQL 文件还原数据库 + * 还原数据库时只能同步,防止该过程中修改数据造成数据不一致 + * + * @param backupSqlPath 备份 sql 文件地址 + */ + public boolean restoreWithSql(String backupSqlPath) { + try { + long startTs = System.currentTimeMillis(); + IPlugin plugin = PluginFactory.getPlugin("db-h2"); + Map map = new HashMap<>(10); + map.put("backupSqlPath", backupSqlPath); + plugin.execute("restoreBackupSql", map); + // h2BackupService.restoreBackupSql(backupSqlPath); + long endTs = System.currentTimeMillis(); + log.debug("restore H2 Database backup...success...cast {} ms", endTs - startTs); + return true; + } catch (Exception e) { + // 记录错误日志信息,返回数据库备份还原执行失败 + log.error("restore H2 Database backup...catch exception...message: {}", e.getMessage(), e); + return false; + } + } + + /** + * load table name list from h2 database + * + * @return list + */ + public List h2TableNameList() { + String sql = "show tables;"; + List list = super.query(sql); + // 筛选字段 + return list.stream() + .filter(entity -> StringUtils.hasLength(String.valueOf(entity.get(ServerConst.TABLE_NAME)))) + .flatMap(entity -> Stream.of(String.valueOf(entity.get(ServerConst.TABLE_NAME)))) + .distinct() + .collect(Collectors.toList()); + } + + @Override + public int delByKey(String keyValue) { + // 根据 id 查询备份信息 + BackupInfoModel backupInfoModel = super.getByKey(keyValue); + Objects.requireNonNull(backupInfoModel, I18nMessageUtil.get("i18n.backup_data_not_exist.f88c")); + + // 删除对应的文件 + boolean del = FileUtil.del(backupInfoModel.getFilePath()); + Assert.state(del, I18nMessageUtil.get("i18n.delete_backup_data_file_failure.2ebf")); + + // 删除备份信息 + return super.delByKey(keyValue); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/BuildInfoService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/BuildInfoService.java new file mode 100644 index 0000000000..20eddfa077 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/BuildInfoService.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.db.Entity; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.cron.ICron; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.build.BuildExecuteService; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.enums.BuildReleaseMethod; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.util.StringUtil; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 构建 service 新版本,数据从数据库里面加载 + * + * @author Hotstrip + * @since 2021-08-10 + **/ +@Service +@Slf4j +public class BuildInfoService extends BaseWorkspaceService implements ICron, IStatusRecover, ITriggerToken { + + /** + * 更新状态 + * + * @param id ID + * @param buildStatus to Status + */ + public void updateStatus(String id, BuildStatus buildStatus, String desc) { + BuildInfoModel buildInfoModel = new BuildInfoModel(); + buildInfoModel.setId(id); + buildInfoModel.setStatusMsg(desc); + buildInfoModel.setStatus(buildStatus.getCode()); + this.updateById(buildInfoModel); + } + + /** + * 更新状态 + * + * @param id ID + * @param buildNumberId 构建编号id + * @param buildStatus to Status + */ + public void updateStatus(String id, int buildNumberId, BuildStatus buildStatus, String msg) { + + BuildInfoModel buildInfoModel = new BuildInfoModel(); + buildInfoModel.setId(id); + buildInfoModel.setBuildId(buildNumberId); + Entity where = this.dataBeanToEntity(buildInfoModel); + // + BuildInfoModel dataModel = new BuildInfoModel(); + dataModel.setStatus(buildStatus.getCode()); + dataModel.setStatusMsg(msg); + Entity data = this.dataBeanToEntity(dataModel); + this.update(data, where); + } + + @Override + public int insert(BuildInfoModel buildInfoModel) { + int count = super.insert(buildInfoModel); + this.checkCron(buildInfoModel); + return count; + } + + @Override + public int updateById(BuildInfoModel info, HttpServletRequest request) { + int update = super.updateById(info, request); + if (update > 0) { + this.checkCron(info); + } + return update; + } + + @Override + public int delByKey(String keyValue, HttpServletRequest request) { + int delByKey = super.delByKey(keyValue, request); + if (delByKey > 0) { + String taskId = "build:" + delByKey; + CronUtils.remove(taskId); + } + return delByKey; + } + + /** + * 开启定时构建任务 + */ + @Override + public List queryStartingList() { + String sql = "select * from " + super.getTableName() + " where autoBuildCron is not null and autoBuildCron <> ''"; + return super.queryList(sql); + } + + @Override + public int statusRecover() { + // 恢复异常数据 + String updateSql = "update " + super.getTableName() + " set status=? where status=? or status=? or status=?"; + return super.execute(updateSql, BuildStatus.AbnormalShutdown.getCode(), BuildStatus.Ing.getCode(), BuildStatus.PubIng.getCode(), BuildStatus.WaitExec.getCode()); + } + + /** + * 检查定时任务 状态 + * + * @param buildInfoModel 构建信息 + */ + @Override + public boolean checkCron(BuildInfoModel buildInfoModel) { + String id = buildInfoModel.getId(); + String taskId = "build:" + id; + String autoBuildCron = buildInfoModel.getAutoBuildCron(); + autoBuildCron = StringUtil.parseCron(autoBuildCron); + if (StrUtil.isEmpty(autoBuildCron)) { + CronUtils.remove(taskId); + return false; + } + log.debug("start build cron {} {} {}", id, buildInfoModel.getName(), autoBuildCron); + CronUtils.upsert(taskId, autoBuildCron, new CronTask(id, autoBuildCron)); + return true; + } + + @Override + public String typeName() { + return getTableName(); + } + + + public List hasResultKeep() { + // + String sql = "select * from " + super.getTableName() + " where resultKeepDay>0"; + return super.queryList(sql); + } + + private static class CronTask implements Task { + + private final String buildId; + private final String autoBuildCron; + + public CronTask(String buildId, String autoBuildCron) { + this.buildId = buildId; + this.autoBuildCron = autoBuildCron; + } + + @Override + public void execute() { + BuildExecuteService buildExecuteService = SpringUtil.getBean(BuildExecuteService.class); + try { + BaseServerController.resetInfo(UserModel.EMPTY); + buildExecuteService.start(this.buildId, null, null, 2, "auto build:" + this.autoBuildCron); + } finally { + BaseServerController.removeEmpty(); + } + } + } + + + /** + * 判断是否存在 节点关联 + * + * @param nodeId 节点ID + * @return true 关联 + */ + public boolean checkNode(String nodeId, HttpServletRequest request) { + Entity entity = new Entity(); + entity.set("releaseMethod", BuildReleaseMethod.Project.getCode()); + String workspaceId = this.getCheckUserWorkspace(request); + entity.set("workspaceId", workspaceId); + entity.set("releaseMethodDataId", StrUtil.format(" like '{}:%'", nodeId)); + return super.exists(entity); + } + + /** + * 判断是否存在 发布关联 + * + * @param dataId 数据ID + * @param releaseMethod 发布方法 + * @param request 请求对象 + * @return true 关联 + */ + public boolean checkReleaseMethodByLike(String dataId, HttpServletRequest request, BuildReleaseMethod releaseMethod) { + Entity entity = new Entity(); + entity.set("releaseMethod", releaseMethod.getCode()); + String workspaceId = this.getCheckUserWorkspace(request); + entity.set("workspaceId", workspaceId); + entity.set("releaseMethodDataId", StrUtil.format(" like '%{}%'", dataId)); + return super.exists(entity); + } + + /** + * 判断是否存在 发布关联 + * + * @param dataId 数据ID + * @param releaseMethod 发布方法 + * @return true 关联 + */ + public boolean checkReleaseMethodByLike(String dataId, BuildReleaseMethod releaseMethod) { + Entity entity = new Entity(); + entity.set("releaseMethod", releaseMethod.getCode()); + entity.set("releaseMethodDataId", StrUtil.format(" like '%{}%'", dataId)); + return super.exists(entity); + } + + /** + * 判断是否存在 发布关联 + * + * @param dataId 数据ID + * @param request 请求对象 + * @param releaseMethod 发布方法 + * @return true 关联 + */ + public boolean checkReleaseMethod(String dataId, HttpServletRequest request, BuildReleaseMethod releaseMethod) { + BuildInfoModel buildInfoModel = new BuildInfoModel(); + String workspaceId = this.getCheckUserWorkspace(request); + buildInfoModel.setWorkspaceId(workspaceId); + buildInfoModel.setReleaseMethodDataId(dataId); + buildInfoModel.setReleaseMethod(releaseMethod.getCode()); + return super.exists(buildInfoModel); + } + + /** + * 查询发布关联的构建 + * + * @param dataId 数据ID + * @param request 请求对象 + * @param releaseMethod 发布方法 + * @return list + */ + public List listReleaseMethod(String dataId, HttpServletRequest request, BuildReleaseMethod releaseMethod) { + BuildInfoModel buildInfoModel = new BuildInfoModel(); + String workspaceId = this.getCheckUserWorkspace(request); + buildInfoModel.setWorkspaceId(workspaceId); + buildInfoModel.setReleaseMethodDataId(dataId); + buildInfoModel.setReleaseMethod(releaseMethod.getCode()); + return super.listByBean(buildInfoModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbBuildHistoryLogService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbBuildHistoryLogService.java new file mode 100644 index 0000000000..96fd005ac2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbBuildHistoryLogService.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import cn.hutool.db.sql.Direction; +import cn.hutool.db.sql.Order; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.event.ISystemTask; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.build.BuildExtraModule; +import org.dromara.jpom.build.BuildUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +import org.dromara.jpom.model.BaseDbModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.BuildInfoModel; +import org.dromara.jpom.model.enums.BuildStatus; +import org.dromara.jpom.model.log.BuildHistoryLog; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * 构建历史db + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@Service +@Slf4j +public class DbBuildHistoryLogService extends BaseWorkspaceService implements ISystemTask { + + private final BuildInfoService buildService; + private final BuildExtConfig buildExtConfig; + + public DbBuildHistoryLogService(BuildInfoService buildService, + BuildExtConfig buildExtConfig) { + this.buildService = buildService; + this.buildExtConfig = buildExtConfig; + } + + /** + * 更新状态 + * + * @param logId 记录id + * @param resultDirFile 构建产物目录 + */ + public void updateResultDirFile(String logId, String resultDirFile) { + if (logId == null || StrUtil.isEmpty(resultDirFile)) { + return; + } + + BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); + buildHistoryLog.setId(logId); + buildHistoryLog.setResultDirFile(resultDirFile); + this.updateById(buildHistoryLog); + } + + + /** + * 清理文件并删除记录 + * + * @param buildHistoryLog 构建记录 + * @return json + */ + public IJsonMessage deleteLogAndFile(BuildHistoryLog buildHistoryLog) { + if (buildHistoryLog == null) { + return JsonMessage.success(I18nMessageUtil.get("i18n.no_corresponding_build_record_ignore_deletion.86a0")); + } + BuildInfoModel item = buildService.getByKey(buildHistoryLog.getBuildDataId()); + if (item != null) { + File logFile = BuildUtil.getLogFile(item.getId(), buildHistoryLog.getBuildNumberId()); + if (logFile != null) { + File dataFile = logFile.getParentFile(); + if (dataFile.exists()) { + boolean s = FileUtil.del(dataFile); + if (!s) { + return new JsonMessage<>(500, I18nMessageUtil.get("i18n.file_cleanup_failed.511e")); + } + } + } + } + int count = this.delByKey(buildHistoryLog.getId()); + return new JsonMessage<>(200, I18nMessageUtil.get("i18n.delete_success.0007"), String.valueOf(count)); + } + + @Override + protected void fillSelectResult(BuildHistoryLog data) { + super.fillSelectResult(data); + Optional.ofNullable(data).ifPresent(buildHistoryLog -> { + // 不能返回环境变量的信息(存在隐私字段) + buildHistoryLog.setBuildEnvCache(null); + }); + + } + + @Override + public int insert(BuildHistoryLog buildHistoryLog) { + int count = super.insert(buildHistoryLog); + // 清理单个 + BuildExtraModule build = BuildExtraModule.build(buildHistoryLog); + int resultKeepCount = ObjectUtil.defaultIfNull(build.getResultKeepCount(), 0); + int buildItemMaxHistoryCount = buildExtConfig.getItemMaxHistoryCount(); + if (resultKeepCount > 0 || buildItemMaxHistoryCount > 0) { + // 至少有一个配置 + int useCount; + if (resultKeepCount > 0 && buildItemMaxHistoryCount > 0) { + // 都配置过,使用最小值 + useCount = Math.min(resultKeepCount, buildItemMaxHistoryCount); + } else { + // 只配置了一处,使用最大值 + useCount = Math.max(resultKeepCount, buildItemMaxHistoryCount); + } + super.autoLoopClear("startTime", useCount, entity -> this.fillClearWhere(entity, buildHistoryLog.getBuildDataId()), this.predicate()); + } + return count; + } + + private Predicate predicate() { + return buildHistoryLog1 -> { + IJsonMessage jsonMessage = this.deleteLogAndFile(buildHistoryLog1); + if (!jsonMessage.success()) { + log.warn("{} {} {}", buildHistoryLog1.getBuildName(), buildHistoryLog1.getBuildNumberId(), jsonMessage); + return false; + } + return true; + }; + } + + private void fillClearWhere(Entity entity, String buildDataId) { + entity.set("buildDataId", buildDataId); + // 清理单项构建历史保留个数只判断(构建结束、发布中、发布失败、发布失败)有效构建状态,避免无法保留有效构建历史 + entity.set("status", CollUtil.newArrayList( + BuildStatus.Success.getCode(), + BuildStatus.PubIng.getCode(), + BuildStatus.PubSuccess.getCode(), + BuildStatus.PubError.getCode())); + } + + @Override + protected void executeClearImpl(int count) { + // 清理总数据 + int buildMaxHistoryCount = buildExtConfig.getMaxHistoryCount(); + int saveCount = Math.min(count, buildMaxHistoryCount); + if (saveCount <= 0) { + // 不清除 + return; + } + super.autoLoopClear("startTime", saveCount, + null, + buildHistoryLog1 -> { + IJsonMessage jsonMessage = this.deleteLogAndFile(buildHistoryLog1); + if (!jsonMessage.success()) { + log.warn("{} {} {}", buildHistoryLog1.getBuildName(), buildHistoryLog1.getBuildNumberId(), jsonMessage); + return false; + } + return true; + }); + } + + @Override + protected String[] clearTimeColumns() { + return super.clearTimeColumns(); + } + + @Override + public void executeTask() { + List buildInfoModels = buildService.hasResultKeep(); + if (CollUtil.isEmpty(buildInfoModels)) { + return; + } + for (BuildInfoModel buildInfoModel : buildInfoModels) { + Integer resultKeepDay = buildInfoModel.getResultKeepDay(); + if (resultKeepDay == null || resultKeepDay <= 0) { + continue; + } + log.debug(I18nMessageUtil.get("i18n.auto_delete_expired_build_history_files.723b"), buildInfoModel.getName(), resultKeepDay); + Entity entity = Entity.create(); + this.fillClearWhere(entity, buildInfoModel.getId()); + DateTime date = DateTime.now(); + date = DateUtil.offsetDay(date, -resultKeepDay); + date = DateUtil.beginOfDay(date); + entity.set("startTime", "< " + date.getTime()); + while (true) { + Page page = new Page(1, 50); + page.addOrder(new Order("startTime", Direction.DESC)); + PageResultDto pageResult = this.listPage(entity, page); + if (pageResult.isEmpty()) { + break; + } + List ids = pageResult.getResult() + .stream() + .filter(this.predicate()) + .map(BaseDbModel::getId) + .collect(Collectors.toList()); + // + this.delByKey(ids, null); + } + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbMonitorNotifyLogService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbMonitorNotifyLogService.java new file mode 100644 index 0000000000..add9089687 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbMonitorNotifyLogService.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.model.log.MonitorNotifyLog; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +/** + * 监控消息 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@Service +public class DbMonitorNotifyLogService extends BaseWorkspaceService { + + + @Override + public int insert(MonitorNotifyLog monitorNotifyLog) { + try { + BaseServerController.resetInfo(UserModel.EMPTY); + // + return super.insert(monitorNotifyLog); + // + } finally { + BaseServerController.removeEmpty(); + } + } + + @Override + protected String[] clearTimeColumns() { + return new String[]{"createTime", "createTimeMillis"}; + } + + /** + * 修改执行结果 + * + * @param logId 通知id + * @param status 状态 + * @param errorMsg 错误消息 + */ + public void updateStatus(String logId, boolean status, String errorMsg) { + MonitorNotifyLog monitorNotifyLog = new MonitorNotifyLog(); + monitorNotifyLog.setId(logId); + monitorNotifyLog.setNotifyStatus(status); + monitorNotifyLog.setNotifyError(errorMsg); + super.updateById(monitorNotifyLog); +// Entity entity = new Entity(); +// entity.set("notifyStatus", status); +// if (errorMsg != null) { +// entity.set("notifyError", errorMsg); +// } +// // +// Entity where = new Entity(); +// where.set("logId", logId); +// super.update(entity, where); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbUserOperateLogService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbUserOperateLogService.java new file mode 100644 index 0000000000..4a8f4427f0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/DbUserOperateLogService.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import cn.hutool.core.bean.BeanPath; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.MonitorModel; +import org.dromara.jpom.model.data.MonitorUserOptModel; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.log.UserOperateLogV1; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.monitor.NotifyUtil; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.service.monitor.MonitorUserOptService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.system.init.OperateLogController; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 操作日志 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@Service +@Slf4j +public class DbUserOperateLogService extends BaseWorkspaceService { + + private final MonitorUserOptService monitorUserOptService; + private final UserService userService; + private final WorkspaceService workspaceService; + /** + * 通用 bean 的名称字段 bean-path + */ + private static final BeanPath[] NAME_BEAN_PATHS = new BeanPath[]{BeanPath.create("name"), BeanPath.create("title")}; + + public DbUserOperateLogService(MonitorUserOptService monitorUserOptService, + UserService userService, + WorkspaceService workspaceService) { + this.monitorUserOptService = monitorUserOptService; + this.userService = userService; + this.workspaceService = workspaceService; + } + + /** + * 查询指定用户的操作日志 + * + * @param request 请求信息 + * @param userId 用户id + * @return page + */ + public PageResultDto listPageByUserId(HttpServletRequest request, String userId) { + Map paramMap = ServletUtil.getParamMap(request); + paramMap.put("userId", userId); + return super.listPage(paramMap); + } + + /** + * 根据 数据ID 和 节点ID 查询相关数据名称 + * + * @param classFeature 功能 + * @param cacheInfo 操作缓存 + * @param userOperateLogV1 操作日志 + * @return map + */ + private Map buildDataMsg(ClassFeature classFeature, OperateLogController.CacheInfo cacheInfo, UserOperateLogV1 userOperateLogV1) { + Map optDataNameMap = cacheInfo.getOptDataNameMap(); + if (optDataNameMap != null) { + return optDataNameMap; + } + return this.buildDataMsg(classFeature, userOperateLogV1.getDataId(), userOperateLogV1.getNodeId()); + } + + /** + * 根据 数据ID 和 节点ID 查询相关数据名称 + * + * @param classFeature 功能 + * @param dataId 数据ID + * @param nodeId 节点ID + * @return map + */ + public Map buildDataMsg(ClassFeature classFeature, String dataId, String nodeId) { + if (classFeature == null) { + return null; + } + Class> dbService = classFeature.getDbService(); + if (dbService == null) { + return null; + } + Map map = new LinkedHashMap<>(); + map.put(I18nMessageUtil.get("i18n.data_id_label.81b6"), dataId); + BaseDbService baseDbCommonService = SpringUtil.getBean(dbService); + Object data = baseDbCommonService.getData(nodeId, dataId); + map.put(I18nMessageUtil.get("i18n.data_name_label.5a14"), this.tryGetBeanName(data)); + // + map.put(I18nMessageUtil.get("i18n.node_id.c90a"), nodeId); + ClassFeature parent = classFeature.getParent(); + if (parent == ClassFeature.NODE) { + Class> dbServiceParent = parent.getDbService(); + BaseDbService baseDbCommonServiceParent = SpringUtil.getBean(dbServiceParent); + Object dataParent = baseDbCommonServiceParent.getData(nodeId, dataId); + map.put(I18nMessageUtil.get("i18n.node_name.b178"), this.tryGetBeanName(dataParent)); + } + return map; + } + + private Object tryGetBeanName(Object data) { + for (BeanPath beanPath : NAME_BEAN_PATHS) { + Object o = beanPath.get(data); + if (o != null) { + return o; + } + } + return null; + } + + private String buildContent(UserModel optUserItem, Map dataMap, WorkspaceModel workspaceModel, String optTypeMsg, UserOperateLogV1 userOperateLogV1) { + Map map = new LinkedHashMap<>(10); + map.put(I18nMessageUtil.get("i18n.operation_user.4c89"), optUserItem.getName()); + map.put(I18nMessageUtil.get("i18n.operation_status_code.8231"), userOperateLogV1.getOptStatus()); + map.put(I18nMessageUtil.get("i18n.operation_type.de9c"), optTypeMsg); + if (workspaceModel != null) { + map.put(I18nMessageUtil.get("i18n.associated_workspace.885b"), workspaceModel.getName()); + } + map.put(I18nMessageUtil.get("i18n.operation_ip.cbd4"), userOperateLogV1.getIp()); + map.put(I18nMessageUtil.get("i18n.operation_time.7e95"), DateTime.now().toString()); + if (dataMap != null) { + map.putAll(dataMap); + } + List list = map.entrySet() + .stream() + .filter(entry -> entry.getValue() != null) + .map(entry -> entry.getKey() + ":" + entry.getValue()) + .collect(Collectors.toList()); + // + return CollUtil.join(list, StrUtil.LF); + } + + /** + * 判断当前操作是否需要报警 + * + * @param userOperateLogV1 操作信息 + * @param cacheInfo 操作缓存相关 + * @return 解析后的相关数据 + */ + private Map checkMonitor(UserOperateLogV1 userOperateLogV1, OperateLogController.CacheInfo cacheInfo) { + ClassFeature classFeature = EnumUtil.fromString(ClassFeature.class, userOperateLogV1.getClassFeature(), null); + MethodFeature methodFeature = EnumUtil.fromString(MethodFeature.class, userOperateLogV1.getMethodFeature(), null); + UserModel optUserItem = userService.getByKey(userOperateLogV1.getUserId()); + if (classFeature == null || methodFeature == null || optUserItem == null) { + return null; + } + Map dataMap = this.buildDataMsg(classFeature, cacheInfo, userOperateLogV1); + WorkspaceModel workspaceModel = workspaceService.getByKey(userOperateLogV1.getWorkspaceId()); + + String optTypeMsg = StrUtil.format(" 【{}】->【{}】", I18nMessageUtil.get(classFeature.getName().get()), I18nMessageUtil.get(methodFeature.getName().get())); + List monitorUserOptModels = monitorUserOptService.listByType(userOperateLogV1.getWorkspaceId(), + classFeature, + methodFeature, + userOperateLogV1.getUserId()); + if (CollUtil.isEmpty(monitorUserOptModels)) { + return dataMap; + } + String context = this.buildContent(optUserItem, dataMap, workspaceModel, optTypeMsg, userOperateLogV1); + for (MonitorUserOptModel monitorUserOptModel : monitorUserOptModels) { + List notifyUser = monitorUserOptModel.notifyUser(); + if (CollUtil.isEmpty(notifyUser)) { + continue; + } + for (String userId : notifyUser) { + UserModel item = userService.getByKey(userId); + if (item == null) { + continue; + } + // 邮箱 + String email = item.getEmail(); + if (StrUtil.isNotEmpty(email)) { + MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.mail, email); + I18nThreadUtil.execute(() -> { + try { + NotifyUtil.send(notify1, I18nMessageUtil.get("i18n.user_operation_alarm.15b9"), context); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_alert_error.cd38"), e); + } + }); + + } + // dingding + String dingDing = item.getDingDing(); + if (StrUtil.isNotEmpty(dingDing)) { + MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.dingding, dingDing); + I18nThreadUtil.execute(() -> { + try { + NotifyUtil.send(notify1, I18nMessageUtil.get("i18n.user_operation_alarm.15b9"), context); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_alert_error.cd38"), e); + } + }); + } + // 企业微信 + String workWx = item.getWorkWx(); + if (StrUtil.isNotEmpty(workWx)) { + MonitorModel.Notify notify1 = new MonitorModel.Notify(MonitorModel.NotifyType.workWx, workWx); + I18nThreadUtil.execute(() -> { + try { + NotifyUtil.send(notify1, I18nMessageUtil.get("i18n.user_operation_alarm.15b9"), context); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_alert_error.cd38"), e); + } + }); + } + } + } + return dataMap; + } + + /** + * 插入操作日志 + * + * @param userOperateLogV1 日志信息 + * @param cacheInfo 当前操作相关信息 + */ + public void insert(UserOperateLogV1 userOperateLogV1, OperateLogController.CacheInfo cacheInfo) { + super.insert(userOperateLogV1); + I18nThreadUtil.execute(() -> { + // 更新用户名和工作空间名 + try { + UserOperateLogV1 update = new UserOperateLogV1(); + update.setId(userOperateLogV1.getId()); + UserModel userModel = userService.getByKey(userOperateLogV1.getUserId()); + Optional.ofNullable(userModel).ifPresent(userModel1 -> update.setUsername(userModel1.getName())); + WorkspaceModel workspaceModel = workspaceService.getByKey(userOperateLogV1.getWorkspaceId()); + Optional.ofNullable(workspaceModel).ifPresent(workspaceModel1 -> update.setWorkspaceName(workspaceModel1.getName())); + this.updateById(update); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.update_operation_log_failed.d348"), e); + } + // 检查操作监控 + try { + Map monitor = this.checkMonitor(userOperateLogV1, cacheInfo); + if (monitor != null) { + String dataName = Optional.ofNullable(monitor.get(I18nMessageUtil.get("i18n.data_name_label.5a14"))).map(StrUtil::toStringOrNull).orElse(StrUtil.DASHED); + UserOperateLogV1 userOperateLogV11 = new UserOperateLogV1(); + userOperateLogV11.setDataName(dataName); + userOperateLogV11.setId(userOperateLogV1.getId()); + super.updateById(userOperateLogV11); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.operation_monitoring_error.8036"), e); + } + }); + } + + @Override + public PageResultDto listPage(HttpServletRequest request) { + // 验证工作空间权限 + Map paramMap = ServletUtil.getParamMap(request); + //String workspaceId = this.getCheckUserWorkspace(request); + //paramMap.put("workspaceId:in", workspaceId + StrUtil.COMMA + StrUtil.EMPTY); + return super.listPage(paramMap); + } + + @Override + public String getCheckUserWorkspace(HttpServletRequest request) { + // 忽略检查 + return BaseWorkspaceService.getWorkspaceId(request); + // String header = ServletUtil.getHeader(request, Const.WORKSPACE_ID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); + // return ObjectUtil.defaultIfNull(header, StrUtil.EMPTY); + } + + @Override + protected void checkUserWorkspace(String workspaceId, UserModel userModel) { + // 忽略检查 + } + + @Override + protected String[] clearTimeColumns() { + return new String[]{"optTime", "createTimeMillis"}; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/RepositoryService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/RepositoryService.java new file mode 100644 index 0000000000..4438f72120 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/RepositoryService.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.model.data.RepositoryModel; +import org.dromara.jpom.service.h2db.BaseGlobalOrWorkspaceService; +import org.springframework.stereotype.Service; + +/** + * @author Hotstrip + * Repository service + */ +@Service +public class RepositoryService extends BaseGlobalOrWorkspaceService { + + @Override + protected void fillSelectResult(RepositoryModel repositoryModel) { + if (repositoryModel == null) { + return; + } + if (!StrUtil.startWithIgnoreCase(repositoryModel.getPassword(), ServerConst.REF_WORKSPACE_ENV)) { + // 隐藏密码字段 + repositoryModel.setPassword(null); + } + repositoryModel.setRsaPrv(null); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/dblog/SshTerminalExecuteLogService.java b/modules/server/src/main/java/org/dromara/jpom/service/dblog/SshTerminalExecuteLogService.java new file mode 100644 index 0000000000..878dbd06fb --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/dblog/SshTerminalExecuteLogService.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.dblog; + +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.model.log.SshTerminalExecuteLog; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + + +/** + * ssh 终端执行日志 + * + * @author bwcx_jzy + * @since 2021/08/04 + */ +@Service +public class SshTerminalExecuteLogService extends BaseWorkspaceService { + + @Override + protected String[] clearTimeColumns() { + return new String[]{"createTimeMillis"}; + } + + /** + * 批量记录日志 + * + * @param userInfo 操作的用户 + * @param sshItem ssh 对象 + * @param machineSshModel 资产 + * @param ip 操作人的ip + * @param userAgent 浏览器标识 + * @param commands 命令行 + * @param refuse 是否拒绝执行 + */ + public void batch(UserModel userInfo, MachineSshModel machineSshModel, SshModel sshItem, String ip, String userAgent, boolean refuse, List commands) { + if (machineSshModel == null) { + // 资产信息不能为空 + return; + } + long optTime = SystemClock.now(); + try { + BaseServerController.resetInfo(userInfo); + List executeLogs = commands.stream() + .filter(StrUtil::isNotEmpty) + .map(s -> { + SshTerminalExecuteLog sshTerminalExecuteLog = new SshTerminalExecuteLog(); + if (sshItem != null) { + sshTerminalExecuteLog.setSshId(sshItem.getId()); + sshTerminalExecuteLog.setSshName(sshItem.getName()); + sshTerminalExecuteLog.setWorkspaceId(sshItem.getWorkspaceId()); + } else { + sshTerminalExecuteLog.setWorkspaceId(ServerConst.WORKSPACE_GLOBAL); + } + sshTerminalExecuteLog.setMachineSshId(machineSshModel.getId()); + sshTerminalExecuteLog.setMachineSshName(machineSshModel.getName()); + sshTerminalExecuteLog.setCommands(s); + sshTerminalExecuteLog.setRefuse(refuse); + sshTerminalExecuteLog.setCreateTimeMillis(optTime); + sshTerminalExecuteLog.setIp(ip); + sshTerminalExecuteLog.setUserAgent(userAgent); + //sshTerminalExecuteLog.setUserId(UserModel.getOptUserName(userInfo)); + return sshTerminalExecuteLog; + }).collect(Collectors.toList()); + super.insert(executeLogs); + } finally { + BaseServerController.removeAll(); + } + } +} 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 new file mode 100644 index 0000000000..96c576be3e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerInfoService.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.docker.DockerInfoModel; +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; + + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +@Service +@Slf4j +public class DockerInfoService extends BaseWorkspaceService { + + + public static final String DOCKER_CHECK_PLUGIN_NAME = "docker-cli:check"; + + public static final String DOCKER_PLUGIN_NAME = "docker-cli"; + + @Override + protected void fillSelectResult(DockerInfoModel data) { + //data.setRegistryPassword(null); + } + + @Override + protected void fillInsert(DockerInfoModel dockerInfoModel) { + super.fillInsert(dockerInfoModel); + } + + /** + * 根据 tag 查询 容器 + * + * @param workspaceId 工作空间 + * @param tag tag + * @return list + */ + public List queryByTag(String workspaceId, String tag) { + Condition workspaceIdCondition = new Condition("workspaceId", workspaceId); + if (StrUtil.isEmpty(tag)) { + return super.findByCondition(workspaceIdCondition); + } else { + Condition tagCondition = new Condition(" instr(tags,'" + StrUtil.wrap(tag, StrUtil.COLON) + "')", ""); + tagCondition.setPlaceHolder(false); + tagCondition.setOperator(""); + return super.findByCondition(workspaceIdCondition, tagCondition); + } + } + + /** + * 根据 tag 查询 容器 + * + * @param workspaceId 工作空间 + * @param tag tag + * @return count + */ + public int countByTag(String workspaceId, String tag) { + String sql = StrUtil.format("SELECT * FROM {} where workspaceId=? and instr(tags,?)", super.getTableName()); + 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()); + } + + /** + * 将节点信息同步到其他工作空间 + * + * @param ids 多给节点ID + * @param nowWorkspaceId 当前的工作空间ID + * @param workspaceId 同步到哪个工作空间 + */ + public void syncToWorkspace(String ids, String nowWorkspaceId, String workspaceId) { + StrUtil.splitTrim(ids, StrUtil.COMMA).forEach(id -> { + DockerInfoModel data = super.getByKey(id, false, entity -> entity.set("workspaceId", nowWorkspaceId)); + Assert.notNull(data, I18nMessageUtil.get("i18n.no_docker_details.3343")); + // + DockerInfoModel where = new DockerInfoModel(); + where.setWorkspaceId(workspaceId); + where.setMachineDockerId(data.getMachineDockerId()); + DockerInfoModel exits = super.queryByBean(where); + Assert.isNull(exits, I18nMessageUtil.get("i18n.docker_already_exists_in_workspace.a0de")); + // 不存在则添加节点 + data.setId(null); + data.setWorkspaceId(workspaceId); + data.setCreateTimeMillis(null); + data.setModifyTimeMillis(null); + data.setModifyUser(null); + // 集群 不同步 + data.setSwarmId(null); + data.setSwarmNodeId(null); + this.insert(data); + }); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerSwarmInfoService.java b/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerSwarmInfoService.java new file mode 100644 index 0000000000..8302aa40ab --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerSwarmInfoService.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.docker; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.model.docker.DockerSwarmInfoMode; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +/** + * @author bwcx_jzy + * @since 2022/2/13 + */ +@Service +@Slf4j +public class DockerSwarmInfoService extends BaseWorkspaceService { + + public static final String DOCKER_PLUGIN_NAME = "docker-cli:swarm"; +} 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 new file mode 100644 index 0000000000..64f852f36c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseDbService.java @@ -0,0 +1,851 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.h2db; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.*; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +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.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.BaseDbCommonService; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.dialect.DialectUtil; +import org.dromara.jpom.model.BaseDbModel; +import org.dromara.jpom.model.BaseUserModifyDbModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.UserModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 数据库操作 通用 serve + * + * @author bwcx_jzy + * @since 2021/8/13 + */ +@Slf4j +public abstract class BaseDbService extends BaseDbCommonService { + + @Autowired + @Lazy + private DbExtConfig extConfig; + /** + * 旧版本分组 + */ + private final boolean canGroup; + /** + * 新版本分组字段 + */ + private final boolean canGroupName; + /** + * 默认排序规则 + */ + private static final Order[] DEFAULT_ORDERS = new Order[]{ + new Order("createTimeMillis", Direction.DESC), + new Order("modifyTimeMillis", Direction.DESC), + new Order("id", Direction.DESC) + }; + + public BaseDbService() { + super(); + this.canGroup = ReflectUtil.hasField(this.tClass, "group"); + this.canGroupName = ReflectUtil.hasField(this.tClass, "groupName"); + } + + public boolean isCanGroup() { + return canGroup; + } + + /** + * load date group by group name + * + * @return list + */ + public List listGroup() { + String group = DialectUtil.wrapField("group"); + String sql = String.format("select %s from %s group by %s", group, getTableName(), group); + return this.listGroupByName(sql, "group"); + } + + + /** + * load date group by group name + * + * @return list + */ + public List listGroupName() { + String sql = "select groupName from " + this.getTableName() + " group by groupName"; + return this.listGroupByName(sql, "groupName"); + } + + /** + * 获取分组字段 + * + * @param sql sql 预计 + * @param params 参数 + * @return list + */ + public List listGroupByName(String sql, String fieldName, Object... params) { + Assert.state(this.canGroup || this.canGroupName, I18nMessageUtil.get("i18n.data_table_not_supported_for_grouping.6678")); + List list = super.query(sql, params); + String unWrapField = DialectUtil.unWrapField(fieldName); + // 筛选字段 + return list.stream() + .flatMap(entity -> { + Object obj = entity.get(unWrapField); + if (obj == null) { + return null; + } + return Stream.of(String.valueOf(obj)); + }) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + } + + + /** + * 恢复字段 + */ + public void repairGroupFiled() { + Assert.state(this.canGroup, I18nMessageUtil.get("i18n.data_table_not_supported_for_grouping.6678")); + String group = DialectUtil.wrapField("group"); + String sql = String.format("update %s set %s =? where %s is null or %s = ''", getTableName(), group, group, group); + super.execute(sql, Const.DEFAULT_GROUP_NAME.get()); + } + + public int insert(T t) { + this.fillInsert(t); + int count = super.insertDb(t); + this.executeClear(); + return count; + } + + /** + * 先尝试 更新,更新失败插入 + * + * @param t 数据 + */ + public void upsert(T t) { + int update = this.updateById(t); + if (update <= 0) { + this.insert(t); + } + } + + public void insert(Collection t) { + // def create time + t.forEach(this::fillInsert); + super.insertDb(t); + this.executeClear(); + } + + /** + * 插入数据填充 + * + * @param t 数据 + */ + protected void fillInsert(T t) { + // def create time + t.setCreateTimeMillis(ObjectUtil.defaultIfNull(t.getCreateTimeMillis(), SystemClock.now())); + t.setId(StrUtil.emptyToDefault(t.getId(), IdUtil.fastSimpleUUID())); + if (t instanceof BaseUserModifyDbModel) { + UserModel userModel = BaseServerController.getUserModel(); + userModel = userModel == null ? BaseServerController.getUserByThreadLocal() : userModel; + // 获取数据修改人 + BaseUserModifyDbModel modifyDbModel = (BaseUserModifyDbModel) t; + if (userModel != null) { + modifyDbModel.setModifyUser(ObjectUtil.defaultIfNull(modifyDbModel.getModifyUser(), userModel.getId())); + modifyDbModel.setCreateUser(ObjectUtil.defaultIfNull(modifyDbModel.getCreateUser(), userModel.getId())); + } + } + } + + /** + * update by id with data + * + * @param info data + * @param whereConsumer 查询条件回调 + * @return 影响的行数 + */ + public int updateById(T info, Consumer whereConsumer) { + // check id + String id = info.getId(); + Assert.hasText(id, I18nMessageUtil.get("i18n.cannot_execute_error.4c29")); + // def modify time + info.setModifyTimeMillis(ObjectUtil.defaultIfNull(info.getModifyTimeMillis(), SystemClock.now())); + + // fill modify user + if (info instanceof BaseUserModifyDbModel) { + BaseUserModifyDbModel modifyDbModel = (BaseUserModifyDbModel) info; + UserModel userModel = BaseServerController.getUserModel(); + if (userModel != null) { + modifyDbModel.setModifyUser(ObjectUtil.defaultIfNull(modifyDbModel.getModifyUser(), userModel.getId())); + } + } + // + Entity entity = this.dataBeanToEntity(info); + // + this.removeUpdate(entity); + // + Entity where = new Entity(); + where.set(ID_STR, id); + if (whereConsumer != null) { + whereConsumer.accept(where); + } + return super.updateDb(entity, where); + } + + private void removeUpdate(Entity entity) { + for (String s : new String[]{ID_STR, "createTimeMillis", "createUser"}) { + entity.remove(DialectUtil.wrapField(s)); + entity.remove(s); + } + } + + + /** + * 根据主键查询实体 + * + * @param keyValue 主键值 + * @return 数据 + */ + public T getByKey(String keyValue) { + return this.getByKey(keyValue, true); + } + + /** + * 根据主键查询实体 + * + * @param keyValue 主键值 + * @return 数据 + */ + public List getByKey(Collection keyValue) { + return this.getByKey(keyValue, true, null); + } + + /** + * 根据主键查询实体 + * + * @param keyValue 主键值 + * @return 数据 + */ + public T getByKey(String keyValue, boolean fill) { + return this.getByKey(keyValue, fill, null); + } + + /** + * update by id with data + * + * @param info data + * @return 影响的行数 + */ + public int updateById(T info) { + return this.updateById(info, null); + } + + public int update(Entity entity, Entity where) { + this.removeUpdate(entity); + return super.updateDb(entity, where); + } + + /** + * 同步 bean 删除 + * + * @param info bean + * @return 影响行数 + */ + public int delByBean(T info) { + Entity where = this.dataBeanToEntity(info); + Assert.state(!where.isEmpty(), I18nMessageUtil.get("i18n.no_parameters_added_with_minus_two.a7cf")); + 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); + } + + /** + * 根据主键生成 + * + * @param keyValue 主键值 + * @param consumer 回调 + * @return 影响行数 + */ + public int delByKey(Object keyValue, Consumer consumer) { + Entity where = new Entity(tableName); + if (keyValue != null) { + where.set(ID_STR, keyValue); + } + if (consumer != null) { + consumer.accept(where); + } + Assert.state(!where.isEmpty(), I18nMessageUtil.get("i18n.no_parameters_added_with_minus_one.e47d")); + return del(where); + } + + /** + * 判断是否存在 + * + * @param data 实体 + * @return true 存在 + */ + public boolean exists(T data) { + Entity entity = this.dataBeanToEntity(data); + 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; + } + + /** + * 判断是否存在 + * + * @param where 条件 + * @return true 存在 + */ + public boolean exists(Entity where) { + long count = this.count(where); + return count > 0; + } + + /** + * 查询一个 + * + * @param where 条件 + * @return Entity + */ + public Entity query(Entity where) { + List entities = this.queryList(where); + return CollUtil.getFirst(entities); + } + + /** + * 查询 list + * + * @param where 条件 + * @return data + */ + public List listByEntity(Entity where) { + List entity = this.queryList(where); + return this.entityToBeanList(entity); + } + + /** + * 查询 list + * + * @param where 条件 + * @param fill 是否填充 + * @return data + */ + public List listByEntity(Entity where, boolean fill) { + List entity = this.queryList(where); + return this.entityToBeanList(entity, fill); + } + + /** + * 查询列表 + * + * @param data 数据 + * @param count 查询数量 + * @param orders 排序 + * @return List + */ + public List queryList(T data, int count, Order... orders) { + Entity where = this.dataBeanToEntity(data); + return this.queryList(where, count, orders); + } + + /** + * 查询列表 + * + * @param where 条件 + * @param count 查询数量 + * @param orders 排序 + * @return List + */ + public List queryList(Entity where, int count, Order... orders) { + Page page = new Page(1, count); + page.addOrder(orders); + PageResultDto tPageResultDto = this.listPage(where, page); + return tPageResultDto.getResult(); + } + + + /** + * 分页查询 + * + * @param where 条件 + * @param page 分页 + * @return 结果 + */ + public PageResultDto listPage(Entity where, Page page) { + return this.listPage(where, page, true); + } + + /** + * 分页查询 + * + * @param where 条件 + * @param page 分页 + * @return 结果 + */ + public List listPageOnlyResult(Entity where, Page page) { + PageResultDto pageResultDto = this.listPage(where, page); + return pageResultDto.getResult(); + } + + /** + * sql 查询 list + * + * @param sql sql 语句 + * @param params 参数 + * @return list + */ + public List queryList(String sql, Object... params) { + List query = this.query(sql, params); + return this.entityToBeanList(query); + } + + /** + * 查询实体对象 + * + * @param data 实体 + * @return data + */ + public List listByBean(T data) { + return this.listByBean(data, true); + } + + /** + * 查询实体对象 + * + * @param data 实体 + * @return data + */ + public List listByBean(T data, boolean fill) { + Entity where = this.dataBeanToEntity(data); + List entitys = this.queryList(where); + return this.entityToBeanList(entitys, fill); + } + + /** + * 查询实体对象 + * + * @param data 实体 + * @return data + */ + public T queryByBean(T data) { + Entity where = this.dataBeanToEntity(data); + Entity entity = this.query(where); + return this.entityToBean(entity, true); + } + + public List list() { + return this.list(true); + } + + public List list(boolean fill) { + return this.listByBean(ReflectUtil.newInstance(this.tClass), fill); + } + + public long count() { + return super.count(Entity.create()); + } + + public long count(T data) { + return super.count(this.dataBeanToEntity(data)); + } + + /** + * 通用的分页查询, 使用该方法查询,数据库表字段不能包含 "page", "limit", "order_field", "order", "total" + *

+ * page=1&limit=10&order=ascend&order_field=name + * + * @param request 请求对象 + * @return page + */ + public PageResultDto listPage(HttpServletRequest request) { + return this.listPage(request, true); + } + + /** + * 通用的分页查询, 使用该方法查询,数据库表字段不能包含 "page", "limit", "order_field", "order", "total" + *

+ * page=1&limit=10&order=ascend&order_field=name + * + * @param request 请求对象 + * @return page + */ + public PageResultDto listPage(HttpServletRequest request, boolean fill) { + Map paramMap = ServletUtil.getParamMap(request); + return this.listPage(paramMap, fill); + } + + /** + * 转换为 page 对象 + * + * @param paramMap 请求参数 + * @return page + */ + public Page parsePage(Map paramMap) { + int page = Convert.toInt(paramMap.get("page"), 1); + int limit = Convert.toInt(paramMap.get("limit"), 10); + Assert.state(page > 0, "page value error"); + Assert.state(limit > 0 && limit < 200, "limit value error"); + // 移除 默认字段 + MapUtil.removeAny(paramMap, "page", "limit", "order_field", "order", "total"); + // + return new Page(page, limit); + } + + /** + * 通用的分页查询, 使用该方法查询,数据库表字段不能包含 "page", "limit", "order_field", "order", "total" + *

+ * page=1&limit=10&order=ascend&order_field=name + * + * @param paramMap 请求参数 + * @return page + */ + public PageResultDto listPage(Map paramMap) { + return this.listPage(paramMap, true); + } + + /** + * 通用的分页查询, 使用该方法查询,数据库表字段不能包含 "page", "limit", "order_field", "order", "total" + *

+ * page=1&limit=10&order=ascend&order_field=name + * + * @param paramMap 请求参数 + * @return page + */ + public PageResultDto listPage(Map paramMap, boolean fill) { + String orderField = paramMap.get("order_field"); + String order = paramMap.get("order"); + // + Page pageReq = this.parsePage(paramMap); + Entity where = Entity.create(); + List ignoreField = new ArrayList<>(10); + // 查询条件 + for (Map.Entry stringStringEntry : paramMap.entrySet()) { + String key = stringStringEntry.getKey(); + String value = stringStringEntry.getValue(); + if (StrUtil.isEmpty(value)) { + continue; + } + key = StrUtil.removeAll(key, "%"); + if (StrUtil.startWith(stringStringEntry.getKey(), "%") && StrUtil.endWith(stringStringEntry.getKey(), "%")) { + where.set(DialectUtil.wrapField(key), StrUtil.format(" like '%{}%'", value)); + } else if (StrUtil.endWith(stringStringEntry.getKey(), "%")) { + where.set(DialectUtil.wrapField(key), StrUtil.format(" like '{}%'", value)); + } else if (StrUtil.startWith(stringStringEntry.getKey(), "%")) { + where.set(DialectUtil.wrapField(key), StrUtil.format(" like '%{}'", value)); + } else if (StrUtil.containsIgnoreCase(key, "time") && StrUtil.contains(value, "~")) { + // 时间筛选 + String[] val = StrUtil.splitToArray(value, "~"); + if (val.length == 2) { + DateTime startDateTime = DateUtil.parse(val[0], DatePattern.NORM_DATETIME_FORMAT); + where.set(key, ">= " + startDateTime.getTime()); + + DateTime endDateTime = DateUtil.parse(val[1], DatePattern.NORM_DATETIME_FORMAT); + if (startDateTime.equals(endDateTime)) { + endDateTime = DateUtil.endOfDay(endDateTime); + } + // 防止字段重复 + where.set(key + " ", "<= " + endDateTime.getTime()); + } + } else if (StrUtil.containsIgnoreCase(key, "time")) { + // 时间筛选 + String timeKey = StrUtil.removeAny(key, "[0]", "[1]"); + if (ignoreField.contains(timeKey)) { + continue; + } + String startTime = paramMap.get(timeKey + "[0]"); + String endTime = paramMap.get(timeKey + "[1]"); + if (StrUtil.isAllNotEmpty(startTime, endTime)) { + DateTime startDateTime = DateUtil.parse(startTime, DatePattern.NORM_DATETIME_FORMAT); + where.set(timeKey, ">= " + startDateTime.getTime()); + + DateTime endDateTime = DateUtil.parse(endTime, DatePattern.NORM_DATETIME_FORMAT); + if (startDateTime.equals(endDateTime)) { + endDateTime = DateUtil.endOfDay(endDateTime); + } + // 防止字段重复 + where.set(timeKey + " ", "<= " + endDateTime.getTime()); + } + ignoreField.add(timeKey); + } else if (StrUtil.endWith(key, ":in")) { + String inKey = StrUtil.removeSuffix(key, ":in"); + where.set(DialectUtil.wrapField(inKey), StrUtil.split(value, StrUtil.COMMA)); + } else { + where.set(DialectUtil.wrapField(key), value); + } + } + // 排序 + if (StrUtil.isNotEmpty(orderField)) { + orderField = StrUtil.removeAll(orderField, "%"); + pageReq.addOrder(new Order(DialectUtil.wrapField(orderField), StrUtil.equalsIgnoreCase(order, "ascend") ? Direction.ASC : Direction.DESC)); + } + return this.listPage(where, pageReq, fill); + } + + public PageResultDto listPage(Entity where, Page page, boolean fill) { + if (ArrayUtil.isEmpty(page.getOrders())) { + page.addOrder(this.defaultOrders()); + } + return this.listPageDb(where, page, fill); + } + + public Order[] defaultOrders() { + return DEFAULT_ORDERS; + } + + /** + * 多个 id 查询数据 + * + * @param ids ids + * @return list + */ + public List listById(Collection ids) { + return this.listById(ids, null); + } + + /** + * 多个 id 查询数据 + * + * @param ids ids + * @return list + */ + public List listById(Collection ids, boolean fill) { + return this.listById(ids, null, fill); + } + + /** + * 多个 id 查询数据 + * + * @param ids ids + * @return list + */ + public List listById(Collection ids, Consumer consumer) { + return this.listById(ids, consumer, true); + } + + /** + * 多个 id 查询数据 + * + * @param ids ids + * @return list + */ + public List listById(Collection ids, Consumer consumer, boolean fill) { + if (CollUtil.isEmpty(ids)) { + return null; + } + Entity entity = Entity.create(); + entity.set(ID_STR, ids); + if (consumer != null) { + consumer.accept(entity); + } + List entities = super.queryList(entity); + return this.entityToBeanList(entities, fill); + } + + /** + * 执行清理 + */ + private void executeClear() { + int h2DbLogStorageCount = extConfig.getLogStorageCount(); + if (h2DbLogStorageCount <= 0) { + return; + } + this.executeClearImpl(h2DbLogStorageCount); + } + + /** + * 清理分发实现 + * + * @param h2DbLogStorageCount 保留数量 + */ + protected void executeClearImpl(int h2DbLogStorageCount) { + String[] strings = this.clearTimeColumns(); + for (String timeColumn : strings) { + this.autoClear(timeColumn, h2DbLogStorageCount, time -> { + Entity entity = Entity.create(super.getTableName()); + entity.set(timeColumn, "< " + time); + int count = super.del(entity); + if (count > 0) { + log.debug(I18nMessageUtil.get("i18n.cleaned_data.0e9d"), super.getTableName(), count); + } + }); + } + } + + /** + * 安装时间自动清理数据对字段 + * + * @return 数组 + */ + protected String[] clearTimeColumns() { + return new String[]{}; + } + + /** + * 自动清理数据接口 + * + * @param timeColumn 时间字段 + * @param maxCount 最大数量 + * @param consumer 查询出超过范围的时间回调 + */ + protected void autoClear(String timeColumn, int maxCount, Consumer consumer) { + if (maxCount <= 0) { + return; + } + ThreadUtil.execute(() -> { + long timeValue = this.getLastTimeValue(timeColumn, maxCount, null); + if (timeValue <= 0) { + return; + } + consumer.accept(timeValue); + }); + } + + /** + * 查询指定字段降序 指定条数对最后一个值 + * + * @param timeColumn 时间字段 + * @param maxCount 最大数量 + * @param whereCon 添加查询条件回调 + * @return 时间 + */ + protected long getLastTimeValue(String timeColumn, int maxCount, Consumer whereCon) { + Entity entity = Entity.create(super.getTableName()); + if (whereCon != null) { + // 条件 + whereCon.accept(entity); + } + Page page = new Page(maxCount, 1); + page.addOrder(new Order(timeColumn, Direction.DESC)); + PageResultDto pageResult; + try { + pageResult = this.listPage(entity, page); + } catch (java.lang.IllegalStateException illegalStateException) { + return 0L; + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.query_data_error.45e7"), e); + return 0L; + } + if (pageResult.isEmpty()) { + return 0L; + } + T entity1 = pageResult.get(0); + Object fieldValue = ReflectUtil.getFieldValue(entity1, timeColumn); + return Convert.toLong(fieldValue, 0L); + } + + /** + * 自动清理数据接口 + * + * @param timeClo 时间字段 + * @param maxCount 最大数量 + * @param predicate 查询出超过范围的时间,回调 + */ + protected void autoLoopClear(String timeClo, int maxCount, Consumer whereCon, Predicate predicate) { + if (maxCount <= 0) { + return; + } + ThreadUtil.execute(() -> { + Entity entity = Entity.create(super.getTableName()); + long timeValue = this.getLastTimeValue(timeClo, maxCount, whereCon); + if (timeValue <= 0) { + return; + } + if (whereCon != null) { + // 条件 + whereCon.accept(entity); + } + entity.set(timeClo, "< " + timeValue); + while (true) { + Page page = new Page(1, 50); + page.addOrder(new Order(timeClo, Direction.DESC)); + PageResultDto pageResult = this.listPage(entity, page); + if (pageResult.isEmpty()) { + return; + } +// pageResult.each(consumer); + List ids = pageResult.getResult().stream().filter(predicate).map(BaseDbModel::getId).collect(Collectors.toList()); + this.delByKey(ids, null); + } + }); + } + + /** + * 根据 节点和数据ID查询数据 + * + * @param nodeId 节点ID + * @param dataId 数据ID + * @return data + */ + public T getData(String nodeId, String dataId) { + return this.getByKey(dataId); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseGlobalOrWorkspaceService.java b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseGlobalOrWorkspaceService.java new file mode 100644 index 0000000000..f76c6cf61a --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseGlobalOrWorkspaceService.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.h2db; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.UserModel; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * 支持全局贡献的数据 + * + * @author bwcx_jzy + * @since 2023/03/23 + */ +public abstract class BaseGlobalOrWorkspaceService extends BaseWorkspaceService { + + @Override + public T getByKey(String keyValue, HttpServletRequest request) { + String workspace = this.getCheckUserWorkspace(request); + return super.getByKey(keyValue, true, entity -> entity.set("workspaceId", CollUtil.newArrayList(workspace, ServerConst.WORKSPACE_GLOBAL))); + } + + /** + * 获取列表 + * + * @param request 请求对象 + * @return page + */ + @Override + public PageResultDto listPage(HttpServletRequest request) { + // 验证工作空间权限 + Map paramMap = ServletUtil.getParamMap(request); + String workspaceId = this.getCheckUserWorkspace(request); + paramMap.put("workspaceId:in", workspaceId + StrUtil.COMMA + ServerConst.WORKSPACE_GLOBAL); + return super.listPage(paramMap); + } + +// public PageResultDto listPage(HttpServletRequest request, Map paramMap) { +// String workspaceId = this.getCheckUserWorkspace(request); +// paramMap.put("workspaceId:in", workspaceId + StrUtil.COMMA + ServerConst.WORKSPACE_GLOBAL); +// return super.listPage(paramMap, false); +// } + + @Override + public List listByWorkspace(HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + Entity entity = Entity.create(); + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + List entities = super.queryList(entity); + return super.entityToBeanList(entities); + } + + /** + * 更新数据,根据ID+工作空间ID + * + * @param info 更新的数据 + * @param request 请求 + * @return 影响行数 + */ + public int updateById(T info, HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + return super.updateById(info, entity -> entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL))); + } + + @Override + public int delByKey(String keyValue, HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + return super.delByKey(keyValue, entity -> entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL))); + } + + @Override + public int delByWorkspace(HttpServletRequest request, Consumer consumer) { + String workspaceId = this.getCheckUserWorkspace(request); + return super.delByKey(null, entity -> { + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + consumer.accept(entity); + int size = entity.size(); + Assert.state(size > 1, I18nMessageUtil.get("i18n.no_parameters_added.1721")); + }); + } + + @Override + public T getByKey(String keyValue, UserModel userModel) { + return super.getByKey(keyValue, userModel); + } + + /** + * 查询数据,并判断管理和创建人 + * + * @param keyValue 主机id + * @param request 请求信息 + * @return data + */ + public T getByKeyAndGlobal(String keyValue, HttpServletRequest request) { + return this.getByKeyAndGlobal(keyValue, request, I18nMessageUtil.get("i18n.data_does_not_exist.b201")); + } + + /** + * 查询数据,并判断管理和创建人 + * + * @param keyValue 主机id + * @param request 请求信息 + * @return data + */ + public T getByKeyAndGlobal(String keyValue, HttpServletRequest request, String errorMsg) { + T byKey = this.getByKey(keyValue, request); + Assert.notNull(byKey, errorMsg); + UserModel userModel = BaseServerController.getUserByThreadLocal(); + Assert.notNull(userModel, I18nMessageUtil.get("i18n.not_logged_in.c89f")); + if (StrUtil.equals(byKey.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL) && !userModel.isSystemUser()) { + // 是全局共享数据,并且不是管理员 + Assert.state(StrUtil.equals(userModel.getId(), byKey.getCreateUser()), I18nMessageUtil.get("i18n.no_current_data_permission.17d7")); + } + return byKey; + } + + public String covertGlobalWorkspace(HttpServletRequest request) { + Map paramMap = ServletUtil.getParamMap(request); + boolean global = Convert.toBool(paramMap.get("global"), false); + // 判断是否为全局模式 + return global ? ServerConst.WORKSPACE_GLOBAL : this.getCheckUserWorkspace(request); + } + +} 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 new file mode 100644 index 0000000000..a0001bfdc0 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeService.java @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.h2db; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +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.common.i18n.I18nMessageUtil; +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.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 javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author bwcx_jzy + * @since 2021/12/5 + */ +@Slf4j +public abstract class BaseNodeService extends BaseGlobalOrWorkspaceService { + + protected final NodeService nodeService; + protected final WorkspaceService workspaceService; + private final String dataName; + + protected BaseNodeService(NodeService nodeService, + WorkspaceService workspaceService, + String dataName) { + this.nodeService = nodeService; + this.workspaceService = workspaceService; + this.dataName = dataName; + } + + @Override + public List listByWorkspace(HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + 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); + } + + /** + * 同步所有节点的项目 + */ + public void syncAllNode() { + ThreadUtil.execute(() -> { + List list = nodeService.list(); + if (CollUtil.isEmpty(list)) { + log.debug(I18nMessageUtil.get("i18n.no_nodes.17b4")); + return; + } + // 排序 避免项目被个节点绑定 + list.sort((o1, o2) -> { + if (StrUtil.equals(o1.getWorkspaceId(), Const.WORKSPACE_DEFAULT_ID)) { + return 1; + } + if (StrUtil.equals(o2.getWorkspaceId(), Const.WORKSPACE_DEFAULT_ID)) { + return 1; + } + return 0; + }); + for (NodeModel nodeModel : list) { + this.syncNode(nodeModel); + } + }); + } + + + /** + * 同步节点的项目 + * + * @param nodeModel 节点 + */ + public void syncNode(final NodeModel nodeModel) { + 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(I18nMessageUtil.get("i18n.project_data_workspace_id_inconsistency.7ed6"), 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()); + } + + /** + * 同步执行 同步节点信息 + * + * @param nodeModel 节点信息 + * @return json + */ + public String syncExecuteNode(NodeModel nodeModel) { + String nodeModelName = nodeModel.getName(); + if (!nodeModel.isOpenStatus()) { + log.debug(I18nMessageUtil.get("i18n.node_not_enabled.10ef"), nodeModelName); + return I18nMessageUtil.get("i18n.node_not_enabled.a14d"); + } + try { + JSONArray jsonArray = this.getLitDataArray(nodeModel); + if (CollUtil.isEmpty(jsonArray)) { + Entity entity = Entity.create(); + entity.set("nodeId", nodeModel.getId()); + int del = super.del(entity); + // + log.debug(I18nMessageUtil.get("i18n.node_no_data_pulled.0dae"), nodeModelName, dataName, del); + return I18nMessageUtil.get("i18n.node_did_not_pull_anything.8af5") + dataName; + } + // 查询现在存在的项目 + T where = ReflectUtil.newInstance(this.tClass); + // where.setWorkspaceId(nodeModel.getWorkspaceId()); + where.setNodeId(nodeModel.getId()); + List cacheAll = super.listByBean(where); + cacheAll = ObjectUtil.defaultIfNull(cacheAll, Collections.emptyList()); + Set needDelete = new HashSet<>(); + Set cacheIds = cacheAll.stream() + .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(I18nMessageUtil.get("i18n.data_creation_time_format_incorrect.7772"), 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(I18nMessageUtil.get("i18n.data_modification_time_format_incorrect.7ffe"), 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())) + .filter(item -> { + if (StrUtil.equals(item.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL)) { + return true; + } + // 检查对应的工作空间 是否存在 + return workspaceService.exists(new WorkspaceModel(item.getWorkspaceId())); + }) + .filter(item -> { + if (StrUtil.equals(item.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL)) { + return true; + } + // 避免重复同步 + return StrUtil.equals(nodeModel.getWorkspaceId(), item.getWorkspaceId()); + }) + .peek(item -> { + item.setNodeName(nodeModel.getName()); + WorkspaceModel workspaceModel = workspaceService.getByKey(nodeModel.getWorkspaceId()); + item.setWorkspaceName(Optional.ofNullable(workspaceModel).map(WorkspaceModel::getName).orElse(I18nMessageUtil.get("i18n.data_does_not_exist.b201"))); + cacheIds.remove(item.dataId()); + // 需要删除相反的工作空间的数据(避免出现一个脚本同步出2条数据的问题) + if (StrUtil.equals(item.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL)) { + needDelete.add(BaseNodeModel.fullId(nodeModel.getWorkspaceId(), nodeModel.getId(), item.dataId())); + } else { + needDelete.add(BaseNodeModel.fullId(ServerConst.WORKSPACE_GLOBAL, nodeModel.getId(), item.dataId())); + } + }) + .collect(Collectors.toList()); + // 设置 临时缓存,便于放行检查 + BaseServerController.resetInfo(UserModel.EMPTY); + // + projectInfoModels.forEach(BaseNodeService.super::upsert); + // 删除项目 + int delCount = 0; + Set strings = cacheIds.stream() + .flatMap((Function>) s -> Stream.of( + BaseNodeModel.fullId(nodeModel.getWorkspaceId(), nodeModel.getId(), s), + BaseNodeModel.fullId(ServerConst.WORKSPACE_GLOBAL, nodeModel.getId(), s))) + .collect(Collectors.toSet()); + // + needDelete.addAll(strings); + if (CollUtil.isNotEmpty(needDelete)) { + delCount = super.delByKey(needDelete, null); + } + int size = CollUtil.size(projectInfoModels); + String template = I18nMessageUtil.get("i18n.physical_node_pull.874e"); + String format = StrUtil.format( + template, + nodeModelName, CollUtil.size(jsonArray), dataName, + CollUtil.size(cacheAll), dataName, + size, dataName, + delCount); + this.refreshCacheStat(nodeModel.getId(), size); + log.debug(format); + return format; + } catch (Exception e) { + return this.checkException(e, nodeModelName); + } finally { + BaseServerController.removeEmpty(); + } + } + + /** + * 刷新缓存统计 + * + * @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(I18nMessageUtil.get("i18n.synchronization_failed.091a"), nodeModelName, agentException.getMessage()); + return I18nMessageUtil.get("i18n.synchronization_failed.d610") + agentException.getMessage(); + } else if (e instanceof AgentAuthorizeException) { + AgentAuthorizeException agentAuthorizeException = (AgentAuthorizeException) e; + log.error(I18nMessageUtil.get("i18n.authorization_exception.acc0"), nodeModelName, agentAuthorizeException.getMessage()); + return I18nMessageUtil.get("i18n.auth_exception.27be") + agentAuthorizeException.getMessage(); + } +// else if (e instanceof JSONException) { +// log.error("{} 消息解析失败 {}", nodeModelName, e.getMessage()); +// return "消息解析失败" + e.getMessage(); +// } + log.error(I18nMessageUtil.get("i18n.synchronization_node_failure_with_details.8660"), dataName, nodeModelName, e); + return StrUtil.format(I18nMessageUtil.get("i18n.synchronization_node_failure.8a2c"), dataName, e.getMessage()); + } + + /** + * 同步节点的项目 + * + * @param nodeModel 节点 + * @param id 项目id + */ + public void syncNode(final NodeModel nodeModel, String id) { + String nodeModelName = nodeModel.getName(); + if (!nodeModel.isOpenStatus()) { + log.debug(I18nMessageUtil.get("i18n.node_not_enabled.10ef"), nodeModelName); + return; + } + ThreadUtil.execute(() -> { + try { + JSONObject data = this.getItem(nodeModel, id); + if (data == null) { + // 删除 + String fullId = BaseNodeModel.fullId(nodeModel.getWorkspaceId(), nodeModel.getId(), id); + super.delByKey(fullId); + return; + } + T projectInfoModel = data.toJavaObject(this.tClass); + this.fullData(projectInfoModel, nodeModel); + // 设置 临时缓存,便于放行检查 + BaseServerController.resetInfo(UserModel.EMPTY); + // + super.upsert(projectInfoModel); + } catch (Exception e) { + this.checkException(e, nodeModelName); + } finally { + BaseServerController.removeEmpty(); + } + }); + } + + /** + * 填充数据ID + * + * @param item 对象 + * @param nodeModel 节点 + */ + private void fullData(T item, NodeModel nodeModel) { + item.dataId(item.getId()); + if (StrUtil.isEmpty(item.getNodeId())) { + item.setNodeId(nodeModel.getId()); + } + if (StrUtil.isEmpty(item.getWorkspaceId())) { + item.setWorkspaceId(nodeModel.getWorkspaceId()); + } + item.setId(item.fullId()); + } + + /** + * 删除节点 工作空间缓存 + * + * @param nodeId 节点 + * @param request 请求 + * @return 影响行数 + */ + public int delCache(String nodeId, HttpServletRequest request) { + String checkUserWorkspace = this.getCheckUserWorkspace(request); + Entity entity = Entity.create(); + entity.set("nodeId", nodeId); + entity.set("workspaceId", checkUserWorkspace); + return super.del(entity); + } + + /** + * 删除节点 工作空间缓存 + * + * @param dataId 数据ID + * @param nodeId 节点 + * @param request 请求 + * @return 影响行数 + */ + public int delCache(String dataId, String nodeId, HttpServletRequest request) { + return this.delByWorkspace(request, entity -> { + T data = ReflectUtil.newInstance(this.tClass); + data.setNodeId(nodeId); + data.dataId(dataId); + Entity entity1 = dataBeanToEntity(data); + entity.putAll(entity1); + }); + + + } + + /** + * 根据 节点和数据ID查询数据 + * + * @param nodeId 节点ID + * @param dataId 数据ID + * @return data + */ + @Override + public T getData(String nodeId, String dataId) { + T data = ReflectUtil.newInstance(this.tClass); + data.setNodeId(nodeId); + data.dataId(dataId); + return super.queryByBean(data); + } + + /** + * 查询远端项目 + * + * @param nodeModel 节点 + * @param id 项目ID + * @return json + */ + public abstract JSONObject getItem(NodeModel nodeModel, String id); + + /** + * 查询列表数据 + * + * @param nodeModel 节点 + * @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 new file mode 100644 index 0000000000..8e0339d0af --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseWorkspaceService.java @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.h2db; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ReflectUtil; +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.hutool.extra.servlet.ServletUtil; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.dialect.DialectUtil; +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; +import org.dromara.jpom.service.user.UserBindWorkspaceService; +import org.springframework.util.Assert; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * 工作空间 通用 service + * + * @author bwcx_jzy + * @since 2021/8/13 + */ +public abstract class BaseWorkspaceService extends BaseDbService { + /** + * 是否可以进行排序 + */ + private final boolean canSort; + /** + * 有排序字段的默认排序规则 + */ + private static final Order[] SORT_DEFAULT_ORDERS = new Order[]{ + new Order("sortValue", Direction.DESC), + new Order("createTimeMillis", Direction.DESC), + new Order("modifyTimeMillis", Direction.DESC), + new Order("id", Direction.DESC) + }; + + public BaseWorkspaceService() { + super(); + this.canSort = ReflectUtil.hasField(this.tClass, "sortValue"); + } + + /** + * 根据工作空间查询 + * + * @param request 请求 + * @return list + */ + public List listByWorkspace(HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + Entity entity = Entity.create(); + entity.set("workspaceId", workspaceId); + List entities = super.queryList(entity); + return super.entityToBeanList(entities); + } + + /** + * 根据主键ID + 请信息查询 + * + * @param keyValue ID + * @param request 请求 + * @return data + */ + public T getByKey(String keyValue, HttpServletRequest request) { + String workspace = this.getCheckUserWorkspace(request); + return super.getByKey(keyValue, true, entity -> entity.set("workspaceId", workspace)); + } + + /** + * 根据主键ID + 请信息查询 + * + * @param keyValue ID + * @param request 请求 + * @return data + */ + public List getByKey(Collection keyValue, HttpServletRequest request) { + String workspace = this.getCheckUserWorkspace(request); + return super.getByKey(keyValue, true, entity -> entity.set("workspaceId", workspace)); + } + + /** + * 根据主键ID + 请信息查询 + * + * @param keyValue ID + * @param request 请求 + * @param fill 是否填充数据 + * @return data + */ + public T getByKey(String keyValue, HttpServletRequest request, boolean fill) { + String workspace = this.getCheckUserWorkspace(request); + return super.getByKey(keyValue, fill, entity -> entity.set("workspaceId", workspace)); + } + + /** + * 根据主键ID + 用户ID + * + * @param keyValue ID + * @param userModel 用户 + * @return data + */ + public T getByKey(String keyValue, UserModel userModel) { + T byKey = super.getByKey(keyValue, false); + if (byKey == null) { + return null; + } + this.checkUserWorkspace(byKey.getWorkspaceId(), userModel); + return byKey; + } + + @Override + protected void fillInsert(T t) { + super.fillInsert(t); + if (StrUtil.isEmpty(t.getWorkspaceId())) { + // 自动绑定 工作空间ID + ServletRequestAttributes servletRequestAttributes = BaseServerController.tryGetRequestAttributes(); + if (servletRequestAttributes != null) { + HttpServletRequest request = servletRequestAttributes.getRequest(); + String workspaceId = this.getCheckUserWorkspace(request); + t.setWorkspaceId(workspaceId); + } else { + t.setWorkspaceId(Const.WORKSPACE_DEFAULT_ID); + } + } else { + // 检查权限 + String modifyUser = t.getModifyUser(); + if (!StrUtil.equals(modifyUser, UserModel.SYSTEM_ADMIN)) { + this.checkUserWorkspace(t.getWorkspaceId()); + } + } + } + + /** + * 获取我所有的空间 + * + * @param request 请求对象 + * @return page + */ + @Override + public PageResultDto listPage(HttpServletRequest request) { + // 验证工作空间权限 + Map paramMap = ServletUtil.getParamMap(request); + String workspaceId = this.getCheckUserWorkspace(request); + //Assert.hasText(workspaceId, "此接口需要传workspaceId!"); + paramMap.put("workspaceId", workspaceId); + return super.listPage(paramMap); + } + +// /** +// * 根据 workspaceId获取空间变量列表 +// * +// * @param workspaceId +// * @return +// */ +// public List listByWorkspaceId(String workspaceId) { +// Entity entity = Entity.create(); +// entity.set("workspaceId", workspaceId); +// List entities = super.queryList(entity); +// return super.entityToBeanList(entities); +// } + + /** + * 删除 + * + * @param keyValue 主键 + * @param request 请求信息 + * @return 影响行数 + */ + public int delByKey(String keyValue, HttpServletRequest request) { + String workspace = this.getCheckUserWorkspace(request); + return super.delByKey(keyValue, entity -> entity.set("workspaceId", workspace)); + } + + /** + * 删除,根据工作空间删除 + * + * @param consumer 回调 + * @param request 请求信息 + * @return 影响行数 + */ + public int delByWorkspace(HttpServletRequest request, Consumer consumer) { + String workspace = this.getCheckUserWorkspace(request); + return super.delByKey(null, entity -> { + entity.set("workspaceId", workspace); + consumer.accept(entity); + int size = entity.size(); + Assert.state(size > 1, I18nMessageUtil.get("i18n.no_parameters_added.1721")); + }); + } + + public static String getWorkspaceId(HttpServletRequest request) { + String workspaceId = request.getParameter(Const.WORKSPACE_ID_REQ_HEADER); + if (StrUtil.isEmpty(workspaceId)) { + workspaceId = ServletUtil.getHeader(request, Const.WORKSPACE_ID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); + } + return StrUtil.emptyToDefault(workspaceId, Const.WORKSPACE_DEFAULT_ID); + } + + /** + * 获取 工作空间ID 并判断是否有权限 + * + * @param request 请求对象 + * @return 工作空间ID + */ + public String getCheckUserWorkspace(HttpServletRequest request) { + String workspaceId = getWorkspaceId(request); + Assert.hasText(workspaceId, I18nMessageUtil.get("i18n.workspace_required.b3bd")); + // + this.checkUserWorkspace(workspaceId); + return workspaceId; + } + + /** + * 判断用户是否有对应工作空间权限 + * + * @param workspaceId 工作空间ID + */ + public void checkUserWorkspace(String workspaceId) { + UserModel userModel = BaseServerController.getUserByThreadLocal(); + this.checkUserWorkspace(workspaceId, userModel); + } + + /** + * 判断用户是否有对应工作空间权限 + * + * @param workspaceId 工作空间ID + */ + protected void checkUserWorkspace(String workspaceId, UserModel userModel) { + Assert.notNull(userModel, I18nMessageUtil.get("i18n.no_user.3b69")); + if (StrUtil.equals(userModel.getId(), UserModel.SYSTEM_ADMIN)) { + // 系统执行,发行检查 + return; + } + if (userModel.isSuperSystemUser()) { + // 超级管理员 + return; + } + if (StrUtil.equals(workspaceId, ServerConst.WORKSPACE_GLOBAL)) { + // 全局 ID 忽略 + return; + } + // 查询绑定的权限 + UserBindWorkspaceService userBindWorkspaceService = SpringUtil.getBean(UserBindWorkspaceService.class); + boolean exists = userBindWorkspaceService.exists(userModel, workspaceId); + if (!exists) { + throw new PermissionException(I18nMessageUtil.get("i18n.no_corresponding_workspace_permission.8402")); + } + } + + + public List listById(Collection ids, HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + return super.listById(ids, entity -> entity.set("workspaceId", workspaceId)); + } + + /** + * 更新数据,根据ID+工作空间ID + * + * @param info 更新的数据 + * @param request 请求 + * @return 影响行数 + */ + public int updateById(T info, HttpServletRequest request) { + String workspaceId = this.getCheckUserWorkspace(request); + return super.updateById(info, entity -> entity.set("workspaceId", workspaceId)); + } + + // 排序相关方法 --------- + + + @Override + public Order[] defaultOrders() { + if (canSort) { + return SORT_DEFAULT_ORDERS; + } + return super.defaultOrders(); + } + + /** + * 置顶 + * + * @param id 数据ID + * @param request 请求 + */ + public void sortToTop(String id, HttpServletRequest request) { + Assert.state(canSort, I18nMessageUtil.get("i18n.data_not_supported_for_sorting.5431")); + String workspaceId = this.getCheckUserWorkspace(request); + String maxSql = "select max(sortValue) as sortValue from " + this.tableName + " where workspaceId=?"; + List query = this.query(maxSql, workspaceId); + float maxSortValue = Opt.ofEmptyAble(query) + .map(CollUtil::getFirst) + .map(entity -> entity.getFloat("sortValue")) + .orElse(1f); + // + String updateSql = "update " + this.tableName + " set sortValue=? where id=? and workspaceId=?"; + this.execute(updateSql, maxSortValue + 0.0001f, id, workspaceId); + } + + /** + * 向上移 + * + * @param id 数据ID + * @param beforeId 前面一个数据的ID + * @param request 请求 + */ + public void sortMoveUp(String id, String beforeId, HttpServletRequest request) { + this.sortMove(id, beforeId, request, true); + } + + /** + * 向下移 + * + * @param id 数据ID + * @param afterId 后面一个数据的ID + * @param request 请求 + */ + public void sortMoveDown(String id, String afterId, HttpServletRequest request) { + this.sortMove(id, afterId, request, false); + } + + /** + * 移动排序方法 + * + * @param id 数据ID + * @param compareId 比较的数据ID + * @param request 请求 + * @param up true 上移 + */ + private void sortMove(String id, String compareId, HttpServletRequest request, boolean up) { + Assert.state(canSort, I18nMessageUtil.get("i18n.data_not_supported_for_sorting.5431")); + Assert.hasText(id, I18nMessageUtil.get("i18n.data_id_does_not_exist.a566")); + Assert.hasText(compareId, I18nMessageUtil.get("i18n.compare_id_not_exist.43be")); + String workspaceId = this.getCheckUserWorkspace(request); + String sql = "select sortValue as sortValue from " + this.tableName + " where id=? and workspaceId=?"; + List query = this.query(sql, compareId, workspaceId); + float compareSortValue = Opt.ofEmptyAble(query) + .map(CollUtil::getFirst) + .map(entity -> entity.getFloat("sortValue")) + .orElse(1f); + String updateSql = "update " + this.tableName + " set sortValue=? where id=? and workspaceId=?"; + this.execute(updateSql, up ? compareSortValue + 0.0001f : compareSortValue - 0.0001f, id, workspaceId); + } + + /** + * load date group by group name + * + * @return list + */ + public List listGroup(HttpServletRequest request) { + String workspaceId = getCheckUserWorkspace(request); + String group = DialectUtil.wrapField("group"); + String sql = String.format("select %s from %s where workspaceId=? group by %s", group, getTableName(), group); + return super.listGroupByName(sql, group, workspaceId); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/monitor/MonitorService.java b/modules/server/src/main/java/org/dromara/jpom/service/monitor/MonitorService.java new file mode 100644 index 0000000000..d8b23a0a1f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/monitor/MonitorService.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.monitor; + +import cn.keepbx.jpom.cron.ICron; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.model.data.MonitorModel; +import org.dromara.jpom.monitor.MonitorItem; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.util.StringUtil; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 监控管理Service + * + * @author Arno + */ +@Service +@Slf4j +public class MonitorService extends BaseWorkspaceService implements ICron { + + @Override + public int insert(MonitorModel monitorModel) { + int count = super.insert(monitorModel); + this.checkCron(monitorModel); + return count; + } + + @Override + public int delByKey(String keyValue, HttpServletRequest request) { + int i = super.delByKey(keyValue, request); + if (i > 0) { + String taskId = "monitor:" + keyValue; + CronUtils.remove(taskId); + } + return i; + } + + @Override + public int updateById(MonitorModel info, HttpServletRequest request) { + int update = super.updateById(info, request); + if (update > 0) { + this.checkCron(info); + } + return update; + } + + @Override + public List queryStartingList() { + // 关闭监听 + MonitorModel monitorModel = new MonitorModel(); + monitorModel.setStatus(true); + return super.listByBean(monitorModel); + } + + /** + * 检查定时任务 状态 + * + * @param monitorModel 监控信息 + */ + @Override + public boolean checkCron(MonitorModel monitorModel) { + String id = monitorModel.getId(); + String taskId = "monitor:" + id; + String autoExecCron = monitorModel.getExecCron(); + autoExecCron = StringUtil.parseCron(autoExecCron); + if (!monitorModel.status(autoExecCron)) { + CronUtils.remove(taskId); + return false; + } + log.debug("start monitor cron {} {} {}", id, monitorModel.getName(), autoExecCron); + CronUtils.upsert(taskId, autoExecCron, new MonitorItem(id)); + return true; + } + + /** + * 设置报警状态 + * + * @param id 监控id + * @param alarm 状态 + */ + public void setAlarm(String id, boolean alarm) { + MonitorModel monitorModel = new MonitorModel(); + monitorModel.setId(id); + monitorModel.setAlarm(alarm); + super.updateById(monitorModel); + } + + /** + * 判断是否存在对应节点数据 + * + * @param nodeId 节点id + * @return true 存在 + */ + public boolean checkNode(String nodeId) { + List list = list(); + if (list == null || list.isEmpty()) { + return false; + } + for (MonitorModel monitorModel : list) { + List projects = monitorModel.projects(); + if (projects != null) { + for (MonitorModel.NodeProject project : projects) { + if (nodeId.equals(project.getNode())) { + return true; + } + } + } + } + return false; + } + + + /*public boolean checkProject(String nodeId, String projectId) { + List list = list(); + if (list == null || list.isEmpty()) { + return false; + } + for (MonitorModel monitorModel : list) { + List projects = monitorModel.projects(); + if (projects != null) { + for (MonitorModel.NodeProject project : projects) { + if (project.getNode().equals(nodeId)) { + List projects1 = project.getProjects(); + if (projects1 != null && projects1.contains(projectId)) { + return true; + } + } + } + } + } + return false; + }*/ +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/monitor/MonitorUserOptService.java b/modules/server/src/main/java/org/dromara/jpom/service/monitor/MonitorUserOptService.java new file mode 100644 index 0000000000..f4b118cd91 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/monitor/MonitorUserOptService.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.monitor; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.model.data.MonitorUserOptModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 监控用户操作Service + * + * @author bwcx_jzy + */ +@Service +public class MonitorUserOptService extends BaseWorkspaceService { + + /** + * 查询 对应操作的监控信息 + * + * @param workspaceId 工作空间ID + * @param classFeature 功能 + * @param methodFeature 操作 + * @return list + */ + public List listByType(String workspaceId, ClassFeature classFeature, MethodFeature methodFeature, String userId) { + MonitorUserOptModel where = new MonitorUserOptModel(); + if (StrUtil.isNotEmpty(workspaceId)) { + // 没有工作空间查询全部 + where.setWorkspaceId(workspaceId); + } + where.setStatus(true); + List list = super.listByBean(where); + if (CollUtil.isEmpty(list)) { + return null; + } + return list.stream().filter(monitorUserOptModel -> { + List classFeatures = monitorUserOptModel.monitorFeature(); + List methodFeatures = monitorUserOptModel.monitorOpt(); + boolean b = CollUtil.contains(classFeatures, classFeature) && CollUtil.contains(methodFeatures, methodFeature); + if (b) { + List monitorUser = monitorUserOptModel.monitorUser(); + return CollUtil.contains(monitorUser, userId); + } + return false; + }).collect(Collectors.toList()); + } + +// public List listByType(UserOperateLogV1.OptType optType, String userId) { +// List userOptModels = this.listByType(optType); +// if (CollUtil.isEmpty(userOptModels)) { +// return null; +// } +// return userOptModels.stream().filter(monitorUserOptModel -> { +// List monitorUser = monitorUserOptModel.getMonitorUser(); +// return CollUtil.contains(monitorUser, userId); +// }).collect(Collectors.toList()); +// } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/NodeService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/NodeService.java new file mode 100644 index 0000000000..ca115b408f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/NodeService.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2021/12/4 + */ +@Service +@Slf4j +public class NodeService extends BaseWorkspaceService { + + private final SshService sshService; + + @Resource + @Lazy + private ProjectInfoCacheService projectInfoCacheService; + + public NodeService(SshService sshService) { + this.sshService = sshService; + } + + @Override + protected void fillSelectResult(NodeModel data) { + if (data != null) { + data.setLoginPwd(null); + } + } + + +// public boolean existsByUrl(String url, String workspaceId, String id) { +// +// // 可能出现错误 +// NodeModel nodeModel1 = new NodeModel(); +// nodeModel1.setUrl(url); +// nodeModel1.setWorkspaceId(workspaceId); +// List nodeModels = ObjectUtil.defaultIfNull(super.listByBean(nodeModel1), Collections.EMPTY_LIST); +// Optional any = nodeModels.stream().filter(nodeModel2 -> !StrUtil.equals(id, nodeModel2.getId())).findAny(); +// return any.isPresent(); +// } + + private NodeModel resolveNode(HttpServletRequest request) { + // 创建对象 + NodeModel nodeModel = ServletUtil.toBean(request, NodeModel.class, true); + String id = nodeModel.getId(); + Assert.hasText(id, I18nMessageUtil.get("i18n.node_id_not_found.2f9e")); + Assert.hasText(nodeModel.getName(), I18nMessageUtil.get("i18n.node_name_required.5bdf")); + // 兼容就数据判断 + String checkId = StrUtil.replace(id, StrUtil.DASHED, StrUtil.UNDERLINE); + Validator.validateGeneral(checkId, 2, Const.ID_MAX_LEN, I18nMessageUtil.get("i18n.node_id_required_and_format.5926")); + + Assert.hasText(nodeModel.getName(), I18nMessageUtil.get("i18n.node_name_required.ac0f")); + String workspaceId = this.getCheckUserWorkspace(request); + nodeModel.setWorkspaceId(workspaceId); + + // 判断 ssh + String sshId = nodeModel.getSshId(); + if (StrUtil.isNotEmpty(sshId)) { + SshModel byKey = sshService.getByKey(sshId, request); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.ssh_does_not_exist.5bec")); + Entity entity = Entity.create(); + entity.set("sshId", sshId); + entity.set("workspaceId", workspaceId); + if (StrUtil.isNotEmpty(id)) { + entity.set("id", StrUtil.format(" <> {}", id)); + } + boolean exists = super.exists(entity); + Assert.state(!exists, I18nMessageUtil.get("i18n.ssh_already_bound_to_other_node.2d4e")); + } + NodeModel update = new NodeModel(); + update.setId(id); + update.setName(nodeModel.getName()); + update.setGroup(nodeModel.getGroup()); + update.setSshId(nodeModel.getSshId()); + update.setOpenStatus(nodeModel.getOpenStatus()); + return update; + } + + /** + * 修改 节点 + * + * @param request 请求对象 + */ + public void update(HttpServletRequest request) { + NodeModel nodeModel = this.resolveNode(request); + this.updateById(nodeModel); + // 同步项目 + projectInfoCacheService.syncNode(nodeModel); + } + + + public void existsNode(String workspaceId, String machineId) { + // + NodeModel where = new NodeModel(); + where.setWorkspaceId(workspaceId); + where.setMachineId(machineId); + NodeModel nodeModel = this.queryByBean(where); + Assert.isNull(nodeModel, () -> I18nMessageUtil.get("i18n.node_already_exists_in_workspace.9499") + nodeModel.getName()); + } + + public boolean existsNode2(String workspaceId, String machineId) { + // + NodeModel where = new NodeModel(); + where.setWorkspaceId(workspaceId); + where.setMachineId(machineId); + return this.exists(where); + } + + /** + * 将节点信息同步到其他工作空间 + * + * @param ids 多给节点ID + * @param nowWorkspaceId 当前的工作空间ID + * @param workspaceId 同步到哪个工作空间 + */ + public void syncToWorkspace(String ids, String nowWorkspaceId, String workspaceId) { + StrUtil.splitTrim(ids, StrUtil.COMMA).forEach(id -> { + NodeModel data = this.getByKey(id, false, entity -> entity.set("workspaceId", nowWorkspaceId)); + Assert.notNull(data, I18nMessageUtil.get("i18n.no_corresponding_node_info.cd24")); + this.existsNode(workspaceId, data.getMachineId()); + // 不存在则添加节点 + data.setId(null); + data.setWorkspaceId(workspaceId); + data.setCreateTimeMillis(null); + data.setModifyTimeMillis(null); + data.setModifyUser(null); + data.setLoginName(null); + data.setUrl(null); + data.setLoginPwd(null); + data.setProtocol(null); + data.setHttpProxy(null); + data.setHttpProxyType(null); + // ssh 不同步 + data.setSshId(null); + this.insert(data); + }); + } + + @Override + protected void fillInsert(NodeModel nodeModel) { + super.fillInsert(nodeModel); + // 表中字段不能为空,设置为空字符串 + nodeModel.setLoginName(StrUtil.EMPTY); + nodeModel.setLoginPwd(StrUtil.EMPTY); + nodeModel.setProtocol(StrUtil.EMPTY); + nodeModel.setUrl(StrUtil.EMPTY); + } + + // @Override +// public void insertNotFill(NodeModel nodeModel) { +// nodeModel.setWorkspaceId(StrUtil.emptyToDefault(nodeModel.getWorkspaceId(), Const.WORKSPACE_DEFAULT_ID)); +// this.fillNodeInfo(nodeModel); +// super.insertNotFill(nodeModel); +// } + +// /** +// * 填充默认字段 +// * +// * @param nodeModel 节点 +// */ +// private void fillNodeInfo(NodeModel nodeModel) { +// nodeModel.setProtocol(StrUtil.emptyToDefault(nodeModel.getProtocol(), "http")); +// nodeModel.setOpenStatus(ObjectUtil.defaultIfNull(nodeModel.getOpenStatus(), 0)); +// } + + + public List getNodeBySshId(String sshId) { + NodeModel nodeModel = new NodeModel(); + nodeModel.setSshId(sshId); + return super.listByBean(nodeModel); + } + + @Override + public NodeModel getData(String nodeId, String dataId) { + return Opt.ofBlankAble(nodeId) + .map(super::getByKey) + .orElseGet(() -> Opt.ofBlankAble(dataId) + .map(NodeService.super::getByKey) + .orElse(null) + ); + } + + public long countByMachine(String machineId) { + NodeModel nodeModel = new NodeModel(); + nodeModel.setMachineId(machineId); + return this.count(nodeModel); + } +} 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 new file mode 100644 index 0000000000..5843492206 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/ProjectInfoCacheService.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node; + +import cn.keepbx.jpom.model.JsonMessage; +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.common.i18n.I18nMessageUtil; +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; +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 + */ +@Service +public class ProjectInfoCacheService extends BaseNodeService implements ITriggerToken { + + public ProjectInfoCacheService(NodeService nodeService, + WorkspaceService workspaceService) { + super(nodeService, workspaceService, I18nMessageUtil.get("i18n.project_name.31ec")); + } + + /** + * 查询远端项目 + * + * @param nodeModel 节点ID + * @param id 项目ID + * @return json + */ + @Override + public JSONObject getItem(NodeModel nodeModel, String id) { + JsonMessage request = NodeForward.request(nodeModel, NodeUrl.Manage_GetProjectItem, "id", id); + return request.getData(); + } + + /** + * 查询项目是否存在 + * + * @param workspaceId 工作空间ID + * @param nodeId 节点id + * @param id 项目id + * @return true 存在 + */ + public boolean exists(String workspaceId, String nodeId, String id) { + ProjectInfoCacheModel projectInfoCacheModel = new ProjectInfoCacheModel(); + projectInfoCacheModel.setWorkspaceId(workspaceId); + projectInfoCacheModel.setNodeId(nodeId); + projectInfoCacheModel.setProjectId(id); + return super.exists(projectInfoCacheModel); + } + + /** + * 查询项目是否存在 + * + * @param nodeId 节点id + * @param id 项目id + * @return true 存在 + */ + public boolean exists(String nodeId, String id) { + NodeModel nodeModel = nodeService.getByKey(nodeId); + if (nodeModel == null) { + return false; + } + return this.exists(nodeModel.getWorkspaceId(), nodeId, id); + } + + /** + * 将响应的数据转为请求的数据 + * + * @param item 数据 + * @return data + */ + public JSONObject convertToRequestData(JSONObject item) { + + return item; + } + + + @Override + public JSONArray getLitDataArray(NodeModel nodeModel) { + JsonMessage tJsonMessage = NodeForward.request(nodeModel, NodeUrl.Manage_GetProjectInfo, "notStatus", "true"); + return tJsonMessage.getData(); + } + + @Override + public List 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 new file mode 100644 index 0000000000..0d2940c2d1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptExecuteLogServer.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node.script; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSON; +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.ServerConst; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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; +import org.dromara.jpom.model.node.NodeScriptExecuteLogCacheModel; +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.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 脚本默认执行记录 + * + * @author bwcx_jzy + * @since 2021/12/12 + */ +@Service +@Slf4j +public class NodeScriptExecuteLogServer extends BaseNodeService { + + public NodeScriptExecuteLogServer(NodeService nodeService, + WorkspaceService workspaceService) { + super(nodeService, workspaceService, I18nMessageUtil.get("i18n.script_template_log2.6b2c")); + } + + @Override + protected String[] clearTimeColumns() { + return new String[]{"createTimeMillis"}; + } + + @Override + public JSONObject getItem(NodeModel nodeModel, String id) { + return null; + } + + @Override + public JSONArray getLitDataArray(NodeModel nodeModel) { + JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.SCRIPT_PULL_EXEC_LOG, "pullCount", 100); + if (!jsonMessage.success()) { + throw new AgentException(jsonMessage.toString()); + } + Object data = jsonMessage.getData(); + // + JSONArray jsonArray = (JSONArray) JSON.toJSON(data); + for (Object o : jsonArray) { + JSONObject jsonObject = (JSONObject) o; + jsonObject.put("nodeId", nodeModel.getId()); + // 自动 + if (!jsonObject.containsKey("triggerExecType")) { + jsonObject.put("triggerExecType", 1); + } + } + return jsonArray; + } + + @Override + public List lonelyDataArray(MachineNodeModel machineNodeModel) { + throw new IllegalStateException(I18nMessageUtil.get("i18n.unsupported_mode_with_script_log.6a7a")); + } + + @Override + public void syncAllNode() { + // + } + + /** + * 同步执行 同步节点信息(增量) + * + * @param nodeModel 节点信息 + * @return json + */ + public Collection syncExecuteNodeInc(NodeModel nodeModel) { + String nodeModelName = nodeModel.getName(); + if (!nodeModel.isOpenStatus()) { + log.debug(I18nMessageUtil.get("i18n.node_not_enabled.10ef"), nodeModelName); + return null; + } + try { + JSONArray jsonArray = this.getLitDataArray(nodeModel); + if (CollUtil.isEmpty(jsonArray)) { + // + return null; + } + // + List models = jsonArray.toJavaList(this.tClass) + .stream() + .filter(item -> { + if (StrUtil.equals(item.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL)) { + return true; + } + // 检查对应的工作空间 是否存在 + return workspaceService.exists(new WorkspaceModel(item.getWorkspaceId())); + }) + .filter(item -> { + if (StrUtil.equals(item.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL)) { + return true; + } + // 避免重复同步 + return StrUtil.equals(nodeModel.getWorkspaceId(), item.getWorkspaceId()); + }) + .collect(Collectors.toList()); + // 设置 临时缓存,便于放行检查 + BaseServerController.resetInfo(UserModel.EMPTY); + // + models.forEach(NodeScriptExecuteLogServer.super::upsert); + String template = I18nMessageUtil.get("i18n.physical_node_pull_records.99df"); + String format = StrUtil.format( + template, + nodeModelName, CollUtil.size(jsonArray), + CollUtil.size(models)); + log.debug(format); + return models.stream().map(BaseDbModel::getId).collect(Collectors.toList()); + } catch (Exception e) { + this.checkException(e, nodeModelName); + return null; + } finally { + BaseServerController.removeEmpty(); + } + } + + @Override + protected void executeClearImpl(int h2DbLogStorageCount) { + super.autoLoopClear("createTimeMillis", h2DbLogStorageCount, + null, + executeLogModel -> { + try { + NodeModel nodeModel = nodeService.getByKey(executeLogModel.getNodeId()); + JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.SCRIPT_DEL_LOG, + "id", executeLogModel.getScriptId(), "executeId", executeLogModel.getId()); + if (!jsonMessage.success()) { + log.warn("{} {} {}", executeLogModel.getNodeId(), executeLogModel.getScriptName(), jsonMessage); + return false; + } + return true; + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.auto_clear_data_errors.112f"), executeLogModel.getNodeId(), executeLogModel.getScriptName(), e); + return false; + } + }); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptServer.java b/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptServer.java new file mode 100644 index 0000000000..fbd2f35c3d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptServer.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node.script; + +import cn.hutool.db.Entity; +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.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.node.NodeScriptCacheModel; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseNodeService; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2019/8/16 + */ +@Service +public class NodeScriptServer extends BaseNodeService implements ITriggerToken { + + + public NodeScriptServer(NodeService nodeService, + WorkspaceService workspaceService) { + super(nodeService, workspaceService, I18nMessageUtil.get("i18n.script_template.1f77")); + } + + /** + * 查询操作脚本 模版的节点 + * + * @return nodeId list + */ + public List hasScriptNode() { + String sql = "select nodeId from " + super.getTableName() + " group by nodeId "; + List query = super.query(sql); + if (query == null) { + return null; + } + return query.stream().map(entity -> entity.getStr("nodeId")).collect(Collectors.toList()); + } + + @Override + public JSONObject getItem(NodeModel nodeModel, String id) { + return null; + } + + @Override + public JSONArray getLitDataArray(NodeModel nodeModel) { + 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/CommandExecLogService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/CommandExecLogService.java new file mode 100644 index 0000000000..19032bbc18 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/CommandExecLogService.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node.ssh; + +import cn.hutool.core.io.FileUtil; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.util.CommandUtil; +import org.springframework.stereotype.Service; + +import java.io.File; + +/** + * @author bwcx_jzy + * @since 2021/12/22 + */ +@Service +public class CommandExecLogService extends BaseWorkspaceService { + + @Override + protected void fillSelectResult(CommandExecLogModel data) { + if (data == null) { + return; + } + data.setHasLog(FileUtil.exist(data.logFile())); + } + + @Override + protected void executeClearImpl(int h2DbLogStorageCount) { + super.autoLoopClear("createTimeMillis", h2DbLogStorageCount, null, commandExecLogModel -> { + File file = commandExecLogModel.logFile(); + CommandUtil.systemFastDel(file); + File parentFile = file.getParentFile(); + boolean empty = FileUtil.isEmpty(parentFile); + if (empty) { + CommandUtil.systemFastDel(parentFile); + } + return true; + }); + } + + @Override + protected String[] clearTimeColumns() { + return super.clearTimeColumns(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshCommandService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshCommandService.java new file mode 100644 index 0000000000..1259392f07 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshCommandService.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node.ssh; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.extra.ssh.JschUtil; +import cn.hutool.system.SystemUtil; +import cn.keepbx.jpom.cron.ICron; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.assets.server.ScriptLibraryServer; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.model.data.CommandModel; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.plugins.JschUtils; +import org.dromara.jpom.script.CommandParam; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.StrictSyncFinisher; +import org.dromara.jpom.util.StringUtil; +import org.dromara.jpom.util.SyncFinisherUtil; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +/** + * 命令管理 + * + * @author : Arno + * @since : 2021/12/6 22:11 + */ +@Service +@Slf4j +public class SshCommandService extends BaseWorkspaceService implements ICron, ITriggerToken { + + private final SshService sshService; + private final CommandExecLogService commandExecLogService; + private final WorkspaceEnvVarService workspaceEnvVarService; + private final ScriptLibraryServer scriptLibraryServer; + + private static final byte[] LINE_BYTES = SystemUtil.getOsInfo().getLineSeparator().getBytes(CharsetUtil.CHARSET_UTF_8); + + public SshCommandService(SshService sshService, + CommandExecLogService commandExecLogService, + WorkspaceEnvVarService workspaceEnvVarService, + ScriptLibraryServer scriptLibraryServer) { + this.sshService = sshService; + this.commandExecLogService = commandExecLogService; + this.workspaceEnvVarService = workspaceEnvVarService; + this.scriptLibraryServer = scriptLibraryServer; + } + + @Override + public int insert(CommandModel commandModel) { + int count = super.insert(commandModel); + this.checkCron(commandModel); + return count; + } + + @Override + public int updateById(CommandModel info, HttpServletRequest request) { + int update = super.updateById(info, request); + if (update > 0) { + this.checkCron(info); + } + return update; + } + + @Override + public int delByKey(String keyValue, HttpServletRequest request) { + int delByKey = super.delByKey(keyValue, request); + if (delByKey > 0) { + String taskId = "ssh_command:" + keyValue; + CronUtils.remove(taskId); + } + return delByKey; + } + + /** + * 检查定时任务 状态 + * + * @param buildInfoModel 构建信息 + */ + @Override + public boolean checkCron(CommandModel buildInfoModel) { + String id = buildInfoModel.getId(); + String taskId = "ssh_command:" + id; + String autoExecCron = buildInfoModel.getAutoExecCron(); + autoExecCron = StringUtil.parseCron(autoExecCron); + if (StrUtil.isEmpty(autoExecCron)) { + CronUtils.remove(taskId); + return false; + } + log.debug("start ssh command cron {} {} {}", id, buildInfoModel.getName(), autoExecCron); + CronUtils.upsert(taskId, autoExecCron, new SshCommandService.CronTask(id)); + return true; + } + + /** + * 开启定时构建任务 + */ + @Override + public List queryStartingList() { + String sql = "select * from " + super.getTableName() + " where autoExecCron is not null and autoExecCron <> ''"; + return super.queryList(sql); + } + + @Override + public String typeName() { + return getTableName(); + } + + private class CronTask implements Task { + + private final String id; + + public CronTask(String id) { + this.id = id; + } + + @Override + public void execute() { + try { + BaseServerController.resetInfo(UserModel.EMPTY); + CommandModel commandModel = SshCommandService.this.getByKey(this.id); + SshCommandService.this.executeBatch(commandModel, commandModel.getDefParams(), commandModel.getSshIds(), 1); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_command_template_exception.4e01"), e); + } finally { + BaseServerController.removeEmpty(); + } + } + } + + /** + * 批量执行命令 + * + * @param id 命令 id + * @param nodes ssh节点 + * @param params 参数 + * @return 批次ID + */ + public String executeBatch(String id, String params, String nodes) { + CommandModel commandModel = this.getByKey(id); + return this.executeBatch(commandModel, params, nodes, 0); + } + + /** + * 批量执行命令 + * + * @param commandModel 命令模版 + * @param nodes ssh节点 + * @param params 参数 + * @return 批次ID + */ + public String executeBatch(CommandModel commandModel, String params, String nodes, int triggerExecType) { + return executeBatch(commandModel, params, nodes, triggerExecType, null); + } + + /** + * 批量执行命令 + * + * @param commandModel 命令模版 + * @param nodes ssh节点 + * @param params 参数 + * @param envMap 环境变量 + * @param triggerExecType 触发方式 + * @return 批次ID + */ + public String executeBatch(CommandModel commandModel, String params, String nodes, int triggerExecType, Map envMap) { + Assert.notNull(commandModel, I18nMessageUtil.get("i18n.no_corresponding_command.165e")); + List sshIds = StrUtil.split(nodes, StrUtil.COMMA, true, true); + Assert.notEmpty(sshIds, I18nMessageUtil.get("i18n.ssh_node_required.4566")); + String batchId = IdUtil.fastSimpleUUID(); + String name = "ssh-command-batch:" + batchId; + StrictSyncFinisher syncFinisher = SyncFinisherUtil.create(name, sshIds.size()); + for (String sshId : sshIds) { + this.executeItem(syncFinisher, commandModel, params, sshId, batchId, triggerExecType, envMap); + } + I18nThreadUtil.execute(() -> { + try { + syncFinisher.start(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.ssh_batch_command_execution_exception.029a"), e); + } finally { + SyncFinisherUtil.close(name); + } + }); + return batchId; + } + + /** + * 准备执行 某一个 + * + * @param syncFinisher 线程同步器 + * @param commandModel 命令模版 + * @param commandParams 参数 + * @param sshId ssh id + * @param batchId 批次ID + */ + private void executeItem(StrictSyncFinisher syncFinisher, CommandModel commandModel, String commandParams, String sshId, String batchId, int triggerExecType, Map envMap) { + SshModel sshModel = sshService.getByKey(sshId, false); + + CommandExecLogModel commandExecLogModel = new CommandExecLogModel(); + commandExecLogModel.setCommandId(commandModel.getId()); + commandExecLogModel.setCommandName(commandModel.getName()); + commandExecLogModel.setBatchId(batchId); + commandExecLogModel.setSshId(sshId); + commandExecLogModel.setWorkspaceId(commandModel.getWorkspaceId()); + commandExecLogModel.setTriggerExecType(triggerExecType); + if (sshModel != null) { + commandExecLogModel.setSshName(sshModel.getName()); + } else { + commandExecLogModel.setSshName(I18nMessageUtil.get("i18n.ssh_not_exist.08a2")); + } + commandExecLogModel.setStatus(CommandExecLogModel.Status.ING.getCode()); + // 拼接参数 + String commandParamsLine = CommandParam.toCommandLine(commandParams); + commandExecLogService.insert(commandExecLogModel); + + syncFinisher.addWorker(() -> { + try { + this.execute(commandModel, commandExecLogModel, sshModel, commandParamsLine, envMap); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.command_template_execution_link_exception.51cf"), e); + this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.SESSION_ERROR); + } + }); + } + + + /** + * 执行命令 + * + * @param commandModel 命令模版 + * @param commandExecLogModel 执行记录 + * @param sshModel ssh + * @param commandParamsLine 参数 + */ + private void execute(CommandModel commandModel, CommandExecLogModel commandExecLogModel, SshModel sshModel, String commandParamsLine, Map envMap) { + File file = commandExecLogModel.logFile(); + try (LogRecorder logRecorder = LogRecorder.builder().file(file).charset(CharsetUtil.CHARSET_UTF_8).build()) { + if (sshModel == null) { + logRecorder.systemError(I18nMessageUtil.get("i18n.ssh_does_not_exist.88d7")); + this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.ERROR, -100); + return; + } + EnvironmentMapBuilder environmentMapBuilder = workspaceEnvVarService.getEnv(commandModel.getWorkspaceId()); + environmentMapBuilder.put("JPOM_SSH_ID", sshModel.getId()); + environmentMapBuilder.put("JPOM_COMMAND_ID", commandModel.getId()); + environmentMapBuilder.putStr(envMap); + environmentMapBuilder.eachStr(logRecorder::system); + Map environment = environmentMapBuilder.environment(); + String commands = StringUtil.formatStrByMap(commandModel.getCommand(), environment); + // 替换全局脚本 + commands = scriptLibraryServer.referenceReplace(commands); + MachineSshModel machineSshModel = sshService.getMachineSshModel(sshModel); + // + 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(I18nMessageUtil.get("i18n.exit_code.ea65"), exitCode); + // 更新状态 + this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.DONE, exitCode); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.command_error.d0b4"), e); + // 更新状态 + this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.ERROR); + // 记录错误日志 + logRecorder.error(I18nMessageUtil.get("i18n.command_error.d0b4"), e); + } finally { + JschUtil.close(session); + } + } + } + + /** + * 修改执行状态 + * + * @param id ID + * @param status 状态 + */ + private void updateStatus(String id, CommandExecLogModel.Status status) { + this.updateStatus(id, status, null); + } + + /** + * 修改执行状态 + * + * @param id ID + * @param status 状态 + * @param exitCode 退出码 + */ + private void updateStatus(String id, CommandExecLogModel.Status status, Integer exitCode) { + CommandExecLogModel commandExecLogModel = new CommandExecLogModel(); + commandExecLogModel.setId(id); + commandExecLogModel.setExitCode(exitCode); + commandExecLogModel.setStatus(status.getCode()); + commandExecLogService.updateById(commandExecLogModel); + } + + /** + * 将ssh 脚本信息同步到其他工作空间 + * + * @param ids 多给节点ID + * @param nowWorkspaceId 当前的工作空间ID + * @param workspaceId 同步到哪个工作空间 + */ + public void syncToWorkspace(String ids, String nowWorkspaceId, String workspaceId) { + StrUtil.splitTrim(ids, StrUtil.COMMA) + .forEach(id -> { + CommandModel data = super.getByKey(id, false, entity -> entity.set("workspaceId", nowWorkspaceId)); + Assert.notNull(data, I18nMessageUtil.get("i18n.no_corresponding_ssh_script_info.1c12")); + // + CommandModel where = new CommandModel(); + where.setWorkspaceId(workspaceId); + where.setName(data.getName()); + CommandModel exits = super.queryByBean(where); + if (exits == null) { + // 不存在则添加 信息 + data.setId(null); + data.setWorkspaceId(workspaceId); + data.setCreateTimeMillis(null); + data.setModifyTimeMillis(null); + data.setSshIds(null); + data.setModifyUser(null); + super.insert(data); + } else { + // 修改信息 + CommandModel update = new CommandModel(); + update.setId(exits.getId()); + update.setCommand(data.getCommand()); + update.setDesc(data.getDesc()); + update.setDefParams(data.getDefParams()); + update.setAutoExecCron(data.getAutoExecCron()); + super.updateById(update); + } + }); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshService.java new file mode 100644 index 0000000000..89fe54fa25 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshService.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.node.ssh; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.ssh.ChannelType; +import cn.hutool.extra.ssh.JschUtil; +import cn.hutool.extra.ssh.Sftp; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.BuildExtConfig; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.model.data.SshModel; +import org.dromara.jpom.plugins.JschLogger; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.MySftp; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.annotation.Resource; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author bwcx_jzy + * @since 2021/12/4 + */ +@Service +@Slf4j +public class SshService extends BaseWorkspaceService { + + @Resource + @Lazy + private MachineSshServer machineSshServer; + private final BuildExtConfig buildExtConfig; + + public SshService(BuildExtConfig buildExtConfig) { + this.buildExtConfig = buildExtConfig; + JSch.setLogger(JschLogger.LOGGER); + } + + @Override + protected void fillSelectResult(SshModel data) { + if (data == null) { + return; + } + if (!StrUtil.startWithIgnoreCase(data.getPassword(), ServerConst.REF_WORKSPACE_ENV)) { + // 隐藏密码字段 + data.setPassword(null); + } + //data.setPassword(null); + data.setPrivateKey(null); + } + + @Override + protected void fillInsert(SshModel sshModel) { + super.fillInsert(sshModel); + sshModel.setHost(StrUtil.EMPTY); + sshModel.setUser(StrUtil.EMPTY); + sshModel.setPort(0); + } + + /** + * 获取 ssh 回话 + * + * @param sshModel sshModel + * @return session + */ + public Session getSessionByModel(SshModel sshModel) { + MachineSshModel machineSshModel = this.getMachineSshModel(sshModel); + return machineSshServer.getSessionByModelNoFill(machineSshModel); + } + + /** + * 获取 ssh 回话 + * + * @param sshModel sshModel + * @return session + */ + public Session getSessionByModel(MachineSshModel sshModel) { + return machineSshServer.getSessionByModelNoFill(sshModel); + } + + + /** + * 获取 ssh 配置对象 + * + * @param sshModel sshModel + * @return session + */ + public MachineSshModel getMachineSshModel(SshModel sshModel) { + MachineSshModel sshModel1 = machineSshServer.getByKey(sshModel.getMachineSshId(), false); + Assert.notNull(sshModel1, I18nMessageUtil.get("i18n.asset_ssh_not_exist.cd43")); + return sshModel1; + } + + + /** + * 上传文件 + * + * @param machineSshModel ssh + * @param remotePath 远程路径 + * @param desc 文件夹或者文件 + */ + public void uploadDir(MachineSshModel machineSshModel, String remotePath, File desc) { + Session session = null; + ChannelSftp channel = null; + // MachineSshModel machineSshModel = this.getMachineSshModel(sshModel); + try { + session = this.getSessionByModel(machineSshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + try (Sftp sftp = new Sftp(channel, machineSshModel.charset(), machineSshModel.timeout())) { + sftp.syncUpload(desc, remotePath); + } + //uploadDir(channel, remotePath, desc, sshModel.getCharsetT()); + } finally { + JschUtil.close(channel); + JschUtil.close(session); + } + } + + /** + * 下载文件 + * + * @param sshModel 实体 + * @param remoteFile 远程文件 + * @param save 文件对象 + * @throws IOException io + * @throws SftpException sftp + */ + public void download(SshModel sshModel, String remoteFile, File save) throws IOException, SftpException { + Session session = null; + ChannelSftp channel = null; + OutputStream output = null; + try { + session = this.getSessionByModel(sshModel); + channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP); + output = Files.newOutputStream(save.toPath()); + channel.get(remoteFile, output); + } finally { + IoUtil.close(output); + JschUtil.close(channel); + JschUtil.close(session); + } + } + + /** + * 将ssh信息同步到其他工作空间 + * + * @param ids 多给节点ID + * @param nowWorkspaceId 当前的工作空间ID + * @param workspaceId 同步到哪个工作空间 + */ + public void syncToWorkspace(String ids, String nowWorkspaceId, String workspaceId) { + StrUtil.splitTrim(ids, StrUtil.COMMA) + .forEach(id -> { + SshModel data = super.getByKey(id, false, entity -> entity.set("workspaceId", nowWorkspaceId)); + Assert.notNull(data, I18nMessageUtil.get("i18n.no_corresponding_ssh_info.d864")); + // + SshModel where = new SshModel(); + where.setWorkspaceId(workspaceId); + where.setMachineSsh(data.getMachineSsh()); + SshModel sshModel = super.queryByBean(where); + Assert.isNull(sshModel, I18nMessageUtil.get("i18n.workspace_ssh_already_exists.ccc0")); + // 不存在则添加 信息 + data.setId(null); + data.setWorkspaceId(workspaceId); + data.setCreateTimeMillis(null); + data.setModifyTimeMillis(null); + data.setModifyUser(null); + data.setHost(null); + data.setUser(null); + data.setPassword(null); + data.setPort(null); + data.setConnectType(null); + data.setCharset(null); + data.setPrivateKey(null); + data.setTimeout(null); + super.insert(data); + }); + } + + public long countByMachine(String machineSshId) { + SshModel nodeModel = new SshModel(); + nodeModel.setMachineSshId(machineSshId); + return this.count(nodeModel); + } + + public void existsSsh(String workspaceId, String machineSshId) { + // + SshModel where = new SshModel(); + where.setWorkspaceId(workspaceId); + where.setMachineSshId(machineSshId); + SshModel data = this.queryByBean(where); + Assert.isNull(data, () -> I18nMessageUtil.get("i18n.ssh_already_exists_in_workspace.569e") + data.getName()); + } + + public boolean existsSsh2(String workspaceId, String machineSshId) { + // + SshModel where = new SshModel(); + where.setWorkspaceId(workspaceId); + where.setMachineSshId(machineSshId); + return this.exists(where); + } + + public void insert(MachineSshModel machineSshModel, String workspaceId) { + SshModel data = new SshModel(); + data.setWorkspaceId(workspaceId); + data.setName(machineSshModel.getName()); + data.setGroup(machineSshModel.getGroupName()); + data.setMachineSshId(machineSshModel.getId()); + this.insert(data); + } + + /** + * 创建文件上传 进度 + * + * @param logRecorder 日志记录器 + * @return 进度监听 + */ + public MySftp.ProgressMonitor createProgressMonitor(LogRecorder logRecorder) { + Set progressRangeList = ConcurrentHashMap.newKeySet((int) Math.floor((float) 100 / buildExtConfig.getLogReduceProgressRatio())); + return new MySftp.ProgressMonitor() { + @Override + public void rest() { + progressRangeList.clear(); + } + + @Override + public void progress(String desc, long max, long now) { + double progressPercentage = Math.floor(((float) now / max) * 100); + int progressRange = (int) Math.floor(progressPercentage / buildExtConfig.getLogReduceProgressRatio()); + if (progressRangeList.add(progressRange)) { + // total, progressSize + logRecorder.system(I18nMessageUtil.get("i18n.upload_progress_with_units.44ad"), desc, + FileUtil.readableFileSize(now), FileUtil.readableFileSize(max), + NumberUtil.formatPercent(((float) now / max), 0) + ); + } + } + }; + } + + public SshModel getByMachineSshId(String id) { + SshModel model = new SshModel(); + model.setMachineSshId(id); + return queryByBean(model); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/tomcat/TomcatService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/tomcat/TomcatService.java new file mode 100644 index 0000000000..fde71f69ab --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/tomcat/TomcatService.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//package io.jpom.service.node.tomcat; +// +//import com.alibaba.fastjson2.JSONArray; +//import com.alibaba.fastjson2.JSONObject; +//import io.jpom.common.forward.NodeForward; +//import io.jpom.common.forward.NodeUrl; +//import org.dromara.jpom.model.data.NodeModel; +//import io.jpom.service.node.NodeService; +//import org.springframework.stereotype.Service; +//import org.springframework.web.multipart.MultipartHttpServletRequest; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +///** +// * tomcat +// * +// * @author lf +// */ +//@Service +//public class TomcatService { +// +// +// private final NodeService nodeService; +// +// public TomcatService(NodeService nodeService) { +// this.nodeService = nodeService; +// } +// +// /** +// * 查询tomcat列表 +// * +// * @param nodeModel 节点信息 +// * @return tomcat的信息 +// */ +// public JSONArray getTomcatList(NodeModel nodeModel) { +// if (!nodeModel.isOpenStatus()) { +// return null; +// } +// return NodeForward.requestData(nodeModel, NodeUrl.Tomcat_List, JSONArray.class, null, null); +// } +// +// /** +// * 查询tomcat信息 +// * +// * @param nodeModel 节点信息 +// * @param id tomcat的id +// * @return tomcat的信息 +// */ +// public JSONObject getTomcatInfo(NodeModel nodeModel, String id) { +// return NodeForward.requestData(nodeModel, NodeUrl.Tomcat_GetItem, JSONObject.class, "id", id); +// } +// +// +// public JSONArray getTomcatProjectList(NodeModel nodeModel, String id) { +// return NodeForward.requestData(nodeModel, NodeUrl.Tomcat_GetTomcatProjectList, JSONArray.class, "id", id); +// } +// +// /** +// * tomcat项目管理 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String tomcatProjectManage(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_TomcatProjectManage).toString(); +// } +// +// /** +// * 新增Tomcat +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String addTomcat(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Add).toString(); +// } +// +// /** +// * 更新Tomcat信息 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String updateTomcat(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Update).toString(); +// } +// +// /** +// * 查询tomcat运行状态 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String getTomcatStatus(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_GetTomcatStatus).toString(); +// } +// +// /** +// * 启动tomcat +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String start(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Start).toString(); +// } +// +// /** +// * 停止tomcat +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String stop(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Stop).toString(); +// } +// +// /** +// * 重启tomcat +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String restart(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Restart).toString(); +// } +// +// /** +// * 删除tomcat +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String delete(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_Delete).toString(); +// } +// +// /** +// * 获取文件列表 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String getFileList(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_File_GetFileList).toString(); +// } +// +// /** +// * 上传文件 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String upload(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_File_Upload).toString(); +// } +// +// /** +// * 下载文件 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @param response 响应信息 +// */ +// public void download(NodeModel nodeModel, HttpServletRequest request, HttpServletResponse response) { +// NodeForward.requestDownload(nodeModel, request, response, NodeUrl.Tomcat_File_Download); +// } +// +// /** +// * 删除文件 +// * +// * @param nodeModel 节点信息 +// * @param request 请求信息 +// * @return 操作结果 +// */ +// public String deleteFile(NodeModel nodeModel, HttpServletRequest request) { +// return NodeForward.request(nodeModel, request, NodeUrl.Tomcat_File_DeleteFile).toString(); +// } +// +// /** +// * 上传War包 +// * +// * @param node 节点信息 +// * @param multiRequest 请求信息 +// * @return 操作结果 +// */ +// public String uploadWar(NodeModel node, MultipartHttpServletRequest multiRequest) { +// return NodeForward.requestMultipart(node, multiRequest, NodeUrl.Tomcat_File_UploadWar).toString(); +// } +//} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/outgiving/DbOutGivingLogService.java b/modules/server/src/main/java/org/dromara/jpom/service/outgiving/DbOutGivingLogService.java new file mode 100644 index 0000000000..04bae5495d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/outgiving/DbOutGivingLogService.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.outgiving; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.db.sql.Direction; +import cn.hutool.db.sql.Order; +import org.dromara.jpom.model.log.OutGivingLog; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 分发日志 + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@Service +public class DbOutGivingLogService extends BaseWorkspaceService { + + + /** + * 查询最新的分发日志 + * + * @param outId 分发id + * @param nodeProject 项目信息 + * @return log + */ + public OutGivingLog getByProject(String outId, OutGivingNodeProject nodeProject) { + OutGivingLog outGivingLog = new OutGivingLog(); + outGivingLog.setOutGivingId(outId); + outGivingLog.setNodeId(nodeProject.getNodeId()); + outGivingLog.setProjectId(nodeProject.getProjectId()); + List givingLogs = this.queryList(outGivingLog, 1, new Order("createTimeMillis", Direction.DESC)); + return CollUtil.getFirst(givingLogs); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/outgiving/LogReadServer.java b/modules/server/src/main/java/org/dromara/jpom/service/outgiving/LogReadServer.java new file mode 100644 index 0000000000..3545594e7d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/outgiving/LogReadServer.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.outgiving; + +import org.dromara.jpom.model.outgiving.LogReadModel; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2022/5/15 + */ +@Service +public class LogReadServer extends BaseWorkspaceService { + + + public void checkNodeProject(String nodeId, String projectId, HttpServletRequest request, String msg) { + // 检查节点分发 + List outGivingModels = super.listByWorkspace(request); + if (outGivingModels != null) { + boolean match = outGivingModels.stream().anyMatch(outGivingModel -> outGivingModel.checkContains(nodeId, projectId)); + Assert.state(!match, msg); + } + } + + public boolean checkNode(String nodeId, HttpServletRequest request) { + List list = super.listByWorkspace(request); + if (list == null || list.isEmpty()) { + return false; + } + for (LogReadModel outGivingModel : list) { + List items = outGivingModel.nodeProjectList(); + if (items != null) { + for (LogReadModel.Item item : items) { + if (item.getNodeId().equals(nodeId)) { + return true; + } + } + } + } + return false; + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/outgiving/OutGivingServer.java b/modules/server/src/main/java/org/dromara/jpom/service/outgiving/OutGivingServer.java new file mode 100644 index 0000000000..5197b6ccd6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/outgiving/OutGivingServer.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.outgiving; + +import org.dromara.jpom.model.outgiving.OutGivingModel; +import org.dromara.jpom.model.outgiving.OutGivingNodeProject; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 分发管理 + * + * @author bwcx_jzy + * @since 2019/4/21 + */ +@Service +public class OutGivingServer extends BaseWorkspaceService implements IStatusRecover { + + + public void checkNodeProject(String nodeId, String projectId, HttpServletRequest request, String msg) { + // 检查节点分发 + List outGivingModels = super.listByWorkspace(request); + if (outGivingModels != null) { + boolean match = outGivingModels.stream().anyMatch(outGivingModel -> outGivingModel.checkContains(nodeId, projectId)); + Assert.state(!match, msg); + } + } + + public boolean checkNode(String nodeId, HttpServletRequest request) { + List list = super.listByWorkspace(request); + if (list == null || list.isEmpty()) { + return false; + } + for (OutGivingModel outGivingModel : list) { + List outGivingNodeProjectList = outGivingModel.outGivingNodeProjectList(); + if (outGivingNodeProjectList != null) { + for (OutGivingNodeProject outGivingNodeProject : outGivingNodeProjectList) { + if (outGivingNodeProject.getNodeId().equals(nodeId)) { + return true; + } + } + } + } + return false; + } + + @Override + public int statusRecover() { + // 恢复异常数据 + String updateSql = "update " + super.getTableName() + " set status=? where status=?"; + return super.execute(updateSql, OutGivingModel.Status.DONE.getCode(), OutGivingModel.Status.ING.getCode()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/script/ScriptExecuteLogServer.java b/modules/server/src/main/java/org/dromara/jpom/service/script/ScriptExecuteLogServer.java new file mode 100644 index 0000000000..5dc8dcac0c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/script/ScriptExecuteLogServer.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.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 org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.func.assets.server.ScriptLibraryServer; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.model.script.ScriptExecuteLogModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.service.h2db.BaseGlobalOrWorkspaceService; +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.io.InputStream; + +/** + * @author bwcx_jzy + * @since 2022/1/19 + */ +@Service +public class ScriptExecuteLogServer extends BaseGlobalOrWorkspaceService { + + private final ScriptLibraryServer scriptLibraryServer; + private final JpomApplication jpomApplication; + + public ScriptExecuteLogServer(ScriptLibraryServer scriptLibraryServer, + JpomApplication jpomApplication) { + this.scriptLibraryServer = scriptLibraryServer; + this.jpomApplication = jpomApplication; + } + + /** + * 创建执行记录 + * + * @param scriptModel 脚本 + * @param type 执行类型 + * @return 对象 + */ + public ScriptExecuteLogModel create(ScriptModel scriptModel, int type) { + return this.create(scriptModel, type, scriptModel.getWorkspaceId()); + } + + + /** + * 创建执行记录 + * + * @param scriptModel 脚本 + * @param type 执行类型 + * @return 对象 + */ + public ScriptExecuteLogModel create(ScriptModel scriptModel, int type, String workspaceId) { + ScriptExecuteLogModel scriptExecuteLogModel = new ScriptExecuteLogModel(); + scriptExecuteLogModel.setScriptId(scriptModel.getId()); + scriptExecuteLogModel.setScriptName(scriptModel.getName()); + scriptExecuteLogModel.setTriggerExecType(type); + scriptExecuteLogModel.setWorkspaceId(workspaceId); + super.insert(scriptExecuteLogModel); + return scriptExecuteLogModel; + } + + /** + * 修改执行状态 + * + * @param id ID + * @param status 状态 + */ + public void updateStatus(String id, CommandExecLogModel.Status status) { + this.updateStatus(id, status, null); + } + + /** + * 修改执行状态 + * + * @param id ID + * @param status 状态 + * @param exitCode 退出码 + */ + public void updateStatus(String id, CommandExecLogModel.Status status, Integer exitCode) { + ScriptExecuteLogModel model = new ScriptExecuteLogModel(); + model.setId(id); + model.setExitCode(exitCode); + model.setStatus(status.getCode()); + this.updateById(model); + } + + /** + * 加载脚本文件 + * + * @param scriptModel 脚本对象 + * @return file + */ + public File toExecLogFile(ScriptModel scriptModel) { + InputStream templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX); + String defaultTemplate = IoUtil.readUtf8(templateInputStream); + String context = defaultTemplate + scriptModel.getContext(); + // 替换全局变量 + context = scriptLibraryServer.referenceReplace(context); + // + String dataPath = jpomApplication.getDataPath(); + File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX)); + FileUtils.writeScript(context, scriptFile, ExtConfigBean.getConsoleLogCharset()); + return scriptFile; + } + + + @Override + protected void executeClearImpl(int h2DbLogStorageCount) { + super.autoLoopClear("createTimeMillis", h2DbLogStorageCount, null, scriptExecuteLogModel -> { + File logFile = ScriptModel.logFile(scriptExecuteLogModel.getScriptId(), scriptExecuteLogModel.getId()); + boolean fastDel = CommandUtil.systemFastDel(logFile); + return !fastDel; + }); + } + + @Override + protected String[] clearTimeColumns() { + return super.clearTimeColumns(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/script/ScriptServer.java b/modules/server/src/main/java/org/dromara/jpom/service/script/ScriptServer.java new file mode 100644 index 0000000000..e903e25af6 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/script/ScriptServer.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.script; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.cron.ICron; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.model.script.ScriptExecuteLogModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseGlobalOrWorkspaceService; +import org.dromara.jpom.socket.ServerScriptProcessBuilder; +import org.dromara.jpom.util.StringUtil; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2022/1/19 + */ +@Service +@Slf4j +public class ScriptServer extends BaseGlobalOrWorkspaceService implements ICron, ITriggerToken { + + @Override + public List queryStartingList() { + String sql = "select * from " + super.getTableName() + " where autoExecCron is not null and autoExecCron <> ''"; + return super.queryList(sql); + } + + @Override + public int insert(ScriptModel scriptModel) { + int count = super.insert(scriptModel); + this.checkCron(scriptModel); + return count; + } + + @Override + public int updateById(ScriptModel info, HttpServletRequest request) { + int update = super.updateById(info, request); + if (update > 0) { + this.checkCron(info); + } + return update; + } + + @Override + public int delByKey(String keyValue, HttpServletRequest request) { + int delByKey = super.delByKey(keyValue, request); + if (delByKey > 0) { + String taskId = "server_script:" + keyValue; + CronUtils.remove(taskId); + } + return delByKey; + } + + /** + * 检查定时任务 状态 + * + * @param scriptModel 构建信息 + */ + @Override + public boolean checkCron(ScriptModel scriptModel) { + String id = scriptModel.getId(); + String taskId = "server_script:" + id; + String autoExecCron = scriptModel.getAutoExecCron(); + autoExecCron = StringUtil.parseCron(autoExecCron); + if (StrUtil.isEmpty(autoExecCron)) { + CronUtils.remove(taskId); + return false; + } + log.debug("start script cron {} {} {}", id, scriptModel.getName(), autoExecCron); + CronUtils.upsert(taskId, autoExecCron, new CronTask(id)); + return true; + } + + + /** + * 将服务端 脚本信息同步到其他工作空间 + * + * @param ids 多给节点ID + * @param nowWorkspaceId 当前的工作空间ID + * @param workspaceId 同步到哪个工作空间 + */ + public void syncToWorkspace(String ids, String nowWorkspaceId, String workspaceId) { + StrUtil.splitTrim(ids, StrUtil.COMMA) + .forEach(id -> { + ScriptModel data = super.getByKey(id, false, entity -> entity.set("workspaceId", nowWorkspaceId)); + Assert.notNull(data, I18nMessageUtil.get("i18n.no_corresponding_script_info_or_global_script_selected.765b")); + // + ScriptModel where = new ScriptModel(); + where.setWorkspaceId(workspaceId); + where.setName(data.getName()); + ScriptModel exits = super.queryByBean(where); + if (exits == null) { + // 不存在则添加 信息 + data.setId(null); + data.setWorkspaceId(workspaceId); + data.setCreateTimeMillis(null); + data.setModifyTimeMillis(null); + data.setNodeIds(null); + data.setModifyUser(null); + super.insert(data); + } else { + // 修改信息 + ScriptModel update = new ScriptModel(); + update.setId(exits.getId()); + update.setContext(data.getContext()); + update.setDefArgs(data.getDefArgs()); + update.setDescription(data.getDescription()); + update.setAutoExecCron(data.getAutoExecCron()); + super.updateById(update); + } + }); + } + + @Override + public String typeName() { + return getTableName(); + } + + private static class CronTask implements Task { + + private final String id; + + public CronTask(String id) { + this.id = id; + } + + @Override + public void execute() { + try { + BaseServerController.resetInfo(UserModel.EMPTY); + ScriptServer nodeScriptServer = SpringUtil.getBean(ScriptServer.class); + ScriptModel scriptServerItem = nodeScriptServer.getByKey(id); + if (scriptServerItem == null) { + return; + } + // 创建记录 + ScriptExecuteLogServer execLogServer = SpringUtil.getBean(ScriptExecuteLogServer.class); + ScriptExecuteLogModel nodeScriptExecLogModel = execLogServer.create(scriptServerItem, 1); + // 执行 + ServerScriptProcessBuilder.create(scriptServerItem, nodeScriptExecLogModel.getId(), scriptServerItem.getDefArgs()); + } finally { + BaseServerController.removeEmpty(); + } + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/system/SystemParametersServer.java b/modules/server/src/main/java/org/dromara/jpom/service/system/SystemParametersServer.java new file mode 100644 index 0000000000..673ab50991 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/system/SystemParametersServer.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.system; + +import cn.hutool.core.util.ReflectUtil; +import cn.keepbx.jpom.model.BaseJsonModel; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.SystemParametersModel; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.stereotype.Service; + +import java.util.function.Function; + +/** + * @author bwcx_jzy + * @since 2021/12/2 + */ +@Service +@Slf4j +public class SystemParametersServer extends BaseDbService { + + + /** + * 先尝试更新,更新失败尝试插入 + * + * @param name 参数名称 + * @param jsonModel 参数值 + * @param desc 描述 + */ + public void upsert(String name, BaseJsonModel jsonModel, String desc) { + SystemParametersModel systemParametersModel = new SystemParametersModel(); + systemParametersModel.setId(name); + systemParametersModel.setValue(jsonModel.toJson().toString()); + systemParametersModel.setDescription(desc); + super.upsert(systemParametersModel); + } + + /** + * 先尝试更新,更新失败尝试插入 + * + * @param name 参数名称 + * @param data 参数值 + * @param desc 描述 + */ + public void upsert(String name, Object data, String desc) { + SystemParametersModel systemParametersModel = new SystemParametersModel(); + systemParametersModel.setId(name); + systemParametersModel.setValue(JSONObject.toJSONString(data)); + systemParametersModel.setDescription(desc); + super.upsert(systemParametersModel); + } + + /** + * 查询 系统参数 值 + * + * @param name 参数名称 + * @param cls 类 + * @param 泛型 + * @return data + */ + public T getConfig(String name, Class cls) { + return this.getConfig(name, cls, null); + } + + /** + * 查询 系统参数 值 + * + * @param name 参数名称 + * @param cls 类 + * @param mapTo 回调 + * @param 泛型 + * @return data + */ + public T getConfig(String name, Class cls, Function mapTo) { + SystemParametersModel parametersModel = super.getByKey(name); + if (parametersModel == null) { + return null; + } + T jsonToBean = parametersModel.jsonToBean(cls); + if (mapTo == null) { + return jsonToBean; + } + return mapTo.apply(jsonToBean); + } + + /** + * 查询系统参数值,没有数据创建一个空对象 + * + * @param name 参数名称 + * @param cls 类 + * @param 泛型 + * @return data + */ + public T getConfigDefNewInstance(String name, Class cls) { + T config; + try { + config = this.getConfig(name, cls); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.read_system_parameter_exception.ee72"), e); + return ReflectUtil.newInstance(cls); + } + return config == null ? ReflectUtil.newInstance(cls) : config; + } +} 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 new file mode 100644 index 0000000000..15e99e81e3 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/system/WhitelistDirectoryService.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.system; + +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.AgentWhitelist; +import org.dromara.jpom.model.data.NodeModel; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 授权 + * + * @author bwcx_jzy + * @since 2019/4/16 + */ +@Service +public class WhitelistDirectoryService { + + public AgentWhitelist getData(NodeModel model) { + return NodeForward.requestData(model, NodeUrl.WhitelistDirectory_data, null, AgentWhitelist.class); + } + + public AgentWhitelist getData(MachineNodeModel machineNodeModel) { + return NodeForward.requestData(machineNodeModel, NodeUrl.WhitelistDirectory_data, null, AgentWhitelist.class); + } + + /** + * 获取项目路径授权 + * + * @param model 实体 + * @return project + */ + public List getProjectDirectory(NodeModel model) { + AgentWhitelist agentWhitelist = getData(model); + if (agentWhitelist == null) { + return null; + } + return agentWhitelist.getProject(); + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/system/WorkspaceEnvVarService.java b/modules/server/src/main/java/org/dromara/jpom/service/system/WorkspaceEnvVarService.java new file mode 100644 index 0000000000..631dc99094 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/system/WorkspaceEnvVarService.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.system; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.data.WorkspaceEnvVarModel; +import org.dromara.jpom.service.ITriggerToken; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2021/12/10 + */ +@Service +public class WorkspaceEnvVarService extends BaseWorkspaceService implements ITriggerToken { + + + /** + * 获取我所有的空间 + * + * @param request 请求对象 + * @return page + */ + @Override + public PageResultDto listPage(HttpServletRequest request) { + // 验证工作空间权限 + Map paramMap = ServletUtil.getParamMap(request); + String workspaceIds = BaseWorkspaceService.getWorkspaceId(request); + List split = StrUtil.split(workspaceIds, StrUtil.COMMA); + for (String workspaceId : split) { + checkUserWorkspace(workspaceId); + } + paramMap.remove("workspaceId"); + paramMap.put("workspaceId:in", workspaceIds); + return super.listPage(paramMap); + } + + /** + * 获取工作空间下面的所有环境变量 + * + * @param workspaceId 工作空间ID + * @return map + */ + public EnvironmentMapBuilder getEnv(String workspaceId) { + Entity entity = Entity.create(); + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + List list = super.listByEntity(entity); + Map map = CollStreamUtil.toMap(list, WorkspaceEnvVarModel::getName, workspaceEnvVarModel -> { + Integer privacy = workspaceEnvVarModel.getPrivacy(); + return new EnvironmentMapBuilder.Item(workspaceEnvVarModel.getValue(), privacy != null && privacy == 1); + }); + // java.lang.UnsupportedOperationException + return EnvironmentMapBuilder.builder(map); + } + + /** + * 转化 工作空间环境变量 + * + * @param workspaceId 工作空间 + * @param value 值 + * @return 如果存在值,则返回环境变量值。不存在则返回原始值 + */ + public String convertRefEnvValue(String workspaceId, String value) { + // "$ref.wEnv." + if (StrUtil.isEmpty(value) || !StrUtil.startWithIgnoreCase(value, ServerConst.REF_WORKSPACE_ENV)) { + return value; + } + Entity entity = Entity.create(); + entity.set("name", StrUtil.removePrefixIgnoreCase(value, ServerConst.REF_WORKSPACE_ENV)); + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + List modelList = super.listByEntity(entity); + WorkspaceEnvVarModel workspaceEnvVarModel = CollUtil.getFirst(modelList); + if (workspaceEnvVarModel == null) { + return value; + } + return workspaceEnvVarModel.getValue(); + } + + @Override + public List listByWorkspace(HttpServletRequest request) { + String workspaceIds = BaseWorkspaceService.getWorkspaceId(request); + List split = StrUtil.splitTrim(workspaceIds, StrUtil.COMMA); + for (String workspaceId : split) { + checkUserWorkspace(workspaceId); + } + Entity entity = Entity.create(); + entity.set("workspaceId", split); + List entities = super.queryList(entity); + return super.entityToBeanList(entities); + } + + @Override + public String typeName() { + return getTableName(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/system/WorkspaceService.java b/modules/server/src/main/java/org/dromara/jpom/service/system/WorkspaceService.java new file mode 100644 index 0000000000..7b0c085d50 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/system/WorkspaceService.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.system; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * @author bwcx_jzy + * @since 2021/12/3 + */ +@Service +@Slf4j +public class WorkspaceService extends BaseDbService implements IStatusRecover { + + @Override + public int statusRecover() { + WorkspaceModel workspaceModel = super.getByKey(Const.WORKSPACE_DEFAULT_ID); + if (workspaceModel == null) { + WorkspaceModel defaultWorkspace = new WorkspaceModel(); + defaultWorkspace.setId(Const.WORKSPACE_DEFAULT_ID); + defaultWorkspace.setName(Const.DEFAULT_GROUP_NAME.get()); + defaultWorkspace.setDescription(I18nMessageUtil.get("i18n.default_workspace_cannot_delete.18b4")); + super.insert(defaultWorkspace); + log.info(I18nMessageUtil.get("i18n.initialize_workspace.bc97"), Const.DEFAULT_GROUP_NAME.get()); + } + + Set> classes = BaseWorkspaceModel.allClass(); + int total = 0; + for (Class aClass : classes) { + TableName tableName = aClass.getAnnotation(TableName.class); + if (tableName == null) { + continue; + } + String sql = "update " + tableName.value() + " set workspaceId=? where (workspaceId is null or workspaceId='' or workspaceId='null')"; + int execute = this.execute(sql, Const.WORKSPACE_DEFAULT_ID); + if (execute > 0) { + log.info(I18nMessageUtil.get("i18n.fix_null_workspace_data.4d0b"), tableName.value(), execute); + } + total += execute; + } + return total; + } +} 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 new file mode 100644 index 0000000000..6480089b02 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/user/TriggerTokenLogServer.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.user; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import cn.keepbx.jpom.event.ISystemTask; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.PageResultDto; +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.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2022/7/22 + */ +@Service +@Slf4j +public class TriggerTokenLogServer extends BaseDbService implements ISystemTask { + + private final UserService userService; + private final List triggerTokens; + private final Map triggerTokenMap; + + public TriggerTokenLogServer(UserService userService, + List triggerTokens) { + this.userService = userService; + this.triggerTokens = triggerTokens; + triggerTokenMap = CollStreamUtil.toMap(triggerTokens, ITriggerToken::typeName, iTriggerToken -> iTriggerToken); + } + + /** + * 获取类型 + * + * @param type 类型名称 + * @return 接口 + */ + public ITriggerToken getByType(String type) { + return MapUtil.get(triggerTokenMap, type, ITriggerToken.class); + } + + /** + * 删除触发器 + * + * @param id Id + */ + public void delete(String id) { + TriggerTokenLogBean tokenLogBean = this.getByKey(id); + if (tokenLogBean == null) { + return; + } + ITriggerToken token = triggerTokens.stream() + .filter(iTriggerToken -> StrUtil.equals(iTriggerToken.typeName(), tokenLogBean.getType())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(I18nMessageUtil.get("i18n.no_trigger_type_specified.5628") + tokenLogBean.getType())); + String sql = "update " + tokenLogBean.getType() + " set triggerToken='' where id=?"; + this.execute(sql, tokenLogBean.getDataId()); + this.delByKey(id); + } + + /** + * 获取所有类型 + * + * @return list + */ + public List allType() { + return triggerTokens.stream() + .map(iTriggerToken -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", iTriggerToken.typeName()); + jsonObject.put("desc", iTriggerToken.getDataDesc()); + return jsonObject; + }) + .collect(Collectors.toList()); + } + + /** + * 通过用户ID 删除数据 + * + * @param userId 用户d + */ + public void delByUserId(String userId) { + TriggerTokenLogBean tokenLogBean = new TriggerTokenLogBean(); + tokenLogBean.setUserId(userId); + this.delByBean(tokenLogBean); + } + + /** + * 通过 token 获取用户ID + * + * @param token token + * @param type 数据类型 + * @return user + */ + public UserModel getUserByToken(String token, String type) { + TriggerTokenLogBean tokenLogBean = super.getByKey(token); + if (tokenLogBean != null) { + UserModel userModel = userService.getByKey(tokenLogBean.getUserId()); + if (userModel != null && StrUtil.equals(type, tokenLogBean.getType())) { + boolean demoUser = userModel.isDemoUser(); + Assert.state(!demoUser, I18nMessageUtil.get("i18n.user_trigger_unavailable.9866")); + // 修改触发次数 + String sql = "update " + this.getTableName() + " set triggerCount=ifnull(triggerCount,0)+1 where id=?"; + int execute = this.execute(sql, tokenLogBean.getId()); + return userModel; + } + } + // + return null; + } + + /** + * 重启生成 token + * + * @param oldToken 之前版本 token + * @param type 类型 + * @param dataId 数据ID + * @param userId 用户ID + * @return 新 token + */ + public String restToken(String oldToken, String type, String dataId, String userId) { + if (StrUtil.isNotEmpty(oldToken)) { + this.delByKey(oldToken); + } + // 创建 token + return this.createToken(type, dataId, userId); + } + + /** + * 创建新 token + * + * @param type 类型 + * @param dataId 数据ID + * @param userId 用户ID + * @return token + */ + private String createToken(String type, String dataId, String userId) { + TriggerTokenLogBean trigger = new TriggerTokenLogBean(); + String uuid = IdUtil.fastSimpleUUID(); + trigger.setId(uuid); + trigger.setTriggerToken(uuid); + trigger.setType(type); + trigger.setDataId(dataId); + trigger.setUserId(userId); + 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; + for (ITriggerToken triggerToken : triggerTokens) { + TriggerTokenLogBean tokenLogBean = new TriggerTokenLogBean(); + tokenLogBean.setType(triggerToken.typeName()); + try { + int pageNumber = 1; + while (true) { + Page page = new Page(pageNumber, 50); + Entity entity = new Entity(); + entity.set("type", triggerToken.typeName()); + entity.setFieldNames("id", "dataId"); + PageResultDto pageResult = this.listPage(entity, page); + if (pageResult.isEmpty()) { + break; + } + List ids = new ArrayList<>(); + List result = pageResult.getResult(); + for (TriggerTokenLogBean bean : result) { + // + String dataId = bean.getDataId(); + if (triggerToken.exists(dataId)) { + continue; + } + String id = bean.getId(); + ids.add(id); + } + // 删除 token + this.delByKey(ids); + if (pageResult.getTotalPage() <= pageNumber) { + break; + } + pageNumber++; + delCount += ids.size(); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.cleanup_token_exception.760e"), 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 new file mode 100644 index 0000000000..ff34e82911 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/user/UserBindWorkspaceService.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.Week; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.user.UserBindWorkspaceModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.model.user.UserPermissionGroupBean; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.time.DayOfWeek; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2021/12/4 + */ +@Service +public class UserBindWorkspaceService extends BaseDbService { + + private final WorkspaceService workspaceService; + private final UserPermissionGroupServer userPermissionGroupServer; + + /** + * ssh 终端没有任何限制 + */ + public static final String SSH_COMMAND_NOT_LIMITED = "-sshCommandNotLimited"; + + public UserBindWorkspaceService(WorkspaceService workspaceService, + UserPermissionGroupServer userPermissionGroupServer) { + this.workspaceService = workspaceService; + this.userPermissionGroupServer = userPermissionGroupServer; + } + + /** + * 更新用户的工作空间信息 + * + * @param userId 用户ID + * @param workspace 工作空间信息 + */ + public void updateUserWorkspace(String userId, List workspace) { + Assert.notEmpty(workspace, I18nMessageUtil.get("i18n.no_workspace_info.75ae")); + List list = new HashSet<>(workspace).stream() + .filter(s -> { + // 过滤 + s = StrUtil.removeSuffix(s, SSH_COMMAND_NOT_LIMITED); + MethodFeature[] values = MethodFeature.values(); + for (MethodFeature value : values) { + s = StrUtil.removeSuffix(s, StrUtil.DASHED + value.name()); + } + return workspaceService.exists(new WorkspaceModel(s)); + }) + .map(s -> { + UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); + userBindWorkspaceModel.setWorkspaceId(s); + userBindWorkspaceModel.setUserId(userId); + userBindWorkspaceModel.setId(UserBindWorkspaceModel.getId(userId, s)); + return userBindWorkspaceModel; + }) + .collect(Collectors.toList()); + // 删除之前的数据 + UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); + userBindWorkspaceModel.setUserId(userId); + super.del(super.dataBeanToEntity(userBindWorkspaceModel)); + // 重新入库 + super.insert(list); + } + + /** + * 查询用户绑定的工作空间 + * + * @param userId 用户ID + * @return list + */ + public List listUserWorkspace(String userId) { + UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); + userBindWorkspaceModel.setUserId(userId); + return super.listByBean(userBindWorkspaceModel); + } + + /** + * 判断对应的工作空间是否被用户绑定 + * + * @param workspaceId 工作空间ID + * @return true 有用户绑定 + */ + public boolean existsWorkspace(String workspaceId) { + UserBindWorkspaceModel userBindWorkspaceModel = new UserBindWorkspaceModel(); + userBindWorkspaceModel.setWorkspaceId(workspaceId); + return super.exists(userBindWorkspaceModel); + } + + /** + * 查询用户绑定的工作空间 + * + * @param userModel 用户 + * @return list + */ + public List listUserWorkspaceInfo(UserModel userModel) { + if (userModel.isSuperSystemUser()) { + // 超级管理员有所有工作空间权限 + return workspaceService.list(); + } + String permissionGroup = userModel.getPermissionGroup(); + List list = StrUtil.splitTrim(permissionGroup, StrUtil.AT); + list = ObjectUtil.defaultIfNull(list, new ArrayList<>()); + // 兼容旧代码 + list.add(userModel.getId()); + Entity entity = Entity.create(); + entity.set("userId", list); + List userBindWorkspaceModels = super.listByEntity(entity); + Assert.notEmpty(userBindWorkspaceModels, I18nMessageUtil.get("i18n.no_workspace_info_contact_admin_for_authorization.825f")); + List collect = userBindWorkspaceModels.stream().map(UserBindWorkspaceModel::getWorkspaceId).collect(Collectors.toList()); + return workspaceService.listById(collect); + } + + /** + * 删除 + * + * @param userId 用户ID + */ + public void deleteByUserId(String userId) { + UserBindWorkspaceModel bindWorkspaceModel = new UserBindWorkspaceModel(); + bindWorkspaceModel.setUserId(userId); + Entity where = super.dataBeanToEntity(bindWorkspaceModel); + super.del(where); + } + + /** + * 查询用户 是否存在工作空间权限 + * + * @param userModel 用户 + * @param workspaceId 工作空间 + * @return list + */ + private List existsList(UserModel userModel, String workspaceId) { + String permissionGroup = userModel.getPermissionGroup(); + List list = StrUtil.splitTrim(permissionGroup, StrUtil.AT); + list = list.stream() + .map(s -> UserBindWorkspaceModel.getId(s, workspaceId)) + .collect(Collectors.toList()); + // 兼容旧数据 + list.add(UserBindWorkspaceModel.getId(userModel.getId(), workspaceId)); + return this.listById(list); + } + + /** + * 查询用户 是否存在工作空间权限 + * + * @param userModel 用户 + * @param workspaceId 工作空间 + * @return true 存在 + */ + public boolean exists(UserModel userModel, String workspaceId) { + List workspaceModels = this.existsList(userModel, workspaceId); + return CollUtil.isNotEmpty(workspaceModels); + } + + /** + * 判断是否可以执行,并且验证时间段 + * + * @param userModel 用户 + * @param workspaceId 工作空间ID + * @return Permission Result + */ + public UserBindWorkspaceModel.PermissionResult checkPermission(UserModel userModel, String workspaceId) { + List workspaceModels = this.existsList(userModel, workspaceId); + if (CollUtil.isEmpty(workspaceModels)) { + return UserBindWorkspaceModel.PermissionResult.builder() + .state(UserBindWorkspaceModel.PermissionResultEnum.FAIL) + .msg(I18nMessageUtil.get("i18n.no_management_permission2.35d4")) + .build(); + } + List permissionGroupIds = workspaceModels.stream() + .map(UserBindWorkspaceModel::getUserId) + .collect(Collectors.toList()); + List permissionGroups = userPermissionGroupServer.listById(permissionGroupIds); + if (CollUtil.isEmpty(permissionGroups)) { + return UserBindWorkspaceModel.PermissionResult.builder() + .state(UserBindWorkspaceModel.PermissionResultEnum.FAIL) + .msg(I18nMessageUtil.get("i18n.no_management_permission.fd25")) + .build(); + } + // 判断禁止执行 + Optional prohibitExecuteRule = this.findProhibitExecuteRule(permissionGroups); + if (prohibitExecuteRule.isPresent()) { + String msg = prohibitExecuteRule.map(jsonObject -> { + String reason = jsonObject.getString("reason"); + String startTime = jsonObject.getString("startTime"); + String endTime = jsonObject.getString("endTime"); + if (StrUtil.isEmpty(reason)) { + return StrUtil.format(I18nMessageUtil.get("i18n.forbidden_operation_time_range.92bf"), startTime, endTime); + } + return StrUtil.format(I18nMessageUtil.get("i18n.forbidden_operation_range.247f"), reason, startTime, endTime); + }).orElse(I18nMessageUtil.get("i18n.forbidden_operation_time.d83d")); + return UserBindWorkspaceModel.PermissionResult.builder() + .state(UserBindWorkspaceModel.PermissionResultEnum.MISS_PROHIBIT) + .msg(msg) + .build(); + } + // 判断允许执行 + return this.checkAllowExecute(permissionGroups); + } + + /** + * 匹配可以执行的时间段 + * + * @param permissionGroups 权限组 + * @return 结果 + */ + private UserBindWorkspaceModel.PermissionResult checkAllowExecute(List permissionGroups) { + List allowExecuteListRule = permissionGroups.stream() + .map(UserPermissionGroupBean::getAllowExecute) + .filter(Objects::nonNull) + .map(JSONArray::parseArray) + .filter(CollUtil::isNotEmpty) + .flatMap(jsonArray -> jsonArray.stream().map(o -> (JSONObject) o)) + .collect(Collectors.toList()); + if (CollUtil.isEmpty(allowExecuteListRule)) { + // 没有配置规则,直接放行 + return UserBindWorkspaceModel.PermissionResult.builder().state(UserBindWorkspaceModel.PermissionResultEnum.SUCCESS).build(); + } + Optional allowExecuteRule = allowExecuteListRule.stream() + .filter(jsonObject -> { + DateTime now = DateTime.now(); + Week nowWeek = now.dayOfWeekEnum(); + int nowWeekInt = nowWeek.getIso8601Value(); + JSONArray week = jsonObject.getJSONArray("week"); + if (CollUtil.isEmpty(week)) { + return false; + } + if (!CollUtil.contains(week, nowWeekInt)) { + return false; + } + String startTime = jsonObject.getString("startTime"); + String endTime = jsonObject.getString("endTime"); + DateTime startDate = DateUtil.parseTimeToday(startTime); + DateTime endDate = DateUtil.parseTimeToday(endTime); + return DateUtil.isIn(DateTime.now(), startDate, endDate); + }) + .findAny(); + if (allowExecuteRule.isPresent()) { + // 允许执行 + return UserBindWorkspaceModel.PermissionResult.builder().state(UserBindWorkspaceModel.PermissionResultEnum.SUCCESS).build(); + } + // 拼接限制规则 + String ruleStr = allowExecuteListRule.stream() + .map(jsonObject -> { + JSONArray week = jsonObject.getJSONArray("week"); + String weekStr = week.stream() + .map(o -> Convert.toInt(o, 0)) + .map(weekInt -> { + DayOfWeek dayOfWeek = DayOfWeek.of(weekInt); + return Week.of(dayOfWeek); + }) + .map(week1 -> week1.toChinese(StrUtil.EMPTY)) + .collect(Collectors.joining(StrUtil.COMMA)); + String startTime = jsonObject.getString("startTime"); + String endTime = jsonObject.getString("endTime"); + return StrUtil.format(I18nMessageUtil.get("i18n.week_day_range_format.ebec"), weekStr, startTime, endTime); + }) + .collect(Collectors.joining(StrUtil.SPACE)); + return UserBindWorkspaceModel.PermissionResult.builder() + .state(UserBindWorkspaceModel.PermissionResultEnum.MISS_PERIOD) + .msg(I18nMessageUtil.get("i18n.forbidden_operation_time_period.86a3") + ruleStr) + .build(); + } + + /** + * 匹配禁止执行规则 + * + * @param permissionGroups 权限组配置 + * @return 找到的第一个禁止规则 + */ + private Optional findProhibitExecuteRule(List permissionGroups) { + return permissionGroups.stream() + .map(UserPermissionGroupBean::getProhibitExecute) + .filter(Objects::nonNull) + .map(JSONArray::parseArray) + .filter(CollUtil::isNotEmpty) + .flatMap(jsonArray -> jsonArray.stream().map(o -> (JSONObject) o)) + .filter(jsonObject -> { + String startTime = jsonObject.getString("startTime"); + String endTime = jsonObject.getString("endTime"); + if (StrUtil.hasEmpty(startTime, endTime)) { + return false; + } + DateTime startDate = DateUtil.parse(startTime); + DateTime endDate = DateUtil.parse(endTime); + return DateUtil.isIn(DateTime.now(), startDate, endDate); + }) + .findFirst(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/user/UserPermissionGroupServer.java b/modules/server/src/main/java/org/dromara/jpom/service/user/UserPermissionGroupServer.java new file mode 100644 index 0000000000..59b0237928 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/user/UserPermissionGroupServer.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.user; + +import org.dromara.jpom.model.user.UserPermissionGroupBean; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.springframework.stereotype.Service; + +/** + * @author bwcx_jzy + * @since 2022/8/3 + */ +@Service +public class UserPermissionGroupServer extends BaseDbService { +} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/user/UserService.java b/modules/server/src/main/java/org/dromara/jpom/service/user/UserService.java new file mode 100644 index 0000000000..f08ca500ca --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/service/user/UserService.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.db.Entity; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.controller.user.UserWorkspaceModel; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.dto.UserLoginDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.dromara.jpom.util.JwtUtil; +import org.dromara.jpom.util.TwoFactorAuthUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2021/12/3 + */ +@Service +public class UserService extends BaseDbService { + private final SystemParametersServer systemParametersServer; + private final UserBindWorkspaceService userBindWorkspaceService; + + public UserService(SystemParametersServer systemParametersServer, + UserBindWorkspaceService userBindWorkspaceService) { + this.systemParametersServer = systemParametersServer; + this.userBindWorkspaceService = userBindWorkspaceService; + } + + /** + * 是否需要初始化 + * + * @return true 有系统管理员账号,系统可以正常使用 + */ + public boolean canUse() { + UserModel userModel = new UserModel(); + userModel.setSystemUser(1); + return super.exists(userModel); + } + + @Override + protected void fillSelectResult(UserModel data) { + if (data == null) { + return; + } + data.setSalt(null); + data.setPassword(null); + data.setTwoFactorAuthKey(null); + } + + /** + * 生成 随机盐值 + * + * @return 随机盐值 + */ + public synchronized String generateSalt() { + while (true) { + String salt = RandomUtil.randomString(UserModel.SALT_LEN); + UserModel userModel = new UserModel(); + userModel.setSalt(salt); + boolean exists = super.exists(userModel); + if (exists) { + continue; + } + return salt + StrUtil.DASHED + SystemClock.now(); + } + } + + /** + * 验证用户md5 + * + * @param userMd5 用户md5 + * @return userModel 用户对象 + */ + public UserModel checkUser(String userMd5) { + UserModel userModel = new UserModel(); + userModel.setPassword(userMd5); + return super.queryByBean(userModel); + } + + /** + * 查询用户 jwt id + * + * @param userModel 用户 + * @return jwt id + */ + public UserLoginDto getUserJwtId(UserModel userModel) { + // 判断是否禁用 + Integer status = userModel.getStatus(); + Assert.state(status == null || status == 1, ServerConst.ACCOUNT_LOCKED_TIP); + String id = userModel.getId(); + String sql = "select password from " + this.tableName + " where id=?"; + List query = super.query(sql, id); + Entity first = CollUtil.getFirst(query); + Assert.notEmpty(first, I18nMessageUtil.get("i18n.no_user_info.0355")); + String password = (String) first.get("password"); + Assert.hasText(password, I18nMessageUtil.get("i18n.no_user_info.0355")); + return new UserLoginDto(JwtUtil.builder(userModel, password), password); + } + + /** + * 当前系统中的系统管理员的数量 + * + * @return int + */ + public long systemUserCount() { + UserModel userModel = new UserModel(); + userModel.setSystemUser(1); + return super.count(super.dataBeanToEntity(userModel)); + } + + /** + * 修改密码 + * + * @param id 账号ID + * @param newPwd 新密码 + */ + public void updatePwd(String id, String newPwd) { + String salt = this.generateSalt(); + UserModel userModel = UserModel.unLock(id); + // userModel.setId(id); + userModel.setSalt(salt); + userModel.setPassword(SecureUtil.sha1(newPwd + salt)); + super.updateById(userModel); + } + + /** + * 用户登录 + * + * @param name 用户名 + * @param pwd 密码 + * @return 登录 + */ + public UserModel simpleLogin(String name, String pwd) { + UserModel userModel = super.getByKey(name, false); + if (userModel == null) { + return null; + } + String obj = SecureUtil.sha1(pwd + userModel.getSalt()); + if (StrUtil.equals(obj, userModel.getPassword())) { + super.fillSelectResult(userModel); + return userModel; + } + return null; + } + + /** + * 重置超级管理账号密码 + * + * @return 新密码 + */ + public String restSuperUserPwd() { + UserModel userModel = new UserModel(); + userModel.setParent(UserModel.SYSTEM_ADMIN); + UserModel queryByBean = super.queryByBean(userModel); + if (queryByBean == null) { + return null; + } + String newPwd = RandomUtil.randomString(UserModel.SALT_LEN); + this.updatePwd(queryByBean.getId(), SecureUtil.sha1(newPwd)); + return StrUtil.format(I18nMessageUtil.get("i18n.reset_super_admin_password_success.50c6"), queryByBean.getId(), newPwd); + } + + /** + * 关闭超级管理账号 mfa + * + * @return 新密码 + */ + public String closeSuperUserMfa() { + UserModel where = new UserModel(); + where.setParent(UserModel.SYSTEM_ADMIN); + UserModel update = new UserModel(); + update.setTwoFactorAuthKey(StrUtil.EMPTY); + int count = super.update(super.dataBeanToEntity(update), super.dataBeanToEntity(where)); + return StrUtil.format(I18nMessageUtil.get("i18n.super_admin_mfa_verification_disabled.b97d"), count); + } + + /** + * 是否包含 demo 账号 + * + * @return true + */ + public boolean hasDemoUser() { + UserModel userModel = new UserModel(); + userModel.setId(UserModel.DEMO_USER); + return super.exists(userModel); + } + + /** + * 判断是否绑定 两步验证 mfa + * + * @param useId 用户ID + * @return false 没有绑定 + */ + public boolean hasBindMfa(String useId) { + UserModel byKey = super.getByKey(useId, false); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.user_not_exist.4892")); + return !StrUtil.isEmpty(byKey.getTwoFactorAuthKey()) && !StrUtil.equals(byKey.getTwoFactorAuthKey(), "ignore"); + } + + /** + * 绑定 两步验证 mfa + * + * @param useId 用户ID + * @param mfa mfa key + */ + public void bindMfa(String useId, String mfa) { + UserModel byKey = new UserModel(); + byKey.setId(useId); + byKey.setTwoFactorAuthKey(mfa); + super.updateById(byKey); + } + + /** + * 判断验证码是否正确 + * + * @param userId 用户ID + * @param code 验证码 + * @return true 正确 + */ + public boolean verifyMfaCode(String userId, String code) { + UserModel byKey = super.getByKey(userId, false); + Assert.notNull(byKey, I18nMessageUtil.get("i18n.user_not_exist.4892")); + if (StrUtil.isEmpty(byKey.getTwoFactorAuthKey()) || StrUtil.equals(byKey.getTwoFactorAuthKey(), "ignore")) { + throw new IllegalStateException(I18nMessageUtil.get("i18n.account_mfa_not_enabled.fd39")); + } + return TwoFactorAuthUtils.validateTFACode(byKey.getTwoFactorAuthKey(), code); + } + + public List myWorkspace(UserModel user) { + List models = userBindWorkspaceService.listUserWorkspaceInfo(user); + Assert.notEmpty(models, I18nMessageUtil.get("i18n.account_not_bound_to_any_workspace.fd61")); + JSONObject parametersServerConfig = systemParametersServer.getConfig("user-my-workspace-" + user.getId(), JSONObject.class); + return models.stream() + .map(workspaceModel -> { + UserWorkspaceModel userWorkspaceModel = new UserWorkspaceModel(); + userWorkspaceModel.setId(workspaceModel.getId()); + userWorkspaceModel.setName(workspaceModel.getName()); + userWorkspaceModel.setGroup(workspaceModel.getGroup()); + userWorkspaceModel.setOriginalName(workspaceModel.getName()); + userWorkspaceModel.setClusterInfoId(workspaceModel.getClusterInfoId()); + Long createTimeMillis = workspaceModel.getCreateTimeMillis(); + userWorkspaceModel.setSort((int) (ObjectUtil.defaultIfNull(createTimeMillis, 0L) / 1000L)); + return userWorkspaceModel; + }) + .peek(userWorkspaceModel -> { + if (parametersServerConfig == null) { + return; + } + UserWorkspaceModel userConfig = parametersServerConfig.getObject(userWorkspaceModel.getId(), UserWorkspaceModel.class); + if (userConfig == null) { + return; + } + userWorkspaceModel.setName(StrUtil.emptyToDefault(userConfig.getName(), userWorkspaceModel.getName())); + userWorkspaceModel.setSort(ObjectUtil.defaultIfNull(userConfig.getSort(), userWorkspaceModel.getSort())); + }) + .sorted((o1, o2) -> CompareUtil.compare(o1.getSort(), o2.getSort())) + .collect(Collectors.toList()); + } +} 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 new file mode 100644 index 0000000000..633129fb54 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/BaseHandler.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.bean.BeanPath; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.BaseNodeModel; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.system.init.OperateLogController; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * @author bwcx_jzy + * @since 2019/8/9 + */ +@Slf4j +public abstract class BaseHandler extends TextWebSocketHandler { + + protected void setLanguage(WebSocketSession session) { + if (session == null) { + return; + } + Map attributes = session.getAttributes(); + String lang = (String) attributes.get("lang"); + I18nMessageUtil.setLanguage(lang); + } + + protected void clearLanguage() { + I18nMessageUtil.clearLanguage(); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + setLanguage(session); + try { + Map attributes = session.getAttributes(); + // + this.showHelloMsg(attributes, session); + // + String permissionMsg = (String) attributes.get("permissionMsg"); + if (StrUtil.isNotEmpty(permissionMsg)) { + this.sendMsg(session, permissionMsg); + ThreadUtil.sleep(2, TimeUnit.SECONDS); + this.destroy(session); + return; + } + this.afterConnectionEstablishedImpl(session); + } finally { + clearLanguage(); + } + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { + try { + setLanguage(session); + super.handleMessage(session, message); + } finally { + clearLanguage(); + } + } + + protected void showHelloMsg(Map attributes, WebSocketSession session) { + UserModel userInfo = (UserModel) attributes.get("userInfo"); + if (userInfo != null) { + String payload = StrUtil.format(I18nMessageUtil.get("i18n.welcome_join_session.1c16"), userInfo.getName(), session.getId() + StrUtil.CRLF); + this.sendMsg(session, payload); + } + } + + /** + * 建立会话后 + * + * @param session 会话 + * @throws Exception 异常 + */ + protected void afterConnectionEstablishedImpl(WebSocketSession session) throws Exception { + Map attributes = session.getAttributes(); + // 连接成功后记录 + this.logOpt(this.getClass(), attributes, attributes); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.error("{}{}", session.getId(), I18nMessageUtil.get("i18n.socket_exception.d836"), exception); + destroy(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + destroy(session); + log.debug(I18nMessageUtil.get("i18n.session_closed_reason.103a"), session.getId(), status); + } + + /** + * 关闭连接 + * + * @param session session + */ + public abstract void destroy(WebSocketSession session); + + protected void sendMsg(WebSocketSession session, String msg) { + try { + SocketSessionUtil.send(session, msg); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + } + } + + /** + * 操作 websocket 日志 + * + * @param cls class + * @param attributes 属性 + * @param reqData 请求数据 + */ + protected void logOpt(Class cls, Map attributes, Object reqData) { + String ip = (String) attributes.get("ip"); + NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo"); + // 记录操作日志 + UserModel userInfo = (UserModel) attributes.get("userInfo"); + String workspaceId = (String) attributes.get("workspaceId"); + OperateLogController.CacheInfo cacheInfo = new OperateLogController.CacheInfo(); + cacheInfo.setIp(ip); + Feature feature = cls.getAnnotation(Feature.class); + MethodFeature method = feature.method(); +// Assert.state(feature != null && feature, "权限功能没有配置正确"); + cacheInfo.setClassFeature(feature.cls()); + cacheInfo.setWorkspaceId(workspaceId); + cacheInfo.setMethodFeature(method); + + cacheInfo.setNodeModel(nodeModel); + // + Object dataItem = attributes.get("dataItem"); + Optional.ofNullable(dataItem).map(o -> { + if (o instanceof BaseNodeModel) { + BaseNodeModel baseNodeModel = (BaseNodeModel) o; + return baseNodeModel.dataId(); + } + Object id = BeanPath.create("id").get(o); + return StrUtil.toStringOrNull(id); + + }).ifPresent(cacheInfo::setDataId); + String userAgent = (String) attributes.get(HttpHeaders.USER_AGENT); + cacheInfo.setUserAgent(userAgent); + cacheInfo.setReqData(JSONObject.toJSONString(reqData)); + + //cacheInfo.setMethodFeature(execute); + Object proxySession = attributes.get("proxySession"); + try { + attributes.remove("proxySession"); + attributes.put("use_type", "WebSocket"); + attributes.put("class_type", cls.getName()); + OperateLogController operateLogController = SpringUtil.getBean(OperateLogController.class); + operateLogController.log(userInfo, JSONObject.toJSONString(attributes), cacheInfo); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.record_operation_log_exception.8012"), e); + } finally { + if (proxySession != null) { + attributes.put("proxySession", proxySession); + } + } + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/BaseProxyHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/BaseProxyHandler.java new file mode 100644 index 0000000000..7df5cbd10e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/BaseProxyHandler.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.transport.*; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Optional; + +/** + * 服务端socket 基本类 + * + * @author bwcx_jzy + * @since 2019/4/25 + */ +@Slf4j +public abstract class BaseProxyHandler extends BaseHandler { + + private final NodeUrl nodeUrl; + + public BaseProxyHandler(NodeUrl nodeUrl) { + this.nodeUrl = nodeUrl; + } + + /** + * 连接参数 + * + * @param attributes 属性 + * @return key, value, key, value..... + */ + protected abstract Object[] getParameters(Map attributes); + + @Override + public void afterConnectionEstablishedImpl(WebSocketSession session) throws Exception { + super.afterConnectionEstablishedImpl(session); + Map attributes = session.getAttributes(); + this.init(session, attributes); + } + + /** + * 连接成功 初始化 + * + * @param session 会话 + * @param attributes 属性 + * @throws URISyntaxException 异常 + * @throws IOException IO + */ + protected void init(WebSocketSession session, Map attributes) throws Exception { + boolean init = (boolean) attributes.getOrDefault("init", false); + if (init) { + return; + } + NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo"); + MachineNodeModel machine = (MachineNodeModel) attributes.get("machine"); + + Object[] parameters = this.getParameters(attributes); + UserModel userModel = (UserModel) attributes.get("userInfo"); + parameters = ArrayUtil.append(parameters, "optUser", userModel.getId(), "lang", attributes.get("lang")); + // 连接节点 + INodeInfo nodeInfo = Optional.ofNullable(machine) + .map(NodeForward::coverNodeInfo) + .orElseGet(() -> Optional.ofNullable(nodeModel) + .map(NodeForward::parseNodeInfo) + .orElse(null) + ); + if (nodeInfo == null) { + return; + } + String workspaceId = Optional.ofNullable(nodeModel).map(BaseWorkspaceModel::getWorkspaceId).orElse(StrUtil.EMPTY); + IUrlItem urlItem = NodeForward.parseUrlItem(nodeInfo, workspaceId, this.nodeUrl, DataContentType.FORM_URLENCODED); + + IProxyWebSocket proxySession = TransportServerFactory.get().websocket(nodeInfo, urlItem, parameters); + proxySession.onMessage(s -> onProxyMessage(session, s)); + if (!proxySession.connectBlocking()) { + this.sendMsg(session, I18nMessageUtil.get("i18n.plugin_connection_failed.02a1")); + this.destroy(session); + return; + } + session.getAttributes().put("proxySession", proxySession); + + + attributes.put("init", true); + } + + protected void onProxyMessage(WebSocketSession session, String msg) { + sendMsg(session, msg); + } + + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String msg = message.getPayload(); + Map attributes = session.getAttributes(); + IProxyWebSocket proxySession = (IProxyWebSocket) attributes.get("proxySession"); + JSONObject json = JSONObject.parseObject(msg); + String op = json.getString("op"); + ConsoleCommandOp consoleCommandOp = StrUtil.isNotEmpty(op) ? ConsoleCommandOp.valueOf(op) : null; + try { + String textMessage; + if (proxySession != null) { + textMessage = this.handleTextMessage(attributes, session, proxySession, json, consoleCommandOp); + } else { + textMessage = this.handleTextMessage(attributes, session, json, consoleCommandOp); + } + if (textMessage != null) { + this.sendMsg(session, textMessage); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.handle_message_exception.0bdc"), e); + this.sendMsg(session, I18nMessageUtil.get("i18n.handle_message_exception_with_colon.56f0") + e.getMessage()); + } + } + + /** + * 消息处理方法 + * + * @param attributes 属性 + * @param session 当前回话 + * @param json 数据 + * @param consoleCommandOp 操作类型 + */ + protected String handleTextMessage(Map attributes, + WebSocketSession session, + JSONObject json, + ConsoleCommandOp consoleCommandOp) throws IOException { + return null; + } + + /** + * 消息处理方法 + * + * @param attributes 属性 + * @param proxySession 代理回话 + * @param json 数据 + * @param consoleCommandOp 操作类型 + */ + protected String handleTextMessage(Map attributes, + WebSocketSession session, + IProxyWebSocket proxySession, + JSONObject json, + ConsoleCommandOp consoleCommandOp) throws IOException { + return this.handleTextMessage(attributes, proxySession, json, consoleCommandOp); + } + + /** + * 消息处理方法 + * + * @param attributes 属性 + * @param proxySession 代理回话 + * @param json 数据 + * @param consoleCommandOp 操作类型 + */ + protected String handleTextMessage(Map attributes, + IProxyWebSocket proxySession, + JSONObject json, + ConsoleCommandOp consoleCommandOp) throws IOException { + return null; + } + + + @Override + public void destroy(WebSocketSession session) { + try { + if (session.isOpen()) { + session.close(); + } + } catch (IOException ignored) { + } + Map attributes = session.getAttributes(); + IProxyWebSocket proxySession = (IProxyWebSocket) attributes.get("proxySession"); + if (proxySession != null) { + try { + proxySession.close(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.close_exception.5b86"), e); + } + } + SocketSessionUtil.close(session); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/HandlerType.java b/modules/server/src/main/java/org/dromara/jpom/socket/HandlerType.java new file mode 100644 index 0000000000..d7834d46fc --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/HandlerType.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import lombok.Getter; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.func.assets.server.MachineSshServer; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.service.node.ProjectInfoCacheService; +import org.dromara.jpom.service.node.script.NodeScriptServer; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.socket.handler.*; + +/** + * @author bwcx_jzy + * @since 2019/8/9 + */ +@Getter +public enum HandlerType { + /** + * 脚本模板 + */ + nodeScript(NodeScriptHandler.class, NodeScriptServer.class), + /** + * 系统日志 + */ + systemLog(SystemLogHandler.class, null), + /** + * 插件端日志 + */ + agentLog(AgentLogHandler.class, null), + /** + * 项目控制台和首页监控 + */ + console(ConsoleHandler.class, ProjectInfoCacheService.class), + /** + * ssh + */ + ssh(SshHandler.class, SshService.class, MachineSshServer.class, "machineSshId"), + /** + * 节点升级 + */ + nodeUpdate(NodeUpdateHandler.class, null), + /** + * 服务端 脚本模版 + */ + script(ServerScriptHandler.class, ScriptServer.class), + /** + * 容器 log + */ + dockerLog(DockerLogHandler.class, DockerInfoService.class, MachineDockerServer.class, "machineDockerId"), + /** + * 容器 终端 + */ + docker(DockerCliHandler.class, DockerInfoService.class, MachineDockerServer.class, "machineDockerId"), + freeScript(FreeScriptHandler.class, null), + ; + final Class handlerClass; + + final Class> serviceClass; + final Class> assetsServiceClass; + /** + * 资产关联字段 + */ + final String assetsLinkDataId; + + HandlerType(Class handlerClass, + Class> serviceClass) { + this.handlerClass = handlerClass; + this.serviceClass = serviceClass; + this.assetsServiceClass = null; + this.assetsLinkDataId = null; + } + + HandlerType(Class handlerClass, + Class> serviceClass, + Class> assetsServiceClass, + String assetsLinkDataId) { + this.handlerClass = handlerClass; + this.serviceClass = serviceClass; + this.assetsServiceClass = assetsServiceClass; + this.assetsLinkDataId = assetsLinkDataId; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/ServerScriptProcessBuilder.java b/modules/server/src/main/java/org/dromara/jpom/socket/ServerScriptProcessBuilder.java new file mode 100644 index 0000000000..f99594cd9d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/ServerScriptProcessBuilder.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.data.CommandExecLogModel; +import org.dromara.jpom.model.script.ScriptModel; +import org.dromara.jpom.script.BaseRunScript; +import org.dromara.jpom.script.CommandParam; +import org.dromara.jpom.service.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.system.WorkspaceEnvVarService; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.WebSocketSession; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 脚本执行 + * + * @author bwcx_jzy + * @since 2022/1/19 + */ +@Slf4j +public class ServerScriptProcessBuilder extends BaseRunScript implements Runnable { + /** + * 执行中的缓存 + */ + private static final ConcurrentHashMap FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP = new SafeConcurrentHashMap<>(); + + private final ProcessBuilder processBuilder; + private final Set sessions = new HashSet<>(); + private final String executeId; + private final File scriptFile; + + private final EnvironmentMapBuilder environmentMapBuilder; + private ScriptExecuteLogServer scriptExecuteLogServer; + + private ServerScriptProcessBuilder(ScriptModel nodeScriptModel, String executeId, String args, Map paramMap) { + super(nodeScriptModel.logFile(executeId), CharsetUtil.CHARSET_UTF_8); + // + if (scriptExecuteLogServer == null) { + scriptExecuteLogServer = SpringUtil.getBean(ScriptExecuteLogServer.class); + } + this.executeId = executeId; + // + WorkspaceEnvVarService workspaceEnvVarService = SpringUtil.getBean(WorkspaceEnvVarService.class); + environmentMapBuilder = workspaceEnvVarService.getEnv(nodeScriptModel.getWorkspaceId()); + environmentMapBuilder.putStr(paramMap); + scriptFile = scriptExecuteLogServer.toExecLogFile(nodeScriptModel); + // + String script = FileUtil.getAbsolutePath(scriptFile); + processBuilder = new ProcessBuilder(); + List command = CommandParam.toCommandList(args); + command.add(0, script); + CommandUtil.paddingPrefix(command); + log.debug(CollUtil.join(command, StrUtil.SPACE)); + processBuilder.redirectErrorStream(true); + processBuilder.command(command); + Map environment = processBuilder.environment(); + environment.putAll(environmentMapBuilder.environment()); + processBuilder.directory(scriptFile.getParentFile()); + } + + /** + * 创建执行 并监听 + * + * @param nodeScriptModel 脚本模版 + * @param executeId 执行ID + * @param args 参数 + */ + public static ServerScriptProcessBuilder create(ScriptModel nodeScriptModel, String executeId, String args) { + return create(nodeScriptModel, executeId, args, null); + } + + /** + * 创建执行 并监听 + * + * @param nodeScriptModel 脚本模版 + * @param executeId 执行ID + * @param args 参数 + * @param paramMap 环境变量参数 + */ + public static ServerScriptProcessBuilder create(ScriptModel nodeScriptModel, String executeId, String args, Map paramMap) { + return FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.computeIfAbsent(executeId, file1 -> { + ServerScriptProcessBuilder serverScriptProcessBuilder1 = new ServerScriptProcessBuilder(nodeScriptModel, executeId, args, paramMap); + I18nThreadUtil.execute(serverScriptProcessBuilder1); + return serverScriptProcessBuilder1; + }); + } + + /** + * 创建执行 并监听 + * + * @param nodeScriptModel 脚本模版 + * @param executeId 执行ID + * @param args 参数 + * @param session 会话 + */ + public static void addWatcher(ScriptModel nodeScriptModel, String executeId, String args, WebSocketSession session) { + ServerScriptProcessBuilder serverScriptProcessBuilder = create(nodeScriptModel, executeId, args); + // + if (serverScriptProcessBuilder.sessions.add(session)) { + if (FileUtil.exist(serverScriptProcessBuilder.logFile)) { + // 读取之前的信息并发送 + FileUtil.readLines(serverScriptProcessBuilder.logFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line -> { + try { + SocketSessionUtil.send(session, line); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + } + }); + } + } + } + + /** + * 判断是否还在执行中 + * + * @param executeId 执行id + * @return true 还在执行 + */ + public static boolean isRun(String executeId) { + return FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.containsKey(executeId); + } + + /** + * 关闭会话 + * + * @param session 会话 + */ + public static void stopWatcher(WebSocketSession session) { + Collection serverScriptProcessBuilders = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.values(); + for (ServerScriptProcessBuilder serverScriptProcessBuilder : serverScriptProcessBuilders) { + Set sessions = serverScriptProcessBuilder.sessions; + sessions.removeIf(session1 -> session1.getId().equals(session.getId())); + } + } + + /** + * 停止脚本命令 + * + * @param executeId 执行ID + */ + public static void stopRun(String executeId) { + ServerScriptProcessBuilder serverScriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.get(executeId); + if (serverScriptProcessBuilder != null) { + serverScriptProcessBuilder.end(I18nMessageUtil.get("i18n.stop_running.1d4e")); + } + } + + @Override + public void run() { + //初始化ProcessBuilder对象 + try { + scriptExecuteLogServer.updateStatus(executeId, CommandExecLogModel.Status.ING); + this.environmentMapBuilder.eachStr(this::info); + process = processBuilder.start(); + inputStream = process.getInputStream(); + IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), (LineHandler) ServerScriptProcessBuilder.this::info); + int waitFor = process.waitFor(); + this.system(I18nMessageUtil.get("i18n.execution_ended.b793"), waitFor); + scriptExecuteLogServer.updateStatus(executeId, CommandExecLogModel.Status.DONE, waitFor); + // + JsonMessage jsonMessage = new JsonMessage<>(200, I18nMessageUtil.get("i18n.execution_completed.24a1") + waitFor); + JSONObject jsonObject = jsonMessage.toJson(); + jsonObject.put(Const.SOCKET_MSG_TAG, Const.SOCKET_MSG_TAG); + jsonObject.put("op", ConsoleCommandOp.stop.name()); + this.end(jsonObject.toString()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e); + scriptExecuteLogServer.updateStatus(executeId, CommandExecLogModel.Status.ERROR); + this.system(I18nMessageUtil.get("i18n.execution_exception.b0d5"), e.getMessage()); + this.end(I18nMessageUtil.get("i18n.general_execution_exception.62e9") + e.getMessage()); + } finally { + this.close(); + } + } + + /** + * 结束执行 + * + * @param msg 响应的消息 + */ + @Override + protected void end(String msg) { + Iterator iterator = sessions.iterator(); + while (iterator.hasNext()) { + WebSocketSession session = iterator.next(); + try { + SocketSessionUtil.send(session, msg); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + } + iterator.remove(); + } + ServerScriptProcessBuilder serverScriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.remove(this.executeId); + IoUtil.close(serverScriptProcessBuilder); + } + + @Override + protected void msgCallback(String info) { + // + Iterator iterator = sessions.iterator(); + while (iterator.hasNext()) { + WebSocketSession session = iterator.next(); + try { + SocketSessionUtil.send(session, info); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure.9621"), e); + iterator.remove(); + } + } + } + + @Override + public void close() { + super.close(); + try { + FileUtil.del(this.scriptFile); + } catch (Exception ignored) { + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/ServerWebSocketConfig.java b/modules/server/src/main/java/org/dromara/jpom/socket/ServerWebSocketConfig.java new file mode 100644 index 0000000000..77090bf5e5 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/ServerWebSocketConfig.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import org.dromara.jpom.configuration.NodeConfig; +import org.dromara.jpom.func.assets.server.MachineNodeServer; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.dromara.jpom.socket.handler.*; +import org.dromara.jpom.system.ServerConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +/** + * socket 配置 + * + * @author bwcx_jzy + */ +@Configuration +@EnableWebSocket +public class ServerWebSocketConfig implements WebSocketConfigurer { + private final ServerWebSocketInterceptor serverWebSocketInterceptor; + private final SystemParametersServer systemParametersServer; + private final NodeConfig nodeConfig; + private final MachineNodeServer machineNodeServer; + + public ServerWebSocketConfig(ServerWebSocketInterceptor serverWebSocketInterceptor, + SystemParametersServer systemParametersServer, + ServerConfig serverConfig, + MachineNodeServer machineNodeServer) { + this.serverWebSocketInterceptor = serverWebSocketInterceptor; + this.systemParametersServer = systemParametersServer; + this.nodeConfig = serverConfig.getNode(); + this.machineNodeServer = machineNodeServer; + } + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + // 控制台 + registry.addHandler(new ConsoleHandler(), "/socket/console") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // 节点脚本模板 + registry.addHandler(new NodeScriptHandler(), "/socket/node/script_run") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // 系统日志 + registry.addHandler(new SystemLogHandler(), "/socket/system_log") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // 插件端日志 + registry.addHandler(new AgentLogHandler(), "/socket/agent_log") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // ssh + registry.addHandler(new SshHandler(), "/socket/ssh") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // 节点升级 + registry.addHandler(new NodeUpdateHandler(machineNodeServer, systemParametersServer, nodeConfig), "/socket/node_update") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // 脚本模板 + registry.addHandler(new ServerScriptHandler(), "/socket/script_run") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // docker log + registry.addHandler(new DockerLogHandler(), "/socket/docker_log") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // docker cli + registry.addHandler(new DockerCliHandler(), "/socket/docker_cli") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + // free script + registry.addHandler(new FreeScriptHandler(), "/socket/free_script") + .addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*"); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/ServerWebSocketInterceptor.java b/modules/server/src/main/java/org/dromara/jpom/socket/ServerWebSocketInterceptor.java new file mode 100644 index 0000000000..953c398d3e --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/ServerWebSocketInterceptor.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.interceptor.PermissionInterceptor; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.func.assets.server.MachineNodeServer; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.user.UserBindWorkspaceModel; +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.h2db.BaseDbService; +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.service.user.UserService; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; +import java.util.function.Supplier; + +/** + * socket 拦截器、鉴权 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@Slf4j +@Configuration +public class ServerWebSocketInterceptor implements HandshakeInterceptor { + + private final UserService userService; + private final NodeService nodeService; + private final UserBindWorkspaceService userBindWorkspaceService; + private final MachineNodeServer machineNodeServer; + + public ServerWebSocketInterceptor(UserService userService, + NodeService nodeService, + UserBindWorkspaceService userBindWorkspaceService, + MachineNodeServer machineNodeServer) { + this.userService = userService; + this.nodeService = nodeService; + this.userBindWorkspaceService = userBindWorkspaceService; + this.machineNodeServer = machineNodeServer; + } + + private boolean checkNode(HttpServletRequest httpServletRequest, Map attributes, UserModel userModel) { + // 验证 node 权限 + String nodeId = httpServletRequest.getParameter("nodeId"); + if (!Const.SYSTEM_ID.equals(nodeId)) { + NodeModel nodeModel = nodeService.getByKey(nodeId, userModel); + if (nodeModel == null) { + return false; + } + // + attributes.put("nodeInfo", nodeModel); + } + // 验证机器权限 + String machineId = httpServletRequest.getParameter("machineId"); + if (StringUtils.isNotEmpty(machineId)) { + if (!userModel.isSystemUser()) { + // 没有权限 + return false; + } + MachineNodeModel machine = machineNodeServer.getByKey(machineId); + if (machine == null) { + return false; + } + attributes.put("machine", machine); + } + return true; + } + + private HandlerType fromType(HttpServletRequest httpServletRequest) { + // 判断拦截类型 + String type = httpServletRequest.getParameter("type"); + HandlerType handlerType = EnumUtil.fromString(HandlerType.class, type, null); + if (handlerType == null) { + log.warn(I18nMessageUtil.get("i18n.incorrect_type_passed.d42e"), type); + } + return handlerType; + } + + private boolean checkHandlerType(HandlerType handlerType, UserModel userModel, HttpServletRequest httpServletRequest, Map attributes) { + switch (handlerType) { + case console: { + //控制台 + Object dataItem = this.checkData(handlerType, userModel, httpServletRequest); + if (dataItem == null) { + return false; + } + + attributes.put("projectId", BeanUtil.getProperty(dataItem, "projectId")); + attributes.put("dataItem", dataItem); + break; + } + case nodeScript: { + // 节点脚本模板 + Object dataItem = this.checkData(handlerType, userModel, httpServletRequest); + if (dataItem == null) { + return false; + } + attributes.put("dataItem", dataItem); + attributes.put("scriptId", BeanUtil.getProperty(dataItem, "scriptId")); + break; + } + case script: { + // 脚本模板 + Object dataItem = this.checkData(handlerType, userModel, httpServletRequest); + if (dataItem == null) { + return false; + } + attributes.put("dataItem", dataItem); + attributes.put("scriptId", BeanUtil.getProperty(dataItem, "id")); + break; + } + case systemLog: + case agentLog: + break; + case dockerLog: { + Tuple dataItem = this.checkAssetsData(handlerType, userModel, httpServletRequest); + if (dataItem == null) { + return false; + } + attributes.put("dataItem", dataItem.get(2)); + attributes.put("isAssetsManager", dataItem.get(1)); + attributes.put("machineDocker", dataItem.get(0)); + break; + } + case ssh: { + Tuple dataItem = this.checkAssetsData(handlerType, userModel, httpServletRequest); + if (dataItem == null) { + return false; + } + attributes.put("dataItem", dataItem.get(2)); + attributes.put("isAssetsManager", dataItem.get(1)); + attributes.put("machineSsh", dataItem.get(0)); + break; + } + case docker: + Tuple dataItem = this.checkAssetsData(handlerType, userModel, httpServletRequest); + if (dataItem == null) { + return false; + } + attributes.put("dataItem", dataItem.get(2)); + attributes.put("isAssetsManager", dataItem.get(1)); + attributes.put("machineDocker", dataItem.get(0)); + attributes.put("containerId", httpServletRequest.getParameter("containerId")); + break; + case nodeUpdate: + break; + case freeScript: + // + MachineNodeModel machine = (MachineNodeModel) attributes.get("machine"); + if (machine == null) { + return false; + } + break; + default: + return false; + } + return true; + } + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + if (request instanceof ServletServerHttpRequest) { + ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; + HttpServletRequest httpServletRequest = serverHttpRequest.getServletRequest(); + // 判断用户 + String userId = httpServletRequest.getParameter("userId"); + String workspaceId = httpServletRequest.getParameter(ServerConst.WORKSPACE_ID_REQ_HEADER); + attributes.put("workspaceId", workspaceId); + attributes.put("lang", httpServletRequest.getParameter("lang")); + UserModel userModel = userService.checkUser(userId); + if (userModel == null) { + String string = I18nMessageUtil.get("i18n.user_not_exist.4892"); + attributes.put("permissionMsg", string); + return true; + } + HandlerType handlerType = this.fromType(httpServletRequest); + if (handlerType == null) { + String string = I18nMessageUtil.get("i18n.no_matching_process_type.b468"); + attributes.put("permissionMsg", string); + return true; + } + boolean checkNode = this.checkNode(httpServletRequest, attributes, userModel); + if (!checkNode) { + String string = I18nMessageUtil.get("i18n.no_matching_permission.09cf"); + attributes.put("permissionMsg", string); + return true; + } + if (!this.checkHandlerType(handlerType, userModel, httpServletRequest, attributes)) { + String string = I18nMessageUtil.get("i18n.no_matching_data_found.fe9d"); + attributes.put("permissionMsg", string); + return true; + } + // 判断权限 + String permissionMsg = this.checkPermission(userModel, attributes, handlerType); + attributes.put("permissionMsg", permissionMsg); + + // + String ip = ServletUtil.getClientIP(httpServletRequest); + attributes.put("ip", ip); + // + String userAgent = ServletUtil.getHeaderIgnoreCase(httpServletRequest, HttpHeaders.USER_AGENT); + attributes.put(HttpHeaders.USER_AGENT, userAgent); + attributes.put("userInfo", userModel); + return true; + } + return false; + } + + /** + * 检查权限 + * + * @param userInfo 用户 + * @param attributes 属性 + * @param handlerType 功能类型 + * @return 错误消息 + */ + private String checkPermission(UserModel userInfo, Map attributes, HandlerType handlerType) { + Object dataItem = attributes.get("dataItem"); + Object nodeInfo = attributes.get("nodeInfo"); + Object optData = dataItem == null ? nodeInfo : dataItem; + String workspaceId = BeanUtil.getProperty(optData, "workspaceId"); + //? : BeanUtil.getProperty(dataItem, "workspaceId"); + String useWorkspaceId; + if (StrUtil.equals(workspaceId, ServerConst.WORKSPACE_GLOBAL)) { + // 操作工作空间 + useWorkspaceId = (String) attributes.get("workspaceId"); + } else { + // 数据工作空间 + useWorkspaceId = workspaceId; + if (optData instanceof BaseWorkspaceModel && !StrUtil.equals(workspaceId, (String) attributes.get("workspaceId"))) { + return I18nMessageUtil.get("i18n.data_workspace_mismatch.ae1d"); + } + } + if (optData instanceof BaseWorkspaceModel) { + if (StrUtil.isEmpty(useWorkspaceId)) { + return I18nMessageUtil.get("i18n.no_workspace_found_for_data.ac0f"); + } + // 将数据的工作空间设置为当前操作的工作空间 + BeanUtil.setProperty(optData, "workspaceId", useWorkspaceId); + } + // + if (userInfo.isSuperSystemUser()) { + return StrUtil.EMPTY; + } + if (userInfo.isDemoUser()) { + return PermissionInterceptor.DEMO_TIP.get(); + } + boolean isAssetsManager = Convert.toBool(attributes.get("isAssetsManager"), false); + if (isAssetsManager && !userInfo.isSystemUser()) { + // 判断资产权限 + return I18nMessageUtil.get("i18n.no_asset_management_permission.739e"); + } + Supplier nodeUpgradeName = ClassFeature.NODE_UPGRADE.getName(); + if (handlerType == HandlerType.nodeUpdate) { + return StrUtil.format(I18nMessageUtil.get("i18n.no_permission_for_function.b63d"), I18nMessageUtil.get(nodeUpgradeName.get())); + } + Class handlerClass = handlerType.getHandlerClass(); + SystemPermission systemPermission = handlerClass.getAnnotation(SystemPermission.class); + if (systemPermission != null) { + if (!userInfo.isSuperSystemUser()) { + return StrUtil.format(I18nMessageUtil.get("i18n.no_permission_for_function.b63d"), I18nMessageUtil.get(nodeUpgradeName.get())); + } + } + Feature feature = handlerClass.getAnnotation(Feature.class); + MethodFeature method = feature.method(); + ClassFeature cls = feature.cls(); + UserBindWorkspaceModel.PermissionResult permissionResult = userBindWorkspaceService.checkPermission(userInfo, useWorkspaceId + StrUtil.DASHED + method.name()); + if (permissionResult.isSuccess()) { + return StrUtil.EMPTY; + } + return permissionResult.errorMsg(StrUtil.format(I18nMessageUtil.get("i18n.corresponding_function.5bb5"), I18nMessageUtil.get(cls.getName().get()), I18nMessageUtil.get(method.getName().get()))); + } + + private BaseWorkspaceModel checkData(HandlerType handlerType, UserModel userModel, HttpServletRequest httpServletRequest) { + String id = httpServletRequest.getParameter("id"); + BaseWorkspaceService workspaceService = SpringUtil.getBean(handlerType.getServiceClass()); + return workspaceService.getByKey(id, userModel); + } + + /** + * 解析参数,获取对应的数据 + * + * @param handlerType 操作类型 + * @param userModel 用户 + * @param httpServletRequest 请求信息 + * @return 数据 + */ + private Tuple checkAssetsData(HandlerType handlerType, UserModel userModel, HttpServletRequest httpServletRequest) { + String id = httpServletRequest.getParameter("id"); + return Opt.ofBlankAble(id).map(s -> { + BaseWorkspaceService workspaceService = SpringUtil.getBean(handlerType.getServiceClass()); + BaseWorkspaceModel workspaceModel = workspaceService.getByKey(s, userModel); + String assetsLinkDataId = BeanUtil.getProperty(workspaceModel, handlerType.getAssetsLinkDataId()); + BaseDbService assetsServiceClass = SpringUtil.getBean(handlerType.getAssetsServiceClass()); + return new Tuple(assetsServiceClass.getByKey(assetsLinkDataId, false), false, workspaceModel); + }).orElseGet(() -> { + String assetsLinkDataId = httpServletRequest.getParameter(handlerType.getAssetsLinkDataId()); + if (StrUtil.isEmpty(assetsLinkDataId)) { + return null; + } + BaseDbService assetsServiceClass = SpringUtil.getBean(handlerType.getAssetsServiceClass()); + return new Tuple(assetsServiceClass.getByKey(assetsLinkDataId, false), true, null); + }); + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + if (exception != null) { + log.error("afterHandshake", exception); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/ServiceFileTailWatcher.java b/modules/server/src/main/java/org/dromara/jpom/socket/ServiceFileTailWatcher.java new file mode 100644 index 0000000000..59d36ebf91 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/ServiceFileTailWatcher.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.BaseFileTailWatcher; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.WebSocketSession; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文件跟随器 + * + * @author bwcx_jzy + * @since 2019/07/21 + */ +@Slf4j +public class ServiceFileTailWatcher extends BaseFileTailWatcher { + private static final ConcurrentHashMap> CONCURRENT_HASH_MAP = new SafeConcurrentHashMap<>(); + + private static Charset charset; + + public static void setCharset(Charset charset) { + ServiceFileTailWatcher.charset = charset; + } + + private ServiceFileTailWatcher(File logFile) { + super(logFile, charset); + } + + public static int getOneLineCount() { + return CONCURRENT_HASH_MAP.size(); + } + + /** + * 添加文件监听 + * + * @param file 文件 + * @param session 会话 + * @throws IOException 异常 + */ + public static void addWatcher(File file, WebSocketSession session) throws IOException { + if (!file.exists() || file.isDirectory()) { + throw new IOException(I18nMessageUtil.get("i18n.file_or_directory_not_found.f03e") + file.getPath()); + } + ServiceFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.computeIfAbsent(file, s -> { + try { + return new ServiceFileTailWatcher<>(file); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.create_file_watch_failure.bc1a"), e); + return null; + } + }); + if (agentFileTailWatcher == null) { + throw new IOException(I18nMessageUtil.get("i18n.load_file_failure.86cc") + file.getPath()); + } + if (agentFileTailWatcher.add(session, FileUtil.getName(file))) { + agentFileTailWatcher.start(); + } + } + + /** + * 有客户端离线 + * + * @param session 会话 + */ + public static void offline(WebSocketSession session) { + Collection> collection = CONCURRENT_HASH_MAP.values(); + for (ServiceFileTailWatcher agentFileTailWatcher : collection) { + agentFileTailWatcher.socketSessions.removeIf(session::equals); + if (agentFileTailWatcher.socketSessions.isEmpty()) { + agentFileTailWatcher.close(); + } + } + } + + /** + * 关闭文件 + * + * @param fileName 文件 + */ + public static void offlineFile(File fileName) { + ServiceFileTailWatcher agentFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); + if (null == agentFileTailWatcher) { + return; + } + Set socketSessions = agentFileTailWatcher.socketSessions; + for (WebSocketSession socketSession : socketSessions) { + offline(socketSession); + } + agentFileTailWatcher.close(); + } + + /** + * 关闭文件读取流 + * + * @param fileName 文件名 + * @param session 回话 + */ + public static void offlineFile(File fileName, WebSocketSession session) { + ServiceFileTailWatcher serviceFileTailWatcher = CONCURRENT_HASH_MAP.get(fileName); + if (null == serviceFileTailWatcher) { + return; + } + Set socketSessions = serviceFileTailWatcher.socketSessions; + for (WebSocketSession socketSession : socketSessions) { + if (socketSession.equals(session)) { + offline(socketSession); + break; + } + } + if (serviceFileTailWatcher.socketSessions.isEmpty()) { + serviceFileTailWatcher.close(); + } + } + + @Override + protected boolean send(T session, String msg) throws IOException { + return SocketSessionUtil.send((WebSocketSession) session, msg); + } + + /** + * 关闭 + */ + @Override + protected void close() { + super.close(); + // 清理线程记录 + CONCURRENT_HASH_MAP.remove(this.logFile); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/AgentLogHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/AgentLogHandler.java new file mode 100644 index 0000000000..2687d13fb2 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/AgentLogHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.transport.IProxyWebSocket; + +import java.io.IOException; +import java.util.Map; + +/** + * 插件端系统日志消息控制器 + * + * @author bwcx_jzy + * @since 2023/12/26 + */ +@Feature(cls = ClassFeature.AGENT_LOG, method = MethodFeature.EXECUTE) +@Slf4j +public class AgentLogHandler extends BaseProxyHandler { + + public AgentLogHandler() { + super(NodeUrl.Socket_SystemLog); + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[]{}; + } + + @Override + protected String handleTextMessage(Map attributes, IProxyWebSocket proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + proxySession.send(json.toString()); + return null; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/BaseTerminalHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/BaseTerminalHandler.java new file mode 100644 index 0000000000..0ff8045933 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/BaseTerminalHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.socket.BaseHandler; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; + +/** + * @author bwcx_jzy + * @since 2022/2/10 + */ +@Slf4j +public abstract class BaseTerminalHandler extends BaseHandler { + + protected void sendBinary(WebSocketSession session, String msg) { + if (msg == null) { + return; + } + BinaryMessage byteBuffer = new BinaryMessage(msg.getBytes()); + try { + SocketSessionUtil.send(session, byteBuffer); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_failure_prefix.6f8c") + msg, e); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/ConsoleHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/ConsoleHandler.java new file mode 100644 index 0000000000..7261db7fdf --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/ConsoleHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.util.ArrayUtil; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.transport.IProxyWebSocket; + +import java.io.IOException; +import java.util.Map; + +/** + * 控制台消息处理器 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@Feature(cls = ClassFeature.PROJECT_CONSOLE, method = MethodFeature.EXECUTE) +public class ConsoleHandler extends BaseProxyHandler { + + public ConsoleHandler() { + super(NodeUrl.TopSocket); + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[]{"projectId", attributes.get("projectId")}; + } + + @Override + protected String handleTextMessage(Map attributes, + IProxyWebSocket proxySession, + JSONObject json, + ConsoleCommandOp consoleCommandOp) throws IOException { + //ProjectInfoCacheModel dataItem = (ProjectInfoCacheModel) attributes.get("dataItem"); +// UserModel userModel = (UserModel) attributes.get("userInfo"); +// if (RunMode.Dsl.name().equals(dataItem.getRunMode()) && userModel.isDemoUser()) { +// if (consoleCommandOp == ConsoleCommandOp.stop || consoleCommandOp == ConsoleCommandOp.start || consoleCommandOp == ConsoleCommandOp.restart) { +// return PermissionInterceptor.DEMO_TIP; +// } +// } + ConsoleCommandOp[] commandOps = new ConsoleCommandOp[]{ConsoleCommandOp.heart, ConsoleCommandOp.showlog}; + if (!ArrayUtil.contains(commandOps, consoleCommandOp)) { + super.logOpt(this.getClass(), attributes, json); + } + proxySession.send(json.toString()); + return null; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerCliHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerCliHandler.java new file mode 100644 index 0000000000..d9c8f4ac0b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerCliHandler.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONValidator; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.util.SocketSessionUtil; +import org.dromara.jpom.util.StringUtil; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * docker cli + * + * @author bwcx_jzy + * @since 2022/02/10 + */ +@Feature(cls = ClassFeature.DOCKER, method = MethodFeature.EXECUTE) +@Slf4j +public class DockerCliHandler extends BaseTerminalHandler { + + private static final ConcurrentHashMap HANDLER_ITEM_CONCURRENT_HASH_MAP = new SafeConcurrentHashMap<>(); + + + @Override + public void afterConnectionEstablishedImpl(WebSocketSession session) throws Exception { + super.afterConnectionEstablishedImpl(session); + MachineDockerServer machineDockerServer = SpringUtil.getBean(MachineDockerServer.class); + Map attributes = session.getAttributes(); + MachineDockerModel dockerInfoModel = (MachineDockerModel) attributes.get("machineDocker"); + DockerInfoService dockerInfoService = SpringUtil.getBean(DockerInfoService.class); + String containerId = (String) attributes.get("containerId"); + // + HandlerItem handlerItem; + try { + DockerInfoModel model = new DockerInfoModel(); + model.setMachineDockerId(dockerInfoModel.getId()); + model = dockerInfoService.queryByBean(model); + Map parameter = machineDockerServer.toParameter(dockerInfoModel); + handlerItem = new HandlerItem(session, dockerInfoModel, parameter, containerId); + handlerItem.startRead(); + } catch (Exception e) { + // 输出超时日志 @author jzy + log.error(I18nMessageUtil.get("i18n.docker_console_connection_timeout.b2c7"), e); + sendBinary(session, I18nMessageUtil.get("i18n.docker_console_connection_timeout.b2c7")); + this.destroy(session); + return; + } + HANDLER_ITEM_CONCURRENT_HASH_MAP.put(session.getId(), handlerItem); + // + Thread.sleep(1000); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); + if (handlerItem == null) { + sendBinary(session, I18nMessageUtil.get("i18n.already_offline.d3b5")); + IoUtil.close(session); + return; + } + String payload = message.getPayload(); + JSONValidator.Type type = StringUtil.validatorJson(payload); + if (type == JSONValidator.Type.Object) { + JSONObject jsonObject = JSONObject.parseObject(payload); + String data = jsonObject.getString("data"); + if (StrUtil.equals(data, "jpom-heart")) { + // 心跳消息不转发 + return; + } + if (StrUtil.equals(data, "resize")) { + // 缓存区大小 + handlerItem.resize(jsonObject); + return; + } + } + try { + handlerItem.sendCommand(payload); + } catch (Exception e) { + sendBinary(session, "Failure:" + e.getMessage()); + log.error(I18nMessageUtil.get("i18n.command_execution_exception.4ccd"), e); + } + } + + private class HandlerItem implements Runnable, AutoCloseable { + private final WebSocketSession session; + private final MachineDockerModel dockerInfoModel; + private final Map map; + private PipedInputStream inputStream = new PipedInputStream(); + private PipedOutputStream outputStream = new PipedOutputStream(inputStream); + private String containerId; + private Thread thread; + + HandlerItem(WebSocketSession session, MachineDockerModel dockerInfoModel, Map map, String containerId) throws IOException { + this.session = session; + this.dockerInfoModel = dockerInfoModel; + this.containerId = containerId; + this.map = map; + } + + void startRead() { + I18nThreadUtil.execute(this); + } + + private void sendCommand(String data) throws Exception { + if (this.outputStream == null) { + return; + } + this.outputStream.write(data.getBytes()); + this.outputStream.flush(); + } + + /** + * 调整 缓存区大小 + * + * @param jsonObject 参数 + */ + private void resize(JSONObject jsonObject) { + Integer rows = Convert.toInt(jsonObject.getString("rows"), 10); + Integer cols = Convert.toInt(jsonObject.getString("cols"), 10); + Integer wp = Convert.toInt(jsonObject.getString("wp"), 10); + Integer hp = Convert.toInt(jsonObject.getString("hp"), 10); + map.put("sizeHeight", rows); + map.put("sizeWidth", cols); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + try { + plugin.execute("resizeExec", map); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.container_command_execution_exception.a14a"), e); + sendBinary(session, I18nMessageUtil.get("i18n.execution_exception_message.ef79") + e.getMessage()); + } + } + + @Override + public void run() { + map.put("containerId", containerId); + thread = Thread.currentThread(); + Consumer logConsumer = s -> { + if (StrUtil.startWith(s, "CALLBACK_EXECID:")) { + // 终端id + String execId = StrUtil.removePrefix(s, "CALLBACK_EXECID:"); + session.getAttributes().put("execId", execId); + map.put("execId", execId); + return; + } + sendBinary(session, s); + }; + map.put("charset", CharsetUtil.CHARSET_UTF_8); + map.put("stdin", inputStream); + map.put("logConsumer", logConsumer); + Consumer errorConsumer = s -> { + sendBinary(session, s); + if (StrUtil.equals(s, "exit")) { + // 退出 + destroy(session); + } + }; + map.put("errorConsumer", errorConsumer); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + try { + plugin.execute("exec", map); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.container_command_execution_exception.a14a"), e); + sendBinary(session, I18nMessageUtil.get("i18n.execution_exception_message.ef79") + e.getMessage()); + } + log.debug(I18nMessageUtil.get("i18n.docker_exec_terminal_process_ended.c734"), dockerInfoModel.getName()); + // 标记自动结束 + this.containerId = null; + } + + private void tryExit() throws Exception { + if (this.containerId == null) { + // 如果线程已经结束,不再尝试发送消息 + return; + } + // ctrl + c + this.sendCommand(String.valueOf((char) 3)); + ThreadUtil.sleep(100); + // ctrl + c + this.sendCommand(String.valueOf((char) 3)); + ThreadUtil.sleep(100); + // quit + this.sendCommand("quit"); + this.sendCommand(String.valueOf((char) 13)); + ThreadUtil.sleep(100); + // exit + this.sendCommand("exit"); + this.sendCommand(String.valueOf((char) 13)); + ThreadUtil.sleep(100); + } + + @Override + public void close() { + if (this.inputStream == null) { + // 避免多次调用 + return; + } + Object execId = session.getAttributes().get("execId"); + try { + // 多次尝试退出,可能终端内部进入交互命令行 + for (int i = 0; i < 3; i++) { + this.tryExit(); + } + // + Optional.ofNullable(this.thread).ifPresent(Thread::interrupt); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.container_command_execution_exception.a14a"), e); + } + log.debug(I18nMessageUtil.get("i18n.close_docker_exec_terminal.fec3"), dockerInfoModel.getName(), execId); + IoUtil.close(this.inputStream); + IoUtil.close(this.outputStream); + this.inputStream = null; + this.outputStream = null; + } + } + + @Override + public void destroy(WebSocketSession session) { + HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.remove(session.getId()); + IoUtil.close(handlerItem); + IoUtil.close(session); + SocketSessionUtil.close(session); + } +} 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 new file mode 100644 index 0000000000..2c506432e5 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerLogHandler.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.JsonMessage; +import cn.keepbx.jpom.plugins.IPlugin; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.func.assets.model.MachineDockerModel; +import org.dromara.jpom.func.assets.server.MachineDockerServer; +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.plugin.PluginFactory; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.system.ServerConfig; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.WebSocketSession; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * 容器 + * + * @author bwcx_jzy + * @since 2022/02/10 + */ +@Feature(cls = ClassFeature.DOCKER, method = MethodFeature.EXECUTE) +@Slf4j +public class DockerLogHandler extends BaseProxyHandler { + + + @Override + protected void init(WebSocketSession session, Map attributes) throws Exception { + super.init(session, attributes); + // + Object data = attributes.get("dataItem"); + Object machineData = attributes.get("machineDocker"); + String dataName = BeanUtil.getProperty(data, "name"); + String machineDataName = BeanUtil.getProperty(machineData, "name"); + this.sendMsg(session, I18nMessageUtil.get("i18n.connection_successful_with_message.5cf2") + StrUtil.emptyToDefault(dataName, machineDataName) + StrUtil.CRLF); + } + + public DockerLogHandler() { + super(null); + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[0]; + } + + @Override + protected String handleTextMessage(Map attributes, WebSocketSession session, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + MachineDockerModel dockerInfoModel = (MachineDockerModel) attributes.get("machineDocker"); + if (consoleCommandOp == ConsoleCommandOp.heart) { + return null; + } + if (consoleCommandOp == ConsoleCommandOp.showlog) { + MachineDockerServer machineDockerServer = SpringUtil.getBean(MachineDockerServer.class); + ServerConfig serverConfig = SpringUtil.getBean(ServerConfig.class); + super.logOpt(this.getClass(), attributes, json); + String containerId = json.getString("containerId"); + Map map = machineDockerServer.toParameter(dockerInfoModel); + map.put("containerId", containerId); + int tail = json.getIntValue("tail"); + UserModel userModel = (UserModel) attributes.get("userInfo"); + if (userModel == null) { + return I18nMessageUtil.get("i18n.user_not_exist.4892"); + } + if (tail > 0) { + map.put("tail", tail); + } + String uuid = IdUtil.fastSimpleUUID(); + File file = FileUtil.file(serverConfig.getUserTempPath(userModel.getId()), "docker-log", uuid + ".log"); + LogRecorder logRecorder = LogRecorder.builder().file(file).build(); + Consumer consumer = s -> { + try { + logRecorder.append(s); + SocketSessionUtil.send(session, s); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.send_message_exception.7817"), e); + } + }; + attributes.put("uuid", uuid); + attributes.put("logRecorder", logRecorder); + map.put("uuid", uuid); + map.put("charset", CharsetUtil.CHARSET_UTF_8); + map.put("consumer", consumer); + map.put("timestamps", json.getBoolean("timestamps")); + I18nThreadUtil.execute(() -> { + attributes.put("thread", Thread.currentThread()); + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + try { + plugin.execute("logContainer", map); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.container_log_fetch_exception.591a"), e); + try { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.execution_exception_message.ef79") + e.getMessage()); + } catch (IOException ex) { + log.error(I18nMessageUtil.get("i18n.send_message_exception.7817"), e); + } + } + log.debug(I18nMessageUtil.get("i18n.docker_log_thread_ended.8230"), dockerInfoModel.getName(), uuid); + }); + SocketSessionUtil.send(session, JsonMessage.getString(200, "JPOM_MSG_UUID", uuid)); + } else { + return null; + } + return null; + } + + + @Override + public void destroy(WebSocketSession session) { + // + super.destroy(session); + Map attributes = session.getAttributes(); + String uuid = (String) attributes.get("uuid"); + try { + IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME); + Map map = MapUtil.of("uuid", uuid); + plugin.execute("closeAsyncResource", map); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.close_resource_failure.dc66"), e); + } + LogRecorder logRecorder = (LogRecorder) attributes.get("logRecorder"); + IoUtil.close(logRecorder); + // 删除日志缓存 + UserModel userModel = (UserModel) attributes.get("userInfo"); + Optional.ofNullable(userModel).ifPresent(userModel1 -> { + ServerConfig serverConfig = SpringUtil.getBean(ServerConfig.class); + File file = FileUtil.file(serverConfig.getUserTempPath(userModel1.getId()), "docker-log", uuid + ".log"); + FileUtil.del(file); + }); + Thread thread = (Thread) attributes.get("thread"); + Optional.ofNullable(thread).ifPresent(Thread::interrupt); + SocketSessionUtil.close(session); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/FreeScriptHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/FreeScriptHandler.java new file mode 100644 index 0000000000..f444e9f721 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/FreeScriptHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.transport.IProxyWebSocket; +import org.dromara.jpom.util.CommandUtil; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * @author bwcx_jzy1 + * @since 2024/4/26 + */ +@Feature(cls = ClassFeature.FREE_SCRIPT, method = MethodFeature.EXECUTE) +@Slf4j +public class FreeScriptHandler extends BaseProxyHandler { + + public FreeScriptHandler() { + super(NodeUrl.FreeScriptRun); + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[]{}; + } + + @Override + protected String handleTextMessage(Map attributes, WebSocketSession session, IProxyWebSocket proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + + String content = json.getString("content"); + if (StrUtil.isEmpty(content)) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_content_to_execute.66aa")); + session.close(); + return null; + } + + MachineNodeModel machine = (MachineNodeModel) attributes.get("machine"); + String osName = machine.getOsName(); + String template = StrUtil.EMPTY; + boolean appendTemplate = json.getBooleanValue("appendTemplate"); + if (appendTemplate && StrUtil.isNotEmpty(osName)) { + InputStream templateInputStream; + if (osName.startsWith("Windows")) { + templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX_WINDOWS); + } else { + templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX_UNIX); + } + template = IoUtil.readUtf8(templateInputStream); + } + + String uuid = IdUtil.fastSimpleUUID(); + json.put("tag", uuid); + json.put("content", template + content); + String path = json.getString("path"); + json.put("path", StrUtil.emptyToDefault(path, "./")); + json.put("environment", new JSONObject()); + attributes.put("uuidTag", uuid); + proxySession.send(json.toString()); + return null; + } + + @Override + protected void onProxyMessage(WebSocketSession session, String msg) { + if (StrUtil.equals(msg, "JPOM_SYSTEM_TAG:" + session.getAttributes().get("uuidTag"))) { + // 执行结束 + try { + session.close(); + } catch (IOException e) { + log.error(I18nMessageUtil.get("i18n.close_client_session_exception.530a"), e); + } + return; + } + super.onProxyMessage(session, msg); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/NodeScriptHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/NodeScriptHandler.java new file mode 100644 index 0000000000..b01d67e938 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/NodeScriptHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.extra.spring.SpringUtil; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.node.NodeScriptCacheModel; +import org.dromara.jpom.model.node.NodeScriptExecuteLogCacheModel; +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.node.script.NodeScriptExecuteLogServer; +import org.dromara.jpom.service.node.script.NodeScriptServer; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.transport.IProxyWebSocket; + +import java.io.IOException; +import java.util.Map; + +/** + * 脚本模板消息控制器 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@Feature(cls = ClassFeature.NODE_SCRIPT, method = MethodFeature.EXECUTE) +public class NodeScriptHandler extends BaseProxyHandler { + + public NodeScriptHandler() { + super(NodeUrl.Script_Run); + } + + @Override + protected Object[] getParameters(Map attributes) { + NodeScriptCacheModel scriptModel = (NodeScriptCacheModel) attributes.get("dataItem"); + return new Object[]{"id", attributes.get("scriptId"), "workspaceId", scriptModel.getWorkspaceId()}; + } + + @Override + protected String handleTextMessage(Map attributes, IProxyWebSocket proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + if (consoleCommandOp != ConsoleCommandOp.heart) { + super.logOpt(this.getClass(), attributes, json); + } + if (consoleCommandOp == ConsoleCommandOp.start) { + // 开始执行 + String executeId = this.createLog(attributes); + json.put(Const.SOCKET_MSG_TAG, Const.SOCKET_MSG_TAG); + json.put("executeId", executeId); + } + proxySession.send(json.toString()); + return null; + } + + /** + * 创建执行日志 + * + * @param attributes 参数属性 + * @return 执行ID + */ + private String createLog(Map attributes) { + NodeModel nodeInfo = (NodeModel) attributes.get("nodeInfo"); + UserModel userModel = (UserModel) attributes.get("userInfo"); + NodeScriptCacheModel dataItem = (NodeScriptCacheModel) attributes.get("dataItem"); + NodeScriptExecuteLogServer logServer = SpringUtil.getBean(NodeScriptExecuteLogServer.class); + NodeScriptServer nodeScriptServer = SpringUtil.getBean(NodeScriptServer.class); + // + try { + BaseServerController.resetInfo(userModel); + // + NodeScriptCacheModel nodeScriptCacheModel = new NodeScriptCacheModel(); + nodeScriptCacheModel.setId(dataItem.getId()); + nodeScriptCacheModel.setLastRunUser(userModel.getId()); + nodeScriptServer.updateById(nodeScriptCacheModel); + // + NodeScriptExecuteLogCacheModel nodeScriptExecuteLogCacheModel = new NodeScriptExecuteLogCacheModel(); + nodeScriptExecuteLogCacheModel.setScriptId(dataItem.getScriptId()); + nodeScriptExecuteLogCacheModel.setNodeId(nodeInfo.getId()); + nodeScriptExecuteLogCacheModel.setScriptName(dataItem.getName()); + nodeScriptExecuteLogCacheModel.setTriggerExecType(0); + nodeScriptExecuteLogCacheModel.setWorkspaceId(nodeInfo.getWorkspaceId()); + logServer.insert(nodeScriptExecuteLogCacheModel); + return nodeScriptExecuteLogCacheModel.getId(); + } finally { + BaseServerController.removeAll(); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/NodeUpdateHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/NodeUpdateHandler.java new file mode 100644 index 0000000000..ae15a9e9db --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/NodeUpdateHandler.java @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.unit.DataSize; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.Type; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.configuration.NodeConfig; +import org.dromara.jpom.func.assets.model.MachineNodeModel; +import org.dromara.jpom.func.assets.server.MachineNodeServer; +import org.dromara.jpom.model.AgentFileModel; +import org.dromara.jpom.model.UploadFileModel; +import org.dromara.jpom.model.WebSocketMessageModel; +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.SystemParametersServer; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.transport.*; +import org.springframework.web.socket.WebSocketSession; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +/** + * 节点管理控制器 + * + * @author lf + */ +@SystemPermission(superUser = true) +@Feature(cls = ClassFeature.UPGRADE_NODE_LIST, method = MethodFeature.EXECUTE) +@Slf4j +public class NodeUpdateHandler extends BaseProxyHandler { + + private final ConcurrentMap clientMap = new SafeConcurrentHashMap<>(); + + private static final int CHECK_COUNT = 120; + + /** + * 初始等待时间 + */ + private static final int INIT_WAIT = 10 * 1000; + + private final SystemParametersServer systemParametersServer; + private final MachineNodeServer machineNodeServer; + private final NodeConfig nodeConfig; + + public NodeUpdateHandler(MachineNodeServer machineNodeServer, + SystemParametersServer systemParametersServer, + NodeConfig nodeConfig) { + super(null); + this.machineNodeServer = machineNodeServer; + this.systemParametersServer = systemParametersServer; + this.nodeConfig = nodeConfig; + //systemParametersServer = SpringUtil.getBean(SystemParametersServer.class); +// nodeService = SpringUtil.getBean(NodeService.class); + } + + @Override + protected void init(WebSocketSession session, Map attributes) throws Exception { + super.init(session, attributes); + + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[]{}; + } + + @Override + protected void showHelloMsg(Map attributes, WebSocketSession session) { + + } + + private void pullNodeList(WebSocketSession session, String ids) { + List split = StrUtil.split(ids, StrUtil.COMMA); + List nodeModelList = machineNodeServer.listById(split); + if (nodeModelList == null) { + this.onError(session, I18nMessageUtil.get("i18n.node_info_not_found.2c8c") + ids); + return; + } + for (MachineNodeModel model : nodeModelList) { + IProxyWebSocket nodeClient = clientMap.computeIfAbsent(model.getId(), s -> { + INodeInfo nodeInfo = NodeForward.coverNodeInfo(model); + IUrlItem urlItem = NodeForward.parseUrlItem(model, StrUtil.EMPTY, NodeUrl.NodeUpdate, DataContentType.FORM_URLENCODED); + + IProxyWebSocket proxySession = TransportServerFactory.get().websocket(nodeInfo, urlItem); + proxySession.onMessage(msg -> sendMsg(session, msg)); + return proxySession; + }); + // 连接节点 + I18nThreadUtil.execute(() -> { + try { + if (!nodeClient.isConnected()) { + nodeClient.reconnectBlocking(); + } + WebSocketMessageModel command = new WebSocketMessageModel("getVersion", model.getId()); + nodeClient.send(command.toString()); + } catch (Exception e) { + String closeStatusMsg = nodeClient.getCloseStatusMsg(); + log.error(I18nMessageUtil.get("i18n.create_plugin_endpoint_connection_failure.30f8"), closeStatusMsg, e); + IProxyWebSocket webSocket = clientMap.remove(model.getId()); + IoUtil.close(webSocket); + this.onError(session, StrUtil.format(I18nMessageUtil.get("i18n.connect_plugin_failed.e492"), closeStatusMsg, e.getMessage(), model.getId())); + } + }); + } + } + + @Override + public void destroy(WebSocketSession session) { + clientMap.values().forEach(iProxyWebSocket -> { + if (iProxyWebSocket.isConnected()) { + try { + iProxyWebSocket.close(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.close_connection_exception.c855"), e); + } + } + }); + clientMap.clear(); + // + super.destroy(session); + } + + @Override + protected String handleTextMessage(Map attributes, WebSocketSession session, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + WebSocketMessageModel model = WebSocketMessageModel.getInstance(json.toString()); + String command = model.getCommand(); + switch (command) { + case "getAgentVersion": + model.setData(getAgentVersion()); + break; + case "updateNode": + super.logOpt(this.getClass(), attributes, json); + updateNode(model, session); + break; + case "heart": + for (Map.Entry entry : clientMap.entrySet()) { + String key = entry.getKey(); + IProxyWebSocket iProxyWebSocket = entry.getValue(); + try { + iProxyWebSocket.send(model.toString()); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.heartbeat_message_forwarding_failed.89cc"), key, e.getMessage()); + } + } + break; + default: { + if (StrUtil.startWith(command, "getNodeList:")) { + String ids = StrUtil.removePrefix(command, "getNodeList:"); + if (StrUtil.isNotEmpty(ids)) { + pullNodeList(session, ids); + } + } + } + break; + } + if (model.getData() != null) { + return model.toString(); + } + return null; + } + + private void onError(WebSocketSession session, String msg) { + this.onError(session, msg, StrUtil.EMPTY); + } + + private void onError(WebSocketSession session, String msg, String nodeId) { + WebSocketMessageModel error = new WebSocketMessageModel("onError", nodeId); + error.setData(msg); + this.sendMsg(error, session); + } + + /** + * 更新节点 + * + * @param model 参数 + */ + private void updateNode(WebSocketMessageModel model, WebSocketSession session) { + JSONObject params = (JSONObject) model.getParams(); + JSONArray ids = params.getJSONArray("ids"); + if (CollUtil.isEmpty(ids)) { + return; + } + String protocol = params.getString("protocol"); + boolean http = StrUtil.equalsIgnoreCase(protocol, "http"); + try { + AgentFileModel agentFileModel = systemParametersServer.getConfig(AgentFileModel.ID, AgentFileModel.class); + // + if (agentFileModel == null || !FileUtil.exist(agentFileModel.getSavePath())) { + this.onError(session, I18nMessageUtil.get("i18n.agent_jar_not_exist.28ac")); + return; + } + JsonMessage error = JpomManifest.checkJpomJar(agentFileModel.getSavePath(), Type.Agent, false); + if (!error.success()) { + this.onError(session, I18nMessageUtil.get("i18n.agent_jar_damaged.74a8") + error.getMsg()); + return; + } + for (int i = 0; i < ids.size(); i++) { + String id = ids.getString(i); + MachineNodeModel node = machineNodeServer.getByKey(id); + if (node == null) { + this.onError(session, I18nMessageUtil.get("i18n.no_node_specified.fa3d") + id); + continue; + } + I18nThreadUtil.execute(() -> this.updateNodeItem(id, node, session, agentFileModel, http)); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.upgrade_failure.4ae2"), e); + this.onError(session, I18nMessageUtil.get("i18n.upgrade_failure_with_colon.59f1") + e.getMessage()); + } + } + + private boolean updateNodeItemHttp(MachineNodeModel machineNodeModel, WebSocketSession session, AgentFileModel agentFileModel) throws IOException { + File file = FileUtil.file(agentFileModel.getSavePath()); + + JsonMessage message = NodeForward.requestSharding(machineNodeModel, NodeUrl.SystemUploadJar, new JSONObject(), + file, + jsonObject -> NodeForward.request(machineNodeModel, NodeUrl.SystemUploadJarMerge, jsonObject), + (total, progressSize) -> { + UploadFileModel uploadFileModel = new UploadFileModel(); + uploadFileModel.setSize(total); + uploadFileModel.setCompleteSize(progressSize); + uploadFileModel.setId(machineNodeModel.getId()); + uploadFileModel.setVersion(agentFileModel.getVersion()); + // 更新进度 + WebSocketMessageModel model = new WebSocketMessageModel("updateNode", machineNodeModel.getId()); + model.setData(uploadFileModel); + NodeUpdateHandler.this.sendMsg(model, session); + }); + String id = machineNodeModel.getId(); + WebSocketMessageModel callbackRestartMessage = new WebSocketMessageModel("restart", id); + callbackRestartMessage.setData(message.getMsg()); + this.sendMsg(callbackRestartMessage, session); + if (!message.success()) { + return false; + } + // 先等待一会,太快可能还没重启 + ThreadUtil.sleep(INIT_WAIT); + int retryCount = 0; + try { + while (retryCount <= CHECK_COUNT) { + ++retryCount; + try { + ThreadUtil.sleep(1000L); + JsonMessage jsonMessage = NodeForward.request(machineNodeModel, StrUtil.EMPTY, NodeUrl.Info, "nodeId", id); + if (jsonMessage.success()) { + this.sendMsg(callbackRestartMessage.setData(I18nMessageUtil.get("i18n.restart_completed.42b8")), session); + return true; + } + } catch (Exception e) { + log.debug(I18nMessageUtil.get("i18n.node_connection_failed.8497"), id, e.getMessage()); + } + } + this.sendMsg(callbackRestartMessage.setData(I18nMessageUtil.get("i18n.reconnect_failure.7c01")), session); + } catch (Exception e) { + log.error("{}{}", I18nMessageUtil.get("i18n.reconnect_plugin_failure_after_upgrade.73e3"), id, e); + this.sendMsg(callbackRestartMessage.setData(I18nMessageUtil.get("i18n.reconnect_plugin_failure.cc6c")), session); + } + return false; + } + + private boolean updateNodeItemWebSocket(IProxyWebSocket client, String id, WebSocketSession session, AgentFileModel agentFileModel) throws IOException { + // 发送文件信息 + WebSocketMessageModel webSocketMessageModel = new WebSocketMessageModel("upload", id); + webSocketMessageModel.setNodeId(id); + webSocketMessageModel.setParams(agentFileModel); + client.send(webSocketMessageModel.toString()); + // + try (FileInputStream fis = new FileInputStream(agentFileModel.getSavePath())) { + // 发送文件内容 + int len; + int fileSliceSize = nodeConfig.getUploadFileSliceSize(); + byte[] buffer = new byte[(int) DataSize.ofMegabytes(fileSliceSize).toBytes()]; + while ((len = fis.read(buffer)) > 0) { + client.send(ByteBuffer.wrap(buffer, 0, len)); + } + } + WebSocketMessageModel restartMessage = new WebSocketMessageModel("restart", id); + client.send(restartMessage.toString()); + // 先等待一会,太快可能还没重启 + ThreadUtil.sleep(INIT_WAIT); + // 重启后尝试访问插件端,能够连接说明重启完毕 + //WebSocketMessageModel callbackRestartMessage = new WebSocketMessageModel("restart", id); + int retryCount = 0; + try { + while (retryCount <= CHECK_COUNT) { + try { + if (client.reconnect()) { + this.sendMsg(restartMessage.setData(I18nMessageUtil.get("i18n.restart_completed.42b8")), session); + return true; + } + } catch (Exception e) { + log.debug(I18nMessageUtil.get("i18n.node_connection_failed.8497"), id, e.getMessage()); + } + ThreadUtil.sleep(1000L); + ++retryCount; + } + this.sendMsg(restartMessage.setData(I18nMessageUtil.get("i18n.reconnect_failure.7c01")), session); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.reconnect_plugin_failure_after_upgrade.73e3") + id, e); + this.sendMsg(restartMessage.setData(I18nMessageUtil.get("i18n.reconnect_plugin_failure.cc6c")), session); + } + return false; + } + + private void updateNodeItem(String id, MachineNodeModel node, WebSocketSession session, AgentFileModel agentFileModel, boolean http) { + try { + IProxyWebSocket client = clientMap.get(node.getId()); + if (client == null) { + this.onError(session, I18nMessageUtil.get("i18n.plugin_not_initialized.3ea9"), id); + return; + } + if (client.isConnected()) { + boolean result = http ? this.updateNodeItemHttp(node, session, agentFileModel) : this.updateNodeItemWebSocket(client, id, session, agentFileModel); + if (result) { + // + WebSocketMessageModel command = new WebSocketMessageModel("getVersion", node.getId()); + client.send(command.toString()); + } + } else { + this.onError(session, I18nMessageUtil.get("i18n.node_connection_lost.b6c7"), id); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.upgrade_failure_with_colon.59f1") + id, e); + this.onError(session, I18nMessageUtil.get("i18n.node_upgrade_failed.4493") + e.getMessage(), id); + } + } + + private void sendMsg(WebSocketMessageModel model, WebSocketSession session) { + super.sendMsg(session, model.toString()); + } + + /** + * 获取当前系统缓存的Agent + * + * @return json + */ + private String getAgentVersion() { + AgentFileModel agentFileModel = systemParametersServer.getConfig(AgentFileModel.ID, AgentFileModel.class, agentFileModel1 -> { + if (agentFileModel1 == null || !FileUtil.exist(agentFileModel1.getSavePath())) { + return AgentFileModel.EMPTY; + } + return agentFileModel1; + }); + return agentFileModel == null ? null : JSONObject.toJSONString(agentFileModel); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/ServerScriptHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/ServerScriptHandler.java new file mode 100644 index 0000000000..ee4f1170c4 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/ServerScriptHandler.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.Const; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.script.ScriptExecuteLogModel; +import org.dromara.jpom.model.script.ScriptModel; +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.script.ScriptExecuteLogServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.socket.ServerScriptProcessBuilder; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.Map; + +/** + * 服务端脚本日志 + * + * @author bwcx_jzy + * @since 2022/1/19 + */ +@Feature(cls = ClassFeature.SCRIPT, method = MethodFeature.EXECUTE) +public class ServerScriptHandler extends BaseProxyHandler { + + private ScriptExecuteLogServer logServer; + private ScriptServer nodeScriptServer; + + @Override + protected void init(WebSocketSession session, Map attributes) throws Exception { + super.init(session, attributes); + // + this.logServer = SpringUtil.getBean(ScriptExecuteLogServer.class); + this.nodeScriptServer = SpringUtil.getBean(ScriptServer.class); + ScriptModel scriptModel = (ScriptModel) attributes.get("dataItem"); + this.sendMsg(session, I18nMessageUtil.get("i18n.connection_successful_with_message.5cf2") + scriptModel.getName()); + } + + public ServerScriptHandler() { + super(null); + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[0]; + } + + @Override + protected String handleTextMessage(Map attributes, WebSocketSession session, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + ScriptModel scriptModel = (ScriptModel) attributes.get("dataItem"); + if (consoleCommandOp == ConsoleCommandOp.heart) { + return null; + } + super.logOpt(this.getClass(), attributes, json); + switch (consoleCommandOp) { + case start: { + + String args = json.getString("args"); + String executeId = this.createLog(attributes, scriptModel); + json.put(Const.SOCKET_MSG_TAG, Const.SOCKET_MSG_TAG); + json.put("executeId", executeId); + ServerScriptProcessBuilder.addWatcher(scriptModel, executeId, args, session); + JsonMessage jsonMessage = new JsonMessage<>(200, I18nMessageUtil.get("i18n.start_execution.00d7")); + JSONObject jsonObject = jsonMessage.toJson(); + jsonObject.putAll(json); + this.sendMsg(session, jsonObject.toString()); + break; + } + case stop: { + String executeId = json.getString("executeId"); + if (StrUtil.isEmpty(executeId)) { + SocketSessionUtil.send(session, I18nMessageUtil.get("i18n.no_execution_id.68dc")); + session.close(); + return null; + } + ServerScriptProcessBuilder.stopRun(executeId); + break; + } + default: + return null; + } + return null; + } + + /** + * 创建执行日志 + * + * @param attributes 参数属性 + * @return 执行ID + */ + private String createLog(Map attributes, ScriptModel scriptModel) { + UserModel userModel = (UserModel) attributes.get("userInfo"); + + // + try { + BaseServerController.resetInfo(userModel); + // + ScriptModel scriptCacheModel = new ScriptModel(); + scriptCacheModel.setId(scriptModel.getId()); + scriptCacheModel.setLastRunUser(userModel.getId()); + nodeScriptServer.updateById(scriptCacheModel); + // + ScriptExecuteLogModel scriptExecuteLogCacheModel = logServer.create(scriptModel, 0); + return scriptExecuteLogCacheModel.getId(); + } finally { + BaseServerController.removeAll(); + } + } + + + @Override + public void destroy(WebSocketSession session) { + // + super.destroy(session); + ServerScriptProcessBuilder.stopWatcher(session); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/SshHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/SshHandler.java new file mode 100644 index 0000000000..6a6981401f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/SshHandler.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.NioUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.extra.ssh.ChannelType; +import cn.hutool.extra.ssh.JschUtil; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONValidator; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.common.i18n.I18nThreadUtil; +import org.dromara.jpom.func.assets.model.MachineSshModel; +import org.dromara.jpom.model.data.SshModel; +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.dblog.SshTerminalExecuteLogService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.user.UserBindWorkspaceService; +import org.dromara.jpom.util.SocketSessionUtil; +import org.dromara.jpom.util.StringUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * ssh 处理2 + * + * @author bwcx_jzy + * @since 2019/8/9 + */ +@Feature(cls = ClassFeature.SSH_TERMINAL, method = MethodFeature.EXECUTE) +@Slf4j +public class SshHandler extends BaseTerminalHandler { + + private static final ConcurrentHashMap HANDLER_ITEM_CONCURRENT_HASH_MAP = new SafeConcurrentHashMap<>(); + private static SshTerminalExecuteLogService sshTerminalExecuteLogService; + private static UserBindWorkspaceService userBindWorkspaceService; + private static SshService sshService; + + private static void init() { + if (sshTerminalExecuteLogService == null) { + sshTerminalExecuteLogService = SpringUtil.getBean(SshTerminalExecuteLogService.class); + } + if (userBindWorkspaceService == null) { + userBindWorkspaceService = SpringUtil.getBean(UserBindWorkspaceService.class); + } + if (sshService == null) { + sshService = SpringUtil.getBean(SshService.class); + } + } + + @Override + public void afterConnectionEstablishedImpl(WebSocketSession session) throws Exception { + super.afterConnectionEstablishedImpl(session); + init(); + Map attributes = session.getAttributes(); + MachineSshModel machineSshModel = (MachineSshModel) attributes.get("machineSsh"); + SshModel sshModel = (SshModel) attributes.get("dataItem"); + // + UserModel userInfo = (UserModel) attributes.get("userInfo"); + if (sshModel != null) { + // 判断是没有任何限制 + String workspaceId = sshModel.getWorkspaceId(); + boolean sshCommandNotLimited = userBindWorkspaceService.exists(userInfo, workspaceId + UserBindWorkspaceService.SSH_COMMAND_NOT_LIMITED); + attributes.put("sshCommandNotLimited", sshCommandNotLimited); + } else { + // 通过资产管理方式进入 + attributes.put("sshCommandNotLimited", true); + } + // + HandlerItem handlerItem; + try { + // + handlerItem = new HandlerItem(session, machineSshModel, sshModel); + handlerItem.startRead(); + } catch (Exception e) { + // 输出超时日志 @author jzy + log.error(I18nMessageUtil.get("i18n.ssh_console_connection_timeout.8eb3"), e); + sendBinary(session, I18nMessageUtil.get("i18n.ssh_console_connection_timeout.8eb3")); + this.destroy(session); + return; + } + HANDLER_ITEM_CONCURRENT_HASH_MAP.put(session.getId(), handlerItem); + // + Thread.sleep(1000); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); + if (handlerItem == null) { + sendBinary(session, I18nMessageUtil.get("i18n.already_offline.d3b5")); + IoUtil.close(session); + return; + } + String payload = message.getPayload(); + + JSONValidator.Type type = StringUtil.validatorJson(payload); + if (type == JSONValidator.Type.Object) { + JSONObject jsonObject = JSONObject.parseObject(payload); + String data = jsonObject.getString("data"); + if (StrUtil.equals(data, "jpom-heart")) { + // 心跳消息不转发 + return; + } + if (StrUtil.equals(data, "resize")) { + // 缓存区大小 + handlerItem.resize(jsonObject); + return; + } + } + // + Map attributes = session.getAttributes(); + UserModel userInfo = (UserModel) attributes.get("userInfo"); + boolean sshCommandNotLimited = (boolean) attributes.get("sshCommandNotLimited"); + try { + this.sendCommand(handlerItem, payload, userInfo, sshCommandNotLimited); + } catch (Exception e) { + sendBinary(session, "Failure:" + e.getMessage()); + log.error(I18nMessageUtil.get("i18n.command_execution_exception.4ccd"), e); + } + } + + private void sendCommand(HandlerItem handlerItem, String data, UserModel userInfo, boolean sshCommandNotLimited) throws Exception { + if (handlerItem.checkInput(data, userInfo, sshCommandNotLimited)) { + handlerItem.outputStream.write(data.getBytes()); + } else { + handlerItem.outputStream.write(I18nMessageUtil.get("i18n.no_permission_to_execute_command.04d4").getBytes()); + handlerItem.outputStream.flush(); + handlerItem.outputStream.write(new byte[]{3}); + } + handlerItem.outputStream.flush(); + } + + /** + * 记录终端执行记录 + * + * @param session 回话 + * @param command 命令行 + * @param refuse 是否拒绝 + */ + private void logCommands(WebSocketSession session, String command, boolean refuse) { + List split = StrUtil.splitTrim(command, StrUtil.CR); + // 最后一个是否为回车, 最后一个不是回车表示还未提交,还在缓存去待确认 + boolean all = StrUtil.endWith(command, StrUtil.CR); + int size = split.size(); + split = CollUtil.sub(split, 0, all ? size : size - 1); + if (CollUtil.isEmpty(split)) { + return; + } + // 获取基础信息 + Map attributes = session.getAttributes(); + UserModel userInfo = (UserModel) attributes.get("userInfo"); + String ip = (String) attributes.get("ip"); + String userAgent = (String) attributes.get(HttpHeaders.USER_AGENT); + MachineSshModel machineSshModel = (MachineSshModel) attributes.get("machineSsh"); + SshModel sshItem = (SshModel) attributes.get("dataItem"); + // + sshTerminalExecuteLogService.batch(userInfo, machineSshModel, sshItem, ip, userAgent, refuse, split); + } + + private class HandlerItem implements Runnable, AutoCloseable { + private final WebSocketSession session; + private final InputStream inputStream; + private final OutputStream outputStream; + private final Session openSession; + private final ChannelShell channel; + private final SshModel sshItem; + private final MachineSshModel machineSshModel; + private final StringBuilder nowLineInput = new StringBuilder(); + + HandlerItem(WebSocketSession session, MachineSshModel machineSshModel, SshModel sshModel) throws IOException { + this.session = session; + this.sshItem = sshModel; + this.machineSshModel = machineSshModel; + this.openSession = sshService.getSessionByModel(machineSshModel); + this.channel = (ChannelShell) JschUtil.createChannel(openSession, ChannelType.SHELL); + this.inputStream = channel.getInputStream(); + this.outputStream = channel.getOutputStream(); + } + + void startRead() throws JSchException { + this.channel.connect(machineSshModel.timeout()); + I18nThreadUtil.execute(this); + } + + /** + * 调整 缓存区大小 + * + * @param jsonObject 参数 + */ + private void resize(JSONObject jsonObject) { + Integer rows = Convert.toInt(jsonObject.getString("rows"), 10); + Integer cols = Convert.toInt(jsonObject.getString("cols"), 10); + Integer wp = Convert.toInt(jsonObject.getString("wp"), 10); + Integer hp = Convert.toInt(jsonObject.getString("hp"), 10); + this.channel.setPtySize(cols, rows, wp, hp); + } + + /** + * 添加到命令队列 + * + * @param msg 输入 + * @return 当前待确认待所有命令 + */ + private String append(String msg) { + char[] x = msg.toCharArray(); + if (x.length == 1 && x[0] == 127) { + // 退格键 + int length = nowLineInput.length(); + if (length > 0) { + nowLineInput.delete(length - 1, length); + } + } else { + nowLineInput.append(msg); + } + return nowLineInput.toString(); + } + + /** + * 检查输入是否包含禁止命令,记录执行记录 + * + * @param msg 输入 + * @param userInfo 用户 + * @param sshCommandNotLimited 是否解除限制 + * @return true 没有任何限制 + */ + public boolean checkInput(String msg, UserModel userInfo, boolean sshCommandNotLimited) { + String allCommand = this.append(msg); + boolean refuse; + // 超级管理员不限制,有权限都不限制 + boolean systemUser = userInfo.isSuperSystemUser() || sshCommandNotLimited; + if (StrUtil.equalsAny(msg, StrUtil.CR, StrUtil.TAB)) { + String join = nowLineInput.toString(); + if (StrUtil.equals(msg, StrUtil.CR)) { + nowLineInput.setLength(0); + } + // sshItem 可能为空 + refuse = sshItem == null || SshModel.checkInputItem(sshItem, join); + } else { + // 复制输出 + refuse = sshItem == null || SshModel.checkInputItem(sshItem, msg); + } + // 执行命令行记录 + logCommands(session, allCommand, refuse); + return systemUser || refuse; + } + + + @Override + public void run() { + try { + byte[] buffer = new byte[1024]; + int i; + //如果没有数据来,线程会一直阻塞在这个地方等待数据。 + while ((i = inputStream.read(buffer)) != NioUtil.EOF) { + sendBinary(session, new String(Arrays.copyOfRange(buffer, 0, i), machineSshModel.charset())); + } + } catch (Exception e) { + if (!this.openSession.isConnected()) { + log.error(I18nMessageUtil.get("i18n.ssh_error_string.6bdb"), e.getMessage()); + return; + } + log.error(I18nMessageUtil.get("i18n.read_error.7fa5"), e); + SshHandler.this.destroy(this.session); + } + } + + @Override + public void close() throws Exception { + IoUtil.close(this.inputStream); + IoUtil.close(this.outputStream); + JschUtil.close(this.channel); + JschUtil.close(this.openSession); + } + } + + @Override + public void destroy(WebSocketSession session) { + HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); + IoUtil.close(handlerItem); + IoUtil.close(session); + HANDLER_ITEM_CONCURRENT_HASH_MAP.remove(session.getId()); + SocketSessionUtil.close(session); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/SystemLogHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/SystemLogHandler.java new file mode 100644 index 0000000000..76caff5f23 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/SystemLogHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.socket.handler; + +import cn.hutool.core.io.FileUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.socket.BaseProxyHandler; +import org.dromara.jpom.socket.ConsoleCommandOp; +import org.dromara.jpom.socket.ServiceFileTailWatcher; +import org.dromara.jpom.system.LogbackConfig; +import org.dromara.jpom.util.SocketSessionUtil; +import org.springframework.web.socket.WebSocketSession; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +/** + * 脚本模板消息控制器 + * + * @author bwcx_jzy + * @since 2019/4/24 + */ +@Feature(cls = ClassFeature.SYSTEM_LOG, method = MethodFeature.EXECUTE) +@Slf4j +public class SystemLogHandler extends BaseProxyHandler { + + public SystemLogHandler() { + super(null); + } + + @Override + protected Object[] getParameters(Map attributes) { + return new Object[]{}; + } + + @Override + protected String handleTextMessage(Map attributes, WebSocketSession session, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException { + + String fileName = json.getString("fileName"); + if (consoleCommandOp == ConsoleCommandOp.heart) { + // 服务端心跳 + return null; + } + + super.logOpt(this.getClass(), attributes, json); + + // + if (consoleCommandOp == ConsoleCommandOp.showlog) { + + // 进入管理页面后需要实时加载日志 + File file = FileUtil.file(LogbackConfig.getPath(), fileName); + // + File nowFile = (File) attributes.get("nowFile"); + if (nowFile != null && !nowFile.equals(file)) { + // 离线上一个日志 + ServiceFileTailWatcher.offlineFile(file, session); + } + try { + ServiceFileTailWatcher.addWatcher(file, session); + attributes.put("nowFile", file); + } catch (Exception io) { + log.error(I18nMessageUtil.get("i18n.listen_log_changes.9081"), io); + SocketSessionUtil.send(session, io.getMessage()); + } + } + return null; + } + + @Override + public void destroy(WebSocketSession session) { + super.destroy(session); + ServiceFileTailWatcher.offline(session); + } +} 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 new file mode 100644 index 0000000000..33fac77b2b --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/ServerConfig.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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 cn.hutool.system.SystemUtil; +import lombok.Data; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +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.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +import java.io.File; +import java.util.Optional; + +/** + * @author bwcx_jzy + * @since 2022/12/17 + */ +@Configuration +@ConfigurationProperties("jpom") +@EnableConfigurationProperties({ + ClusterConfig.class, + SystemConfig.class, + NodeConfig.class, + UserConfig.class, + FileStorageConfig.class, + WebConfig.class}) +@Data +public class ServerConfig implements InitializingBean { + + private final JpomApplication configBean; + + public ServerConfig(JpomApplication configBean) { + this.configBean = configBean; + } + + /** + * 数据目录 + */ + private String path; + + /** + * 初始读取日志文件行号 + */ + private Integer initReadLine = 10; + /** + * 集群 配置信息 + */ + private ClusterConfig cluster; + /** + * 系统配置参数 + */ + private SystemConfig system; + /** + * 节点配置 + */ + private NodeConfig node; + /** + * 用户相关配置 + */ + private UserConfig user; + /** + * 前端配置 + */ + private WebConfig web; + /** + * 文件中心配置 + */ + private FileStorageConfig fileStorage = new FileStorageConfig(); + + public SystemConfig getSystem() { + return Optional.ofNullable(this.system).orElseGet(() -> { + this.system = new SystemConfig(); + return this.system; + }); + } + + + public NodeConfig getNode() { + return Optional.ofNullable(this.node).orElseGet(() -> { + this.node = new NodeConfig(); + return this.node; + }); + } + + + public UserConfig getUser() { + return Optional.ofNullable(this.user).orElseGet(() -> { + this.user = new UserConfig(); + return this.user; + }); + } + + + public WebConfig getWeb() { + return Optional.ofNullable(this.web).orElseGet(() -> { + this.web = new WebConfig(); + return this.web; + }); + } + + /** + * 获取当前登录用户的临时文件存储路径,如果没有登录则抛出异常 + * + * @return file + */ + public File getUserTempPath() { + UserModel userModel = BaseServerController.getUserModel(); + if (userModel == null) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.not_logged_in.6605")); + } + return getUserTempPath(userModel.getId()); + } + + /** + * 获取指定用户操作的临时目录 + * + * @return file + */ + public File getUserTempPath(String userId) { + File file = configBean.getTempPath(); + file = FileUtil.file(file, userId); + FileUtil.mkdir(file); + return file; + } + + + /** + * 获取保存 agent jar 包目录文件夹 + * + * @return 数据目录下的 agent 目录 + */ + public File getAgentPath() { + File file = new File(configBean.getDataPath()); + file = new File(file.getPath() + "/agent/"); + FileUtil.mkdir(file); + return file; + } + + /** + * 获取保存 agent zip 包目录和文件名 + * + * @return 数据目录下的 agent 目录 + */ + public File getAgentZipPath() { + File file = FileUtil.file(getAgentPath(), AgentFileModel.ZIP_NAME); + FileUtil.mkParentDirs(file); + return file; + } + + /** + * 获取当前集群 Id + * + * @return 集群Id + */ + public ClusterConfig getCluster() { + return Optional.ofNullable(this.cluster).orElseGet(() -> { + this.cluster = new ClusterConfig(); + return this.cluster; + }); + } + + @Override + public void afterPropertiesSet() throws Exception { + ClusterConfig clusterConfig = this.getCluster(); + String clusterId1 = clusterConfig.getId(); + if (!Validator.isGeneral(clusterId1, 1, 20)) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.configure_correct_cluster_id.5a78")); + } + + int initReadLine = ObjectUtil.defaultIfNull(this.initReadLine, 10); + BaseFileTailWatcher.setInitReadLine(initReadLine); + ExtConfigBean.setPath(path); + // + NodeConfig nodeConfig = getNode(); + DataSize messageSizeLimit = nodeConfig.getWebSocketMessageSizeLimit(); + messageSizeLimit = ObjectUtil.defaultIfNull(messageSizeLimit, DataSize.ofMegabytes(5)); + SystemUtil.set("JPOM_NODE_WEB_SOCKET_MESSAGE_SIZE_LIMIT", messageSizeLimit.toBytes() + ""); + } + + + /** + * 获取文件中心存储目录 + * + * @return path + */ + public File fileStorageSavePath() { + String savePah = fileStorage.getSavePah(); + if (StrUtil.isEmpty(savePah)) { + String dataPath = configBean.getDataPath(); + fileStorage.setSavePah(FileUtil.getAbsolutePath(FileUtil.file(dataPath, "file-storage"))); + } + return FileUtil.file(fileStorage.getSavePah()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/system/ServerLogbackConfig.java b/modules/server/src/main/java/org/dromara/jpom/system/ServerLogbackConfig.java new file mode 100644 index 0000000000..b9be535727 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/ServerLogbackConfig.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system; + +/** + * @author bwcx_jzy + * @since 2022/12/17 + */ +public class ServerLogbackConfig extends LogbackConfig { +} diff --git a/modules/server/src/main/java/org/dromara/jpom/system/db/DataInitEvent.java b/modules/server/src/main/java/org/dromara/jpom/system/db/DataInitEvent.java new file mode 100644 index 0000000000..02fd47b8ba --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/db/DataInitEvent.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system.db; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.spring.SpringUtil; +import cn.keepbx.jpom.event.ICacheTask; +import cn.keepbx.jpom.model.BaseIdModel; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseWorkspaceModel; +import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.service.IStatusRecover; +import org.dromara.jpom.service.h2db.BaseDbService; +import org.dromara.jpom.service.h2db.BaseNodeService; +import org.dromara.jpom.service.system.WorkspaceService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 数据库初始化完成后 + * + * @author bwcx_jzy + * @since 2023/2/18 + */ +@Configuration +@Slf4j +public class DataInitEvent implements ILoadEvent, ICacheTask { + + private final WorkspaceService workspaceService; + private final Map> errorWorkspaceTable = new HashMap<>(); + + public DataInitEvent(WorkspaceService workspaceService) { + this.workspaceService = workspaceService; + } + + public Map> getErrorWorkspaceTable() { + return errorWorkspaceTable; + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + // 分组 + Map groupServiceMap = SpringUtil.getApplicationContext().getBeansOfType(BaseDbService.class); + for (BaseDbService value : groupServiceMap.values()) { + if (value.isCanGroup()) { + value.repairGroupFiled(); + } + } + // 状态恢复的数据 + Map statusRecoverMap = SpringUtil.getApplicationContext().getBeansOfType(IStatusRecover.class); + statusRecoverMap.forEach((name, iCron) -> { + int count = iCron.statusRecover(); + if (count > 0) { + log.info(I18nMessageUtil.get("i18n.recover_abnormal_data.9adf"), name, count); + } + }); + // 同步项目 + Map beansOfType = SpringUtil.getApplicationContext().getBeansOfType(BaseNodeService.class); + for (BaseNodeService value : beansOfType.values()) { + value.syncAllNode(); + } + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE + 2; + } + + + private void checkErrorWorkspace() { + errorWorkspaceTable.clear(); + // 判断是否存在关联数据 + Set workspaceIds = this.allowWorkspaceIds(); + Set> classes = BaseWorkspaceModel.allTableClass(); + for (Class aClass : classes) { + TableName tableName = aClass.getAnnotation(TableName.class); + int workspaceBind = tableName.workspaceBind(); + if (workspaceBind == 3) { + // 父级不存在自动删除 + Class parents = tableName.parents(); + Assert.state(parents != Void.class, I18nMessageUtil.get("i18n.table_info_configuration_error_message.6452") + aClass); + // + TableName tableName1 = parents.getAnnotation(TableName.class); + Assert.notNull(tableName1, I18nMessageUtil.get("i18n.parent_table_info_config_error.2f52") + aClass); + } + String sql = "select workspaceId,count(1) as allCount from " + tableName.value() + " group by workspaceId"; + List query = workspaceService.query(sql); + for (Entity entity : query) { + String workspaceId = (String) entity.get("workspaceId"); + long allCount = (long) entity.get("allCount"); + if (workspaceIds.contains(workspaceId)) { + continue; + } + String format = StrUtil.format(I18nMessageUtil.get("i18n.table_error_workspace_data.9021"), I18nMessageUtil.get(tableName.nameKey()), tableName.value(), allCount, workspaceId); + log.error(format); + List stringList = errorWorkspaceTable.computeIfAbsent(tableName.value(), s -> new ArrayList<>()); + stringList.add(format); + } + } + } + + public Set allowWorkspaceIds() { + // 判断是否存在关联数据 + List list = workspaceService.list(); + Set workspaceIds = Optional.ofNullable(list) + .map(workspaceModels -> workspaceModels.stream() + .map(BaseIdModel::getId) + .collect(Collectors.toSet())) + .orElse(new HashSet<>()); + // 添加默认的全局工作空间 id + workspaceIds.add(ServerConst.WORKSPACE_GLOBAL); + return workspaceIds; + } + + public void clearErrorWorkspace(String tableName) { + Assert.state(errorWorkspaceTable.containsKey(tableName), I18nMessageUtil.get("i18n.no_error_data_in_table.3092")); + Set workspaceIds = this.allowWorkspaceIds(); + String sql = "select workspaceId,count(1) as allCount from " + tableName + " group by workspaceId"; + List query = workspaceService.query(sql); + for (Entity entity : query) { + String workspaceId = (String) entity.get("workspaceId"); + if (workspaceIds.contains(workspaceId)) { + continue; + } + String deleteSql = "delete from " + tableName + " where workspaceId=?"; + int execute = workspaceService.execute(deleteSql, workspaceId); + log.info(I18nMessageUtil.get("i18n.delete_table_data.c813"), tableName, execute, workspaceId); + } + this.checkErrorWorkspace(); + } + + @Override + public void refreshCache() { + try { + checkErrorWorkspace(); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.query_workspace_error.6a0d"), e); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java b/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java new file mode 100644 index 0000000000..a33f6e1c0f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system.db; + +import cn.hutool.cache.GlobalPruneTimer; +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.CheckedUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.db.Db; +import cn.hutool.db.ds.DSFactory; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.JpomApplicationEvent; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.*; +import org.dromara.jpom.dialect.DialectUtil; +import org.dromara.jpom.model.data.BackupInfoModel; +import org.dromara.jpom.service.dblog.BackupInfoService; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import javax.sql.DataSource; +import java.io.File; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * 初始化数据库 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@Configuration +@Slf4j +public class InitDb implements DisposableBean, ILoadEvent { + + private final List BEFORE_CALLBACK = new LinkedList<>(); + private final Map> AFTER_CALLBACK = new LinkedHashMap<>(); + private final DbExtConfig dbExtConfig; + private final BackupInfoService backupInfoService; + /** + * 恢复 sql 文件 + */ + private File recoverSqlFile; + + public InitDb(DbExtConfig dbExtConfig, + BackupInfoService backupInfoService) { + this.dbExtConfig = dbExtConfig; + this.backupInfoService = backupInfoService; + } + + public void addBeforeCallback(Runnable consumer) { + BEFORE_CALLBACK.add(consumer); + } + + /** + * 添加监听回调 + * + * @param name 事件名称 + * @param supplier 回调,返回 true 需要重新初始化数据库 + */ + public void addCallback(String name, Supplier supplier) { + AFTER_CALLBACK.put(name, supplier); + } + + public void afterPropertiesSet(ApplicationContext applicationContext) { + this.prepareCallback(applicationContext.getEnvironment()); + // + log.debug(I18nMessageUtil.get("i18n.need_execute_pre_events.b848"), BEFORE_CALLBACK.size()); + BEFORE_CALLBACK.forEach(Runnable::run); + IStorageService storageService = StorageServiceFactory.get(); + log.info("start load {} db", dbExtConfig.getMode()); + DSFactory dsFactory = storageService.init(dbExtConfig); + final String[] sqlFileNow = {StrUtil.EMPTY}; + try { + // 先执行恢复数据 + storageService.executeRecoverDbSql(dsFactory, this.recoverSqlFile); + // + PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); + Resource[] csvResources = pathMatchingResourcePatternResolver.getResources("classpath*:/sql-view/*.csv"); + Predicate filter = resource -> { + String filename = resource.getFilename(); + List list = StrUtil.splitTrim(filename, StrUtil.DOT); + String modeType = CollUtil.get(list, 1); + return StrUtil.equalsAnyIgnoreCase(modeType, "all", StorageServiceFactory.getMode().name()); + }; + List resourceList = Arrays.stream(csvResources).filter(filter).collect(Collectors.toList()); + // + Map> listMap = CollStreamUtil.groupByKey(resourceList, resource -> { + String filename = resource.getFilename(); + List list = StrUtil.splitTrim(filename, StrUtil.DOT); + return CollUtil.getFirst(list); + }); + // + { + Resource[] sqlResources = pathMatchingResourcePatternResolver.getResources("classpath*:/sql-view/execute.*.sql"); + List sqlResourceList = Arrays.stream(sqlResources).filter(filter).collect(Collectors.toList()); + listMap.put("execute", sqlResourceList); + } + { + Resource[] sqlResources = pathMatchingResourcePatternResolver.getResources("classpath*:/sql-view/init.*.sql"); + List sqlResourceList = Arrays.stream(sqlResources).filter(filter).collect(Collectors.toList()); + listMap.put("init", sqlResourceList); + } + // + for (Map.Entry> entry : listMap.entrySet()) { + List value = entry.getValue(); + // 排序,先后顺序执行 + value.sort((o1, o2) -> StrUtil.compare(o1.getFilename(), o2.getFilename(), true)); + entry.setValue(value); + } + // 遍历 + DataSource dataSource = dsFactory.getDataSource(); + // 第一次初始化数据库 + // 加载 sql 变更记录,避免重复执行 + Set executeSqlLog = StorageServiceFactory.loadExecuteSqlLog(); + tryInitSql(dbExtConfig.getMode(), listMap, executeSqlLog, dataSource, s -> sqlFileNow[0] = s); + // + StorageServiceFactory.saveExecuteSqlLog(executeSqlLog); + // 执行回调方法 + log.debug(I18nMessageUtil.get("i18n.need_execute_callbacks.b708"), AFTER_CALLBACK.size()); + long count = AFTER_CALLBACK.entrySet() + .stream() + .mapToInt(value -> { + log.info(I18nMessageUtil.get("i18n.start_executing_database_event.fc57"), value.getKey()); + Supplier supplier = value.getValue(); + boolean arg2 = supplier.get(); + int code = arg2 ? 1 : 0; + log.info(I18nMessageUtil.get("i18n.database_event_execution_ended.690b"), value.getKey(), code); + return code; + }).sum(); + if (count > 0) { + // 因为导入数据后数据结构可能发生变动 + // 第二次初始化数据库 + tryInitSql(dbExtConfig.getMode(), listMap, CollUtil.newHashSet(), dataSource, s -> sqlFileNow[0] = s); + } + // + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.initialize_database_failure.2ef9"), sqlFileNow[0], e); + JpomApplicationEvent.asyncExit(2); + throw Lombok.sneakyThrow(e); + } + log.info("{} db Successfully loaded, url is 【{}】", storageService.mode(), storageService.dbUrl()); + } + + + private void tryInitSql(DbExtConfig.Mode mode, Map> listMap, Set executeSqlLog, DataSource dataSource, Consumer eachSql) { + Consumer> consumer = resources -> { + for (Resource resource : resources) { + try (InputStream inputStream = resource.getInputStream()) { + String sql = IoUtil.readUtf8(inputStream); + this.executeSql(sql, resource.getFilename(), mode, executeSqlLog, dataSource, eachSql); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + }; + // 初始化sql + Optional.ofNullable(listMap.get("init")).ifPresent(consumer); + // + Optional.ofNullable(listMap.get("table")).ifPresent(resources -> { + for (Resource resource : resources) { + String sql = StorageTableFactory.initTable(resource); + this.executeSql(sql, resource.getFilename(), mode, executeSqlLog, dataSource, eachSql); + } + }); + // + Optional.ofNullable(listMap.get("execute")).ifPresent(consumer); + // + Optional.ofNullable(listMap.get("alter")).ifPresent(resources -> { + for (Resource resource : resources) { + String sql = StorageTableFactory.initAlter(resource); + this.executeSql(sql, resource.getFilename(), mode, executeSqlLog, dataSource, eachSql); + } + }); + Optional.ofNullable(listMap.get("index")).ifPresent(resources -> { + for (Resource resource : resources) { + String sql = StorageTableFactory.initIndex(resource); + this.executeSql(sql, resource.getFilename(), mode, executeSqlLog, dataSource, eachSql); + } + }); + } + + private void executeSql(String sql, String name, DbExtConfig.Mode mode, Set executeSqlLog, DataSource dataSource, Consumer eachSql) { + String sha1 = SecureUtil.sha1(sql + mode); + if (executeSqlLog.contains(sha1)) { + // 已经执行过啦,不再执行 + return; + } + eachSql.accept(name); + try { + IStorageSqlBuilderService sqlBuilderService = StorageTableFactory.get(); + Db.use(dataSource, DialectUtil.getDialectByMode(mode)).tx((CheckedUtil.VoidFunc1Rt) parameter -> { + // 分隔后执行,mysql 不能执行多条 sql 语句 + List list = StrUtil.isEmpty(sqlBuilderService.delimiter()) ? + CollUtil.newArrayList(sql) : StrUtil.splitTrim(sql, sqlBuilderService.delimiter()); + int rows = list.stream() + .mapToInt(value -> { + try { + return parameter.execute(value); + } catch (SQLException e) { + log.warn(I18nMessageUtil.get("i18n.error_sql.15ff"), value); + throw Lombok.sneakyThrow(e); + } + }) + .sum(); + log.info("exec init SQL file: {} complete, and affected rows is: {}", name, rows); + }); + } catch (SQLException e) { + throw Lombok.sneakyThrow(e); + } + executeSqlLog.add(sha1); + } + + @Override + public void destroy() throws Exception { + // 需要优先关闭线程池,避免异常更新数据的逻辑没有释放 + JpomApplication.shutdownGlobalThreadPool(); + // + GlobalPruneTimer.INSTANCE.shutdownNow(); + // 关闭数据库 + IoUtil.close(StorageServiceFactory.get()); + } + + private void prepareCallback(Environment environment) { + Opt.ofNullable(environment.getProperty("rest:load_init_db")).ifPresent(s -> { + // 重新执行数据库初始化操作,一般用于手动修改数据库字段错误后,恢复默认的字段 + StorageServiceFactory.clearExecuteSqlLog(); + }); + Opt.ofNullable(environment.getProperty("recover:h2db")).ifPresent(s -> { + // 恢复数据库,一般用于非正常关闭程序导致数据库奔溃,执行恢复数据逻辑 + try { + this.recoverSqlFile = StorageServiceFactory.get().recoverDb(); + } catch (Exception e) { + throw new JpomRuntimeException("Failed to restore database", e); + } + }); + Opt.ofNullable(environment.getProperty("backup-h2")).ifPresent(s -> { + // 备份数据库 + this.addCallback(I18nMessageUtil.get("i18n.backup_database.9524"), () -> { + log.info("Start backing up the database"); + Future backupInfoModelFuture = backupInfoService.autoBackup(); + try { + BackupInfoModel backupInfoModel = backupInfoModelFuture.get(); + log.info("Complete the backup database, save the path as {}", backupInfoModel.getFilePath()); + } catch (Exception e) { + throw new JpomRuntimeException(StrUtil.format("Backup database failed:{}", e.getMessage()), e); + } + return false; + }); + }); + // 导入数据 + Opt.ofBlankAble(environment.getProperty("import-h2-sql")).ifPresent(s -> importH2Sql(environment, s)); + Opt.ofBlankAble(environment.getProperty("replace-import-h2-sql")).ifPresent(s -> { + // 删除掉旧数据 + this.addBeforeCallback(() -> { + try { + String dbFiles = StorageServiceFactory.get().deleteDbFiles(); + if (dbFiles != null) { + log.info("Automatically backup data files to {} path", dbFiles); + } + } catch (Exception e) { + log.error("Failed to import according to sql,{}", s, e); + } + }); + // 导入数据 + importH2Sql(environment, s); + }); + + // 迁移数据 + Consumer migrateOpr = targetMode -> { + DbExtConfig.Mode mode = dbExtConfig.getMode(); + if (mode != targetMode) { + throw new JpomRuntimeException(StrUtil.format(I18nMessageUtil.get("i18n.incorrect_mode_for_migration.caef"), targetMode)); + } + // 都提前清理 + StorageServiceFactory.clearExecuteSqlLog(); + // + String user = environment.getProperty("h2-user"); + String url = environment.getProperty("h2-url"); + String pass = environment.getProperty("h2-pass"); + this.addCallback(I18nMessageUtil.get("i18n.migrate_data.f556"), () -> { + // + StorageServiceFactory.migrateH2ToNow(dbExtConfig, url, user, pass, targetMode); + return false; + }); + log.info(I18nMessageUtil.get("i18n.start_waiting_for_data_migration.e76f")); + }; + Opt.ofNullable(environment.getProperty("h2-migrate-mysql")).ifPresent(s -> { + migrateOpr.accept(DbExtConfig.Mode.MYSQL); + }); + Opt.ofNullable(environment.getProperty("h2-migrate-postgresql")).ifPresent(s -> { + migrateOpr.accept(DbExtConfig.Mode.POSTGRESQL); + }); + Opt.ofNullable(environment.getProperty("h2-migrate-mariadb")).ifPresent(s -> { + migrateOpr.accept(DbExtConfig.Mode.MARIADB); + }); + } + + private void importH2Sql(Environment environment, String importH2Sql) { + this.addCallback(I18nMessageUtil.get("i18n.import_data.8ef8"), () -> { + File file = FileUtil.file(importH2Sql); + String sqlPath = FileUtil.getAbsolutePath(file); + if (!FileUtil.isFile(file)) { + throw new JpomRuntimeException(StrUtil.format("sql file does not exist :{}", sqlPath)); + } + // + Opt.ofNullable(environment.getProperty("transform-sql")).ifPresent(s -> StorageServiceFactory.get().transformSql(file)); + // + log.info("Start importing data:{}", sqlPath); + boolean flag = backupInfoService.restoreWithSql(sqlPath); + if (!flag) { + throw new JpomRuntimeException(StrUtil.format("Failed to import according to sql,{}", sqlPath)); + } + log.info("Import successfully according to sql,{}", sqlPath); + return true; + }); + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } +// @Override +// public void handle(Signal signal) { +// log.warn("signal event {} {}", signal.getName(), signal.getNumber()); +// this.silenceDestroy(); +// } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/system/init/OperateLogController.java b/modules/server/src/main/java/org/dromara/jpom/system/init/OperateLogController.java new file mode 100644 index 0000000000..1d8927638d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/init/OperateLogController.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system.init; + +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.extra.servlet.ServletUtil; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONValidator; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.reflect.MethodSignature; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.model.log.UserOperateLogV1; +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.dblog.DbUserOperateLogService; +import org.dromara.jpom.service.h2db.BaseWorkspaceService; +import org.dromara.jpom.system.AopLogInterface; +import org.dromara.jpom.util.StringUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * 操作记录控制器 + * + * @author bwcx_jzy + * @since 2019/4/19 + */ +@Configuration +@Slf4j +public class OperateLogController implements AopLogInterface { + private static final ThreadLocal CACHE_INFO_THREAD_LOCAL = new ThreadLocal<>(); + + private final DbUserOperateLogService dbUserOperateLogService; + + private final String[] logFilterPar = new String[]{"pwd", "pass", "password"}; + + + public OperateLogController(DbUserOperateLogService dbUserOperateLogService) { + this.dbUserOperateLogService = dbUserOperateLogService; + } + + private ClassFeature findClassFeature(Class declaringClass, Class targetClass) { + Feature feature1 = declaringClass.getAnnotation(Feature.class); + if (feature1 == null || feature1.cls() == ClassFeature.NULL) { + feature1 = targetClass.getAnnotation(Feature.class); + } + return Optional.ofNullable(feature1).map(Feature::cls).orElse(null); + } + + private CacheInfo createCacheInfo(Class targetClass, Method method, HttpServletRequest request) { + Feature feature = method.getAnnotation(Feature.class); + if (feature == null) { + return null; + } + if (!feature.log()) { + log.debug(I18nMessageUtil.get("i18n.ignore_log_record.48f5"), request.getRequestURI()); + return null; + } + Class declaringClass = method.getDeclaringClass(); + MethodFeature methodFeature = feature.method(); + if (methodFeature == MethodFeature.NULL) { + log.error(I18nMessageUtil.get("i18n.permission_distribution_config_error.e7fb"), declaringClass, method.getName()); + return null; + } + ClassFeature classFeature = feature.cls(); + if (classFeature == ClassFeature.NULL) { + classFeature = this.findClassFeature(declaringClass, targetClass); + if (classFeature == null || classFeature == ClassFeature.NULL) { + log.error(I18nMessageUtil.get("i18n.permission_distribution_config_error_class_not_found.ca67"), declaringClass, method.getName()); + return null; + } + } + CacheInfo cacheInfo = new CacheInfo(); + cacheInfo.setClassFeature(classFeature); + cacheInfo.setMethodFeature(methodFeature); + cacheInfo.setOptTime(SystemClock.now()); + cacheInfo.setLogResponse(feature.logResponse()); + // + return cacheInfo; + } + + @Override + public void before(ProceedingJoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + if (signature instanceof MethodSignature) { + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + // + Class targetClass = joinPoint.getTarget().getClass(); + ServletRequestAttributes servletRequestAttributes = BaseServerController.getRequestAttributes(); + HttpServletRequest request = servletRequestAttributes.getRequest(); + CacheInfo cacheInfo = this.createCacheInfo(targetClass, method, request); + if (cacheInfo == null) { + return; + } + // 获取ip地址 + cacheInfo.ip = ServletUtil.getClientIP(request); + // 获取节点 + cacheInfo.nodeModel = (NodeModel) request.getAttribute("node"); + // + cacheInfo.userAgent = ServletUtil.getHeaderIgnoreCase(request, HttpHeaders.USER_AGENT); + cacheInfo.workspaceId = BaseWorkspaceService.getWorkspaceId(request); + //ServletUtil.getHeaderIgnoreCase(request, Const.WORKSPACE_ID_REQ_HEADER); + // + Map allData = this.buildRequestParam(request); + // + cacheInfo.dataId = StrUtil.toStringOrNull(allData.get("id")); + allData.put("request_url", request.getRequestURI()); + // + cacheInfo.reqData = JSONObject.toJSONString(allData); + // + if (cacheInfo.methodFeature == MethodFeature.DEL) { + // 删除数据 提前查询出操作到数据相关信息 + cacheInfo.optDataNameMap = dbUserOperateLogService.buildDataMsg(cacheInfo.classFeature, cacheInfo.dataId, cacheInfo.nodeModel == null ? null : cacheInfo.nodeModel.getId()); + } + CACHE_INFO_THREAD_LOCAL.set(cacheInfo); + } + } + + private Map buildRequestParam(HttpServletRequest request) { + Map map = ServletUtil.getParamMap(request); + // 过滤密码字段 + Set> entries = map.entrySet(); + for (Map.Entry entry : entries) { + String key = entry.getKey(); + if (StrUtil.containsAnyIgnoreCase(key, logFilterPar)) { + entry.setValue("***"); + } + } + // + Map allData = new HashMap<>(30); + String body = ServletFileUpload.isMultipartContent(request) ? null : ServletUtil.getBody(request); + if (StrUtil.isNotEmpty(body)) { + JSONValidator.Type type = StringUtil.validatorJson(body); + if (type == null || type == JSONValidator.Type.Value) { + allData.put("bodyData", body); + } else if (type == JSONValidator.Type.Object) { + JSONObject jsonObject = JSONObject.parseObject(body); + allData.putAll(jsonObject); + // + } else if (type == JSONValidator.Type.Array) { + allData.put("bodyData", JSON.toJSON(body)); + } + } + allData.putAll(map); + return allData; + } + + @Override + public void afterReturning(Object value) { + try { + CacheInfo cacheInfo = CACHE_INFO_THREAD_LOCAL.get(); + if (cacheInfo == null || cacheInfo.methodFeature == MethodFeature.LIST) { + return; + } + if (cacheInfo.classFeature == null || cacheInfo.methodFeature == null) { + log.warn(I18nMessageUtil.get("i18n.permission_function_not_configured_correctly.84dd"), cacheInfo); + return; + } + UserModel userModel = BaseServerController.getUserByThreadLocal(); + // 没有对应的用户 + if (userModel == null) { + return; + } + this.log(userModel, value, cacheInfo); + } finally { + CACHE_INFO_THREAD_LOCAL.remove(); + } + } + + /** + * 记录操作日志 + * + * @param userModel 用户 + * @param value 返回执行 + * @param cacheInfo 请求信息 + */ + public void log(UserModel userModel, Object value, CacheInfo cacheInfo) { + UserOperateLogV1 userOperateLogV1 = new UserOperateLogV1(); + userOperateLogV1.setWorkspaceId(cacheInfo.workspaceId); + userOperateLogV1.setClassFeature(cacheInfo.classFeature.name()); + userOperateLogV1.setMethodFeature(cacheInfo.methodFeature.name()); + userOperateLogV1.setDataId(cacheInfo.dataId); + userOperateLogV1.setUserId(userModel.getId()); + userOperateLogV1.setIp(cacheInfo.ip); + userOperateLogV1.setUserAgent(cacheInfo.userAgent); + userOperateLogV1.setReqData(cacheInfo.reqData); + userOperateLogV1.setOptTime(ObjectUtil.defaultIfNull(cacheInfo.optTime, SystemClock.now())); + if (value != null) { + // 解析结果 + if (value instanceof Throwable) { + // 发生异常 + Throwable throwable = (Throwable) value; + userOperateLogV1.setResultMsg(ExceptionUtil.stacktraceToString(throwable)); + userOperateLogV1.setOptStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } else { + String json = JSONObject.toJSONString(value); + userOperateLogV1.setResultMsg(json); + try { + JsonMessage jsonMessage = JSONObject.parseObject(json, JsonMessage.class); + int code = jsonMessage.getCode(); + userOperateLogV1.setOptStatus(code); + } catch (Exception ignored) { + } + } + // 判断是否记录响应日志 + Boolean logResponse = cacheInfo.getLogResponse(); + if (logResponse != null && !logResponse) { + userOperateLogV1.setResultMsg(new cn.hutool.json.JSONObject().putOpt("hide", "*****").toString()); + } + } + // + if (cacheInfo.nodeModel != null) { + userOperateLogV1.setNodeId(cacheInfo.nodeModel.getId()); + if (StrUtil.isEmpty(cacheInfo.workspaceId)) { + userOperateLogV1.setWorkspaceId(cacheInfo.nodeModel.getWorkspaceId()); + } + } + // + try { + BaseServerController.resetInfo(UserModel.EMPTY); + dbUserOperateLogService.insert(userOperateLogV1, cacheInfo); + } finally { + BaseServerController.removeEmpty(); + } + } + + + /** + * 修改执行结果 + * + * @param reqId 请求id + * @param val 结果 + */ + public void updateLog(String reqId, String val) { + Entity entity = new Entity(); + entity.set("resultMsg", val); + try { + JsonMessage jsonMessage = JSONObject.parseObject(val, JsonMessage.class); + entity.set("optStatus", jsonMessage.getCode()); + } catch (Exception ignored) { + } + // + Entity where = new Entity(); + where.set("reqId", reqId); + dbUserOperateLogService.update(entity, where); + } + + /** + * 临时缓存 + */ + @Data + public static class CacheInfo { + private Long optTime; + private String workspaceId; + private ClassFeature classFeature; + private MethodFeature methodFeature; + private String ip; + private NodeModel nodeModel; + private String dataId; + private String userAgent; + private String reqData; + private Boolean logResponse; + /** + * 操作到数据到名称相关 map + */ + private Map optDataNameMap; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/system/init/ProxySelectorConfig.java b/modules/server/src/main/java/org/dromara/jpom/system/init/ProxySelectorConfig.java new file mode 100644 index 0000000000..6ade286cfc --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/init/ProxySelectorConfig.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system.init; + +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.event.ICacheTask; +import com.alibaba.fastjson2.JSONArray; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.service.system.SystemParametersServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 全局代理配置 + * + * @author bwcx_jzy + * @since 2022/7/4 + */ +@Slf4j +@Configuration +public class ProxySelectorConfig extends ProxySelector implements ILoadEvent, ICacheTask { + + public static final String KEY = "global_proxy"; + + private final SystemParametersServer systemParametersServer; + private volatile List proxyConfigItems; + private ProxySelector defaultProxySelector; + + public ProxySelectorConfig(SystemParametersServer systemParametersServer) { + this.systemParametersServer = systemParametersServer; + } + + @Override + public List select(URI uri) { + String url = uri.toString(); + return Optional.ofNullable(proxyConfigItems) + .flatMap(proxyConfigItems -> proxyConfigItems.stream() + .filter(proxyConfigItem -> { + if (StrUtil.equals(proxyConfigItem.getPattern(), "*")) { + return true; + } + if (ReUtil.isMatch(proxyConfigItem.getPattern(), url)) { + // 满足正则条件 + return true; + } + return StrUtil.containsIgnoreCase(url, proxyConfigItem.getPattern()); + }) + .map(proxyConfigItem -> NodeForward.crateProxy(proxyConfigItem.getProxyType(), proxyConfigItem.getProxyAddress())) + .filter(Objects::nonNull) + .findFirst() + .map(Collections::singletonList) + ).orElseGet(() -> { + // revert to the default behaviour + return defaultProxySelector == null ? Collections.singletonList(Proxy.NO_PROXY) : defaultProxySelector.select(uri); + }); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + if (uri == null || sa == null || ioe == null) { + throw new IllegalArgumentException( + "Arguments can't be null."); + } + } + + /** + * 刷新 + */ + @Override + public void refreshCache() { + JSONArray array = systemParametersServer.getConfigDefNewInstance(ProxySelectorConfig.KEY, JSONArray.class); + proxyConfigItems = array.toJavaList(ProxyConfigItem.class) + .stream() + .filter(proxyConfigItem -> StrUtil.isAllNotEmpty(proxyConfigItem.pattern, proxyConfigItem.proxyAddress, proxyConfigItem.proxyType)) + .collect(Collectors.toList()); + } + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + if (ProxySelector.getDefault() != this) { + defaultProxySelector = ProxySelector.getDefault(); + // + ProxySelector.setDefault(this); + } + // 立马配置 全局代理 + this.refreshCache(); + } + + + /** + * @author bwcx_jzy + * @since 2022/7/4 + */ + @Data + public static class ProxyConfigItem { + + private String pattern; + + /** + * @see Proxy.Type + */ + private String proxyType; + + /** + * 127.0.0.1:8888 + */ + private String proxyAddress; + } + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/system/init/ServerCheckMonitor.java b/modules/server/src/main/java/org/dromara/jpom/system/init/ServerCheckMonitor.java new file mode 100644 index 0000000000..0a49f09d58 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/system/init/ServerCheckMonitor.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.system.init; + +import cn.keepbx.jpom.event.ISystemTask; +import cn.keepbx.jpom.model.JsonMessage; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ILoadEvent; +import org.dromara.jpom.common.RemoteVersion; +import org.dromara.jpom.common.forward.NodeForward; +import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.cron.CronUtils; +import org.dromara.jpom.func.assets.AssetsExecutorPoolService; +import org.dromara.jpom.model.data.NodeModel; +import org.dromara.jpom.script.BaseRunScript; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.node.script.NodeScriptExecuteLogServer; +import org.dromara.jpom.service.node.script.NodeScriptServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; + +import java.util.Collection; +import java.util.List; + +/** + * 检查监控数据状态 + * + * @author bwcx_jzy + * @since 2019/7/14 + */ +@Configuration +@Slf4j +public class ServerCheckMonitor implements ILoadEvent, ISystemTask { + + private final NodeService nodeService; + private final NodeScriptServer nodeScriptServer; + private final NodeScriptExecuteLogServer nodeScriptExecuteLogServer; + private final AssetsExecutorPoolService assetsExecutorPoolService; + + public ServerCheckMonitor(NodeService nodeService, + NodeScriptServer nodeScriptServer, + NodeScriptExecuteLogServer nodeScriptExecuteLogServer, + AssetsExecutorPoolService assetsExecutorPoolService) { + this.nodeService = nodeService; + this.nodeScriptServer = nodeScriptServer; + this.nodeScriptExecuteLogServer = nodeScriptExecuteLogServer; + this.assetsExecutorPoolService = assetsExecutorPoolService; + } + + /** + * 同步 节点的脚本模版日志 + * + * @param nodeModel 节点 + */ + private void pullScriptLogItem(NodeModel nodeModel) { + try { + //NodeScriptExecuteLogServer nodeScriptExecuteLogServer = SpringUtil.getBean(NodeScriptExecuteLogServer.class); + Collection strings = nodeScriptExecuteLogServer.syncExecuteNodeInc(nodeModel); + if (strings == null) { + return; + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("ids", strings); + JsonMessage jsonMessage = NodeForward.requestBody(nodeModel, NodeUrl.SCRIPT_DEL_EXEC_LOG, jsonObject); + if (!jsonMessage.success()) { + log.error(I18nMessageUtil.get("i18n.delete_script_template_execution_error.8bc5"), jsonMessage); + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.synchronization_script_exception.9c70"), e); + } + } + + + @Override + public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception { + // 拉取 脚本模版日志 + CronUtils.upsert("pull_script_log", "0 0/1 * * * ?", () -> { + //NodeService nodeService = SpringUtil.getBean(NodeService.class); + //NodeScriptServer nodeScriptServer = SpringUtil.getBean(NodeScriptServer.class); + List nodeIds = nodeScriptServer.hasScriptNode(); + if (nodeIds == null) { + return; + } + for (String nodeId : nodeIds) { + NodeModel nodeModel = nodeService.getByKey(nodeId); + if (nodeModel == null) { + continue; + } + assetsExecutorPoolService.execute(() -> this.pullScriptLogItem(nodeModel)); + } + }); + } + + @Override + public void executeTask() { + // 尝试获取版本更新信息 + RemoteVersion.loadRemoteInfo(); + // 清空脚本缓存 + BaseRunScript.clearRunScript(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java b/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java new file mode 100644 index 0000000000..886defc4a1 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/AntPathUtil.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.util.AntPathMatcher; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/2/9 + */ +public class AntPathUtil { + + + public static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher(); + + public static List antPathMatcher(File rootFile, String match) { + // 格式化,并处理 Slip 问题 + String matchStr = FileUtil.normalize(StrUtil.SLASH + match); + List paths = new ArrayList<>(); + // + FileUtil.walkFiles(rootFile.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + return this.test(file); + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes exc) throws IOException { + return this.test(dir); + } + + private FileVisitResult test(Path path) { + String subPath = FileUtil.subPath(FileUtil.getAbsolutePath(rootFile), path.toFile()); + subPath = FileUtil.normalize(StrUtil.SLASH + subPath); + if (ANT_PATH_MATCHER.match(matchStr, subPath)) { + paths.add(subPath); + //return FileVisitResult.TERMINATE; + } + return FileVisitResult.CONTINUE; + } + }); + return paths; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/JwtUtil.java b/modules/server/src/main/java/org/dromara/jpom/util/JwtUtil.java new file mode 100644 index 0000000000..dfbf24ed54 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/JwtUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.JWTHeader; +import cn.hutool.jwt.JWTValidator; +import cn.hutool.jwt.signers.JWTSignerUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.configuration.UserConfig; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.system.ServerConfig; + +/** + * jwt 工具类 + * + * @author bwcx_jzy + * @since 2020/7/25 + */ +@Slf4j +public class JwtUtil { + + /** + * 加密算法 + */ + private static final String ALGORITHM = "HS256"; + /** + * token的的加密key + */ + private static byte[] KEY; + public static final String KEY_USER_ID = "userId"; + + public static JWT parseBody(String token) { + if (StrUtil.isEmpty(token)) { + return null; + } + ServerConfig serverConfig = SpringUtil.getBean(ServerConfig.class); + UserConfig user = serverConfig.getUser(); + JWT jwt = JWT.of(token); + if (jwt.verify(JWTSignerUtil.hs256(user.getTokenJwtKeyByte()))) { + return jwt; + } + return null; + } + + + /** + * 读取token 信息 过期也能读取 + * + * @param token token + * @return claims + */ + public static JWT readBody(String token) { + try { + return parseBody(token); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.token_parse_failed.cadf") + token, e); + return null; + } + } + + /** + * 读取用户id + * + * @param jwt jwt + * @return 用户id + */ + public static String readUserId(JWT jwt) { + return Convert.toStr(jwt.getPayload(KEY_USER_ID)); + } + + /** + * 获取jwt的唯一身份标识 + * + * @param jwt jwt + * @return id + */ + public static String getId(JWT jwt) { + if (null == jwt) { + return null; + } + return Convert.toStr(jwt.getPayload(JWT.JWT_ID)); + } + + /** + * 判断是否过期 + * + * @param jwt claims + * @param leeway 容忍空间,单位:秒。当不能晚于当前时间时,向后容忍;不能早于向前容忍。 + * @return 是否过期 + */ + public static boolean expired(JWT jwt, long leeway) { + if (jwt == null) { + return true; + } + try { + JWTValidator of = JWTValidator.of(jwt); + of.validateDate(DateUtil.date(), leeway); + } catch (Exception e) { + return true; + } + return false; + } + + /** + * 生成token + * + * @param userModel 用户 + * @return token + */ + public static String builder(UserModel userModel, String jwtId) { + ServerConfig serverConfig = SpringUtil.getBean(ServerConfig.class); + UserConfig user = serverConfig.getUser(); + // + DateTime now = DateTime.now(); + JWT jwt = JWT.create(); + jwt.setHeader(JWTHeader.ALGORITHM, ALGORITHM); + jwt.setPayload(KEY_USER_ID, userModel.getId()) + .setJWTId(jwtId) + .setIssuer("Jpom") + .setIssuedAt(now) + .setExpiresAt(now.offsetNew(DateField.HOUR, user.getTokenExpired())); + return jwt.sign(JWTSignerUtil.hs256(user.getTokenJwtKeyByte())); + } + + +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/MySftp.java b/modules/server/src/main/java/org/dromara/jpom/util/MySftp.java new file mode 100644 index 0000000000..2a1a902535 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/MySftp.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.ssh.Sftp; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpProgressMonitor; + +import java.io.File; +import java.nio.charset.Charset; + +/** + * @author bwcx_jzy + * @since 2023/3/16 + */ +public class MySftp extends Sftp { + + private final ProgressMonitor progressMonitor; + + public interface ProgressMonitor { + + /** + * 进度回调 + * + * @param desc 远程目录 + * @param max 文件总大小 + * @param now 当前进程 + */ + void progress(String desc, long max, long now); + + /** + * 重置 + */ + void rest(); + } + + public MySftp(Session session, Charset charset, long timeOut, ProgressMonitor monitor) { + super(session, charset, timeOut); + this.progressMonitor = monitor; + } + + @Override + public boolean upload(String destPath, File file) { + final String[] desc = new String[1]; + final long[] maxLen = new long[1]; + progressMonitor.rest(); + SftpProgressMonitor sftpProgressMonitor = new SftpProgressMonitor() { + private long totalCount = 0; + + @Override + public void init(int op, String src, String dest, long max) { + desc[0] = dest; + maxLen[0] = max; + } + + @Override + public boolean count(long count) { + totalCount += count; + progressMonitor.progress(desc[0], maxLen[0], totalCount); + return true; + } + + @Override + public void end() { + + } + }; + super.put(FileUtil.getAbsolutePath(file), destPath, sftpProgressMonitor, Mode.OVERWRITE); + return true; + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/SocketSessionUtil.java b/modules/server/src/main/java/org/dromara/jpom/util/SocketSessionUtil.java new file mode 100644 index 0000000000..b593a0265f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/SocketSessionUtil.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.map.SafeConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.unit.DataSize; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; + +import java.io.IOException; +import java.util.Map; + +/** + * socket 会话对象 + * + * @author bwcx_jzy + * @since 2018/9/29 + */ +@Slf4j +public class SocketSessionUtil { + + private static final Map SOCKET_MAP = new SafeConcurrentHashMap<>(); + + /** + * 发送文本消息 + * + * @param session 会话 + * @param msg 消息 + * @return 是否发送成功 + * @throws IOException io + */ + public static boolean send(WebSocketSession session, String msg) throws IOException { + return send(session, new TextMessage(msg)); + } + + /** + * 发送消息 + * + * @param session 会话 + * @param message 消息 + * @return 是否发送成功 + * @throws IOException io + */ + public static boolean send(WebSocketSession session, WebSocketMessage message) throws IOException { + if (!session.isOpen()) { + // 会话关闭不能发送消息 @author jzy 21-08-04 + log.warn(I18nMessageUtil.get("i18n.session_already_closed.8dcc"), message.getPayload()); + return false; + } + WebSocketSession webSocketSession = SOCKET_MAP.computeIfAbsent(session.getId(), s -> new ConcurrentWebSocketSessionDecorator(session, 60 * 1000, (int) DataSize.ofMegabytes(5).toBytes())); + webSocketSession.sendMessage(message); + return true; + } + + public static void close(WebSocketSession session) { + SOCKET_MAP.remove(session.getId()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/StrictSyncFinisher.java b/modules/server/src/main/java/org/dromara/jpom/util/StrictSyncFinisher.java new file mode 100644 index 0000000000..0eb7d8f40f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/StrictSyncFinisher.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.thread.ExecutorBuilder; +import cn.hutool.core.thread.SyncFinisher; + +import java.io.Closeable; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * 线程同步结束器
+ * 在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 + * + * @author bwcx_jzy + * @see SyncFinisher + * @since 2023/1/10 + */ +public class StrictSyncFinisher implements Closeable { + + private final Set workers; + private final int threadSize; + private final int queueCapacity; + private ExecutorService executorService; + + private boolean isBeginAtSameTime; + /** + * 启动同步器,用于保证所有worker线程同时开始 + */ + private final CountDownLatch beginLatch; + /** + * 结束同步器,用于等待所有worker线程同时结束 + */ + private CountDownLatch endLatch; + + /** + * 构造 + * + * @param threadSize 线程数 + * @param queueCapacity 队列数 + */ + public StrictSyncFinisher(int threadSize, int queueCapacity) { + this.beginLatch = new CountDownLatch(1); + this.threadSize = threadSize; + this.queueCapacity = queueCapacity; + this.workers = new LinkedHashSet<>(); + } + + /** + * 设置是否所有worker线程同时开始 + * + * @param isBeginAtSameTime 是否所有worker线程同时开始 + * @return this + */ + public StrictSyncFinisher setBeginAtSameTime(boolean isBeginAtSameTime) { + this.isBeginAtSameTime = isBeginAtSameTime; + return this; + } + + /** + * 增加定义的线程数同等数量的worker + * + * @param runnable 工作线程 + * @return this + */ + public StrictSyncFinisher addRepeatWorker(final Runnable runnable) { + for (int i = 0; i < this.threadSize; i++) { + addWorker(new StrictSyncFinisher.Worker() { + @Override + public void work() { + runnable.run(); + } + }); + } + return this; + } + + /** + * 增加工作线程 + * + * @param runnable 工作线程 + * @return this + */ + public StrictSyncFinisher addWorker(final Runnable runnable) { + return addWorker(new StrictSyncFinisher.Worker() { + @Override + public void work() { + runnable.run(); + } + }); + } + + /** + * 增加工作线程 + * + * @param worker 工作线程 + * @return this + */ + synchronized public StrictSyncFinisher addWorker(StrictSyncFinisher.Worker worker) { + workers.add(worker); + return this; + } + + /** + * 开始工作
+ * 执行此方法后如果不再重复使用此对象,需调用{@link #stop()}关闭回收资源。 + */ + public void start() { + start(true); + } + + /** + * 开始工作
+ * 执行此方法后如果不再重复使用此对象,需调用{@link #stop()}关闭回收资源。 + * + * @param sync 是否阻塞等待 + * @since 4.5.8 + */ + public void start(boolean sync) { + endLatch = new CountDownLatch(workers.size()); + + if (null == this.executorService || this.executorService.isShutdown()) { + this.executorService = ExecutorBuilder.create() + .setCorePoolSize(threadSize) + .setMaxPoolSize(threadSize) + .setWorkQueue(new LinkedBlockingQueue<>(queueCapacity)) + .build(); + } + for (StrictSyncFinisher.Worker worker : workers) { + executorService.submit(worker); + } + // 保证所有worker同时开始 + this.beginLatch.countDown(); + + if (sync) { + try { + this.endLatch.await(); + } catch (InterruptedException e) { + throw new UtilException(e); + } + } + } + + /** + * 结束线程池。此方法执行两种情况: + *
    + *
  1. 执行start(true)后,调用此方法结束线程池回收资源
  2. + *
  3. 执行start(false)后,用户自行判断结束点执行此方法
  4. + *
+ * + * @since 5.6.6 + */ + public void stop() { + if (null != this.executorService) { + this.executorService.shutdown(); + this.executorService = null; + } + + clearWorker(); + } + + /** + * 立即结束线程池所有线程。此方法执行两种情况: + *
    + *
  1. 执行start(true)后,调用此方法结束线程池回收资源
  2. + *
  3. 执行start(false)后,用户自行判断结束点执行此方法
  4. + *
+ * + * @since 5.8.11 + */ + public void stopNow() { + if (null != this.executorService) { + this.executorService.shutdownNow(); + this.executorService = null; + } + + clearWorker(); + } + + /** + * 清空工作线程对象 + */ + public void clearWorker() { + workers.clear(); + } + + /** + * 剩余任务数 + * + * @return 剩余任务数 + */ + public long count() { + return endLatch.getCount(); + } + + @Override + public void close() throws IOException { + stop(); + } + + /** + * 工作者,为一个线程 + * + * @author xiaoleilu + */ + public abstract class Worker implements Runnable { + + @Override + public void run() { + if (isBeginAtSameTime) { + try { + beginLatch.await(); + } catch (InterruptedException e) { + throw new UtilException(e); + } + } + try { + work(); + } finally { + endLatch.countDown(); + } + } + + /** + * 任务内容 + */ + public abstract void work(); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/SyncFinisherUtil.java b/modules/server/src/main/java/org/dromara/jpom/util/SyncFinisherUtil.java new file mode 100644 index 0000000000..f017dacb15 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/SyncFinisherUtil.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.RuntimeUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.util.Assert; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * 线程同步器 工具类 + * + * @author bwcx_jzy + * @since 2023/3/18 + */ +public final class SyncFinisherUtil { + + private static final Map SYNC_FINISHER_MAP = new SafeConcurrentHashMap<>(); + + /** + * 任务列表 + * + * @return 任务列表 + */ + public static Set keys() { + return SYNC_FINISHER_MAP.keySet(); + } + + /** + * 创建线程同步器 + * + * @param core 线程核心数 + * @param queueCapacity 任务队列数 + * @return 线程同步器 + */ + private static StrictSyncFinisher create(int core, int queueCapacity) { + int threadSize = Math.min(core, RuntimeUtil.getProcessorCount()); + return new StrictSyncFinisher(threadSize, queueCapacity); + } + + /** + * 创建线程同步器 + * 核心任务数 为 cpu 核心数 + * + * @param queueCapacity 任务队列数 + * @param name 任务名 + * @return 线程同步器 + */ + public static StrictSyncFinisher create(String name, int queueCapacity) { + int threadSize = Math.min(RuntimeUtil.getProcessorCount(), queueCapacity); + StrictSyncFinisher strictSyncFinisher = new StrictSyncFinisher(threadSize, queueCapacity); + put(name, strictSyncFinisher); + return strictSyncFinisher; + } + + /** + * 添加任务 + * + * @param name 任务名 + * @param syncFinisher 同步器 + */ + public static void put(String name, StrictSyncFinisher syncFinisher) { + Assert.state(!SYNC_FINISHER_MAP.containsKey(name), I18nMessageUtil.get("i18n.task_already_exists.f59a")); + SYNC_FINISHER_MAP.put(name, syncFinisher); + } + + /** + * 取消 任务 + * + * @param name 任务名 + */ + public static boolean cancel(String name) { + StrictSyncFinisher syncFinisher = SYNC_FINISHER_MAP.remove(name); + Optional.ofNullable(syncFinisher).ifPresent(StrictSyncFinisher::stopNow); + return syncFinisher != null; + } + + /** + * 关闭任务 + * + * @param name 任务名 + */ + public static void close(String name) { + IoUtil.close(SYNC_FINISHER_MAP.remove(name)); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/util/TwoFactorAuthUtils.java b/modules/server/src/main/java/org/dromara/jpom/util/TwoFactorAuthUtils.java new file mode 100644 index 0000000000..bc31aa6e50 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/util/TwoFactorAuthUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.util; + +import cn.hutool.core.codec.Base32; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.HMac; + +/** + * @author bwcx_jzy + * @since 2022/1/27 + */ +public class TwoFactorAuthUtils { + + private static final int VALID_TFA_WINDOW_MILLIS = 60_000; + + + /** + * 生成两步验证 Key + * + * @return 两步验证 Key + */ + public static String generateTFAKey() { + return TimeBasedOneTimePasswordUtil.generateBase32Secret(32); + } + + /** + * 生成两步验证码 + * + * @param tfaKey 两步验证 Key + * @return 两步验证码 + */ + public static String generateTFACode(String tfaKey) { + return TimeBasedOneTimePasswordUtil.generateCurrentNumberString(tfaKey); + } + + /** + * 验证两步验证码 + * + * @param tfaKey 两步验证 Key + * @param tfaCode 两步验证码 + */ + public static boolean validateTFACode(String tfaKey, String tfaCode) { + int validCode = Convert.toInt(tfaCode, 0); + return TimeBasedOneTimePasswordUtil.validateCurrentNumber(tfaKey, validCode, VALID_TFA_WINDOW_MILLIS); + } + + /** + * 生成 Otp Auth Url + * + * @param userName 用户名 + * @param tfaKey 两步验证 Key + * @return URL + */ + public static String generateOtpAuthUrl(String userName, final String tfaKey) { + String jpomName = "jpom-" + userName; + return TimeBasedOneTimePasswordUtil.generateOtpAuthUrl(jpomName, tfaKey); + } + + private static class TimeBasedOneTimePasswordUtil { + public static final int DEFAULT_TIME_STEP_SECONDS = 30; + private static final int NUM_DIGITS_OUTPUT = 6; + + public static String generateBase32Secret(int length) { + return RandomUtil.randomString(RandomUtil.BASE_CHAR, length).toUpperCase(); + } + + public static boolean validateCurrentNumber(String base32Secret, int authNumber, + int windowMillis) { + return validateCurrentNumber(base32Secret, authNumber, windowMillis, + System.currentTimeMillis(), + DEFAULT_TIME_STEP_SECONDS); + } + + public static boolean validateCurrentNumber(String base32Secret, int authNumber, + int windowMillis, long timeMillis, + int timeStepSeconds) { + long fromTimeMillis = timeMillis; + long toTimeMillis = timeMillis; + if (windowMillis > 0) { + fromTimeMillis -= windowMillis; + toTimeMillis += windowMillis; + } + long timeStepMillis = timeStepSeconds * 1000L; + for (long millis = fromTimeMillis; millis <= toTimeMillis; millis += timeStepMillis) { + int generatedNumber = generateNumber(base32Secret, millis, timeStepSeconds); + if (generatedNumber == authNumber) { + return true; + } + } + return false; + } + + public static String generateCurrentNumberString(String base32Secret) { + return generateNumberString(base32Secret, System.currentTimeMillis(), DEFAULT_TIME_STEP_SECONDS); + } + + public static String generateNumberString(String base32Secret, long timeMillis, int timeStepSeconds) { + int number = generateNumber(base32Secret, timeMillis, timeStepSeconds); + String numStr = Integer.toString(number); + return StrUtil.fillBefore(numStr, '0', TimeBasedOneTimePasswordUtil.NUM_DIGITS_OUTPUT); + } + + public static int generateNumber(String base32Secret, long timeMillis, int timeStepSeconds) { + + byte[] key = Base32.decode(base32Secret); + + byte[] data = new byte[8]; + long value = timeMillis / 1000 / timeStepSeconds; + for (int i = 7; value > 0; i--) { + data[i] = (byte) (value & 0xFF); + value >>= 8; + } + HMac hMac = SecureUtil.hmacSha1(key); + byte[] hash = hMac.digest(data); + + // take the 4 least significant bits from the encrypted string as an offset + int offset = hash[hash.length - 1] & 0xF; + + // We're using a long because Java hasn't got unsigned int. + long truncatedHash = 0; + for (int i = offset; i < offset + 4; ++i) { + truncatedHash <<= 8; + // get the 4 bytes at the offset + truncatedHash |= hash[i] & 0xFF; + } + // cut off the top bit + truncatedHash &= 0x7FFFFFFF; + + // the token is then the last 6 digits in the number + truncatedHash %= 1000000; + // this is only 6 digits so we can safely case it + return (int) truncatedHash; + } + + public static String generateOtpAuthUrl(String keyId, String secret) { + // addOtpAuthPart(keyId, secret, sb); + return "otpauth://totp/" + keyId + "?secret=" + secret; + } + } +} diff --git a/modules/server/src/main/resources/META-INF/services/org.dromara.jpom.transport.TransformServer b/modules/server/src/main/resources/META-INF/services/org.dromara.jpom.transport.TransformServer new file mode 100644 index 0000000000..cbc5e7ece9 --- /dev/null +++ b/modules/server/src/main/resources/META-INF/services/org.dromara.jpom.transport.TransformServer @@ -0,0 +1 @@ +org.dromara.jpom.common.forward.JsonMessageTransformServer diff --git a/modules/server/src/main/resources/META-INF/services/org.dromara.jpom.transport.i18n.II18nMessageUtil b/modules/server/src/main/resources/META-INF/services/org.dromara.jpom.transport.i18n.II18nMessageUtil new file mode 100644 index 0000000000..88dc29ba4a --- /dev/null +++ b/modules/server/src/main/resources/META-INF/services/org.dromara.jpom.transport.i18n.II18nMessageUtil @@ -0,0 +1 @@ +org.dromara.jpom.common.i18n.TransportI18nMessageImpl diff --git a/modules/server/src/main/resources/application-dev.yml b/modules/server/src/main/resources/application-dev.yml deleted file mode 100644 index aeee0f2873..0000000000 --- a/modules/server/src/main/resources/application-dev.yml +++ /dev/null @@ -1,14 +0,0 @@ - -netty: - port: 8888 -server: - servlet: - session: - persistent: true - -# spring 相关配置 -spring: - h2: - console: - # 是否开启 web 访问数据库(url: http://${ip}:${port}/h2-console) - enabled: false diff --git a/modules/server/src/main/resources/application-local.yml b/modules/server/src/main/resources/application-local.yml deleted file mode 100644 index eb57522a49..0000000000 --- a/modules/server/src/main/resources/application-local.yml +++ /dev/null @@ -1,19 +0,0 @@ - -netty: - port: 8888 -server: - servlet: - session: - persistent: true - -# spring 相关配置 -spring: - h2: - console: - # 是否开启 web 访问数据库(url: http://${ip}:${port}/h2-console) - enabled: true -# -#jpom: -# subTitle: "测试" -# name: "ccsfsdfdsf" -# loginTitle: dsfasdf diff --git a/modules/server/src/main/resources/application-mariadb.yml b/modules/server/src/main/resources/application-mariadb.yml new file mode 100644 index 0000000000..775a5f46ef --- /dev/null +++ b/modules/server/src/main/resources/application-mariadb.yml @@ -0,0 +1,21 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + db: + # 数据库默认 支持 :H2、MYSQL、MARIADB、POSTGRESQL + mode: MARIADB + url: jdbc:mariadb://127.0.0.1:3309/jpom + # 数据库账号 默认 jpom + #user-name: jpom + user-name: root + # 数据库密码 默认 jpom 如果自行配置请保证密码强度 + # user-pwd: jpom + user-pwd: jpom123456 diff --git a/modules/server/src/main/resources/application-mysql.yml b/modules/server/src/main/resources/application-mysql.yml new file mode 100644 index 0000000000..39f629f210 --- /dev/null +++ b/modules/server/src/main/resources/application-mysql.yml @@ -0,0 +1,21 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + db: + # 数据库默认 支持 :H2、MYSQL、MARIADB、POSTGRESQL + mode: MYSQL + url: jdbc:mysql://127.0.0.1:3306/jpom + # 数据库账号 默认 jpom + #user-name: jpom + user-name: root + # 数据库密码 默认 jpom 如果自行配置请保证密码强度 + # user-pwd: jpom + user-pwd: k@4Qoi6qdV#OYjQd diff --git a/modules/server/src/main/resources/application-postgresql.yml b/modules/server/src/main/resources/application-postgresql.yml new file mode 100644 index 0000000000..d9127c9a2d --- /dev/null +++ b/modules/server/src/main/resources/application-postgresql.yml @@ -0,0 +1,21 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + db: + # 数据库默认 支持 :H2、MYSQL、MARIADB、POSTGRESQL + mode: POSTGRESQL + url: jdbc:postgresql://127.0.0.1:5432/jpom + # 数据库账号 默认 jpom + #user-name: jpom + user-name: jpom + # 数据库密码 默认 jpom 如果自行配置请保证密码强度 + # user-pwd: jpom + user-pwd: jpom123456 diff --git a/modules/server/src/main/resources/application-pro.yml b/modules/server/src/main/resources/application-pro.yml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/modules/server/src/main/resources/application.yml b/modules/server/src/main/resources/application.yml index dd203ca4f5..a056751cdb 100644 --- a/modules/server/src/main/resources/application.yml +++ b/modules/server/src/main/resources/application.yml @@ -1,36 +1,182 @@ -#运行端口号 +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + # jpom 数据存储路径, 如果调试模式运行默认路径为【${user.home}/jpom/】,安装运行默认为jar包文件的父级 + path: + # 集群配置 + cluster: + # 集群Id,默认为 default 不区分大小写,只能是字母或者数字,长度小于 20 + id: default + # 心跳监控时间 (需要大于零) 单位秒 最小配置 5 秒 + heart-second: 30 + node: + # 节点心跳监控时间 (需要大于零) 单位秒 最小配置 5秒 + heart-second: 30 + # 节点统计日志保留天数,如果小于等于 0 不自动删除 + stat-log-keep-days: 3 + # 上传文件的超时时间 单位秒,最短5秒钟 + upload-file-timeout: 300 + # 节点文件分片上传大小,单位 M,建议小于 5MB(需要考虑插件端上传文件大小限制) + upload-file-slice-size: 1 + # 节点文件分片上传并发数,最小1 最大 服务端 CPU 核心数 + upload-file-concurrent: 2 + # web socket 消息最大长度 + web-socket-message-size-limit: 5MB + system: + # cron 定时器是否开启匹配秒 + timer-match-second: false + # 旧包文件保留个数 + old-jars-count: 2 + # Check the url for the new version + remote-version-url: + # 系统日志编码格式 + log-charset: UTF-8 + # 控制台编码格式 + console-charset: + # 在线升级允许降级-操作 + allowed-downgrade: false + # 执行系统主要命名是否填充 sudo(sudo xxx) 使用前提需要配置 sudo 免密 + command-use-sudo: false + # 系统语言:zh-CN、en-US + lang: zh-CN + user: + # 用户连续登录失败次数,超过此数将被限制登录 + always-login-error: 5 + # IP连续登录失败次数,超过此数将被限制登录 + always-ip-login-error: 10 + # 当ip连续登录失败,锁定对应IP时长,5h、1d + ip-error-lock-time: 5h + # 是否强制提醒用户开启 mfa + force-mfa: false + #登录token失效时间(单位:小时),默认为24 + token-expired: 24 + #登录token失效后自动续签时间(单位:分钟),默认为60,不自动续签 + token-renewal: 60 + # jwt 签名(加密)的key 长度建议控制到 16位 + token-jwt-key: + web: + # 前端接口 超时时间 单位秒(最小 5 秒) + api-timeout: 20 + # 禁用登录图形验证码 (一般用于服务器没有字体或者开启了两步验证后才关闭图形验证码) + disabled-captcha: false + # 前端消息弹出位置,可选 topLeft topRight bottomLeft bottomRight + notification-placement: + # 前端消息传输需要编码或者加密 目前支持:NONE、BASE64 + transport-encryption: NONE + # 查看日志时初始读取最后多少行(默认10,0不读取) + init-read-line: 10 + db: + # 数据库默认 支持 :H2、MYSQL、MARIADB、POSTGRESQL + mode: H2 + # 日志存储条数,将自动清理旧数据,配置小于等于零则不清理 + log-storage-count: 10000 + # H2 模式无需配置 mysql 配置 jdbc 地址 + url: + # 数据库账号 默认 jpom + user-name: jpom + # 数据库密码 默认 jpom 如果自行配置请保证密码强度 + user-pwd: jpom + # h2 数据库缓存大小 kilobyte 1KB 1,024 megabyte 1MB 1,048,576 + cache-size: 50MB + # 自动备份间隔天数 小于等于 0 不自动备份 + auto-backup-interval-day: 1 + # 自动备份保留天数 小于等于 0,不自动删除自动备份数据 + auto-backup-reserve-day: 5 + # 数据库连接池相关配置 + max-active: 500 + initial-size: 10 + max-wait: 10 + min-idle: 1 + show-sql: false + # 构建相关配置 + build: + # 最多保存多少份历史记录 + max-history-count: 1000 + # 单个最多保存多少份历史记录 + item-max-history-count: 50 + # 构建命令是否检查 存在删除命令 + check-delete-command: true + # 构建线程池大小,小于 1 则为不限制,默认大小为 5 + pool-size: 5 + # 构建任务等待数量,超过此数量将取消构建任务,值最小为 1 + pool-wait-queue: 10 + # 日志显示 压缩折叠显示进度比例 范围 1-100 + log-reduce-progress-ratio: 5 + fileStorage: + # 文件中心存储路径 + save-pah: + scan-static-dir-cron: 0 0/1 * * * + # 是否开启静态文件目录监听 + watch-monitor-static-dir: true + # 监听静态文件目录层级 + watch-monitor-max-depth: 5 + assets: + # 监控线程池大小,小于等于0 为CPU核心数 + monitor-pool-size: 0 + # 监控任务等待数量,超过此数量将取消监控任务,值最小为 1 + monitor-pool-wait-queue: 500 + ssh: + monitor-cron: 0 0/1 * * * ? + disable-monitor-group-name: + - 禁用监控 + docker: + monitor-cron: 0 0/1 * * * ? server: + #运行端口号 port: 2122 + # address: 127.0.0.1 servlet: session: cookie: name: JPOMID-SERVER timeout: 1H + encoding: + charset: UTF-8 + force: true + enabled: true + forceRequest: true + forceResponse: true + compression: + # gzip 压缩 + enabled: true + #mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,image/png + min-response-size: 2048 + tomcat: + uri-encoding: UTF-8 + connection-timeout: 10M + spring: - application: - name: jpomServer - profiles: - active: local + # profiles: + # active: mysql + # active: mariadb + # active: postgresql + + web: + resources: + static-locations: classpath:/dist/ + cache: + period: 1D mvc: throw-exception-if-no-handler-found: true + log-request-details: true servlet: multipart: + # 上传文件大小限制 max-request-size: 2GB max-file-size: 1GB -#thymeleaf end -banner: - msg: Jpom-Server 管理系统启动中 -# 静态资源配置 -interceptor: - resourceHandler: /css/**,/fonts/**,/img/**,/js/**,/favicon.ico - resourceLocation: classpath:/dist/css/,classpath:/dist/fonts/,classpath:/dist/img/,classpath:/dist/js/,classpath:/dist/favicon.ico - initPackageName: io.jpom.common.interceptor -# 启动完成自动初始化指定包 -preload: - packageName: io.jpom.system.init -# 强制去掉空格 -request: - trimAll: true - parameterXss: false -#在线编辑格式 -#fileFormat: txt|yml|conf|properties|ini + h2: + console: + # 是否开启 web 访问数据库(url: http://${ip}:${port}/h2-console) + enabled: true + # 是否允许远程访问(开启此配置有安全风险),默认为 false(当部署到服务器上之后,是否可以通过其他浏览器访问数据库) + settings: + web-allow-others: false + diff --git a/modules/server/src/main/resources/bin/execTemplate.bat b/modules/server/src/main/resources/bin/execTemplate.bat deleted file mode 100644 index 983fcf5499..0000000000 --- a/modules/server/src/main/resources/bin/execTemplate.bat +++ /dev/null @@ -1,29 +0,0 @@ -@REM -@REM The MIT License (MIT) -@REM -@REM Copyright (c) 2019 码之科技工作室 -@REM -@REM Permission is hereby granted, free of charge, to any person obtaining a copy of -@REM this software and associated documentation files (the "Software"), to deal in -@REM the Software without restriction, including without limitation the rights to -@REM use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -@REM the Software, and to permit persons to whom the Software is furnished to do so, -@REM subject to the following conditions: -@REM -@REM The above copyright notice and this permission notice shall be included in all -@REM copies or substantial portions of the Software. -@REM -@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -@REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -@REM FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -@REM COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -@REM IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -@REM CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -@REM - -@echo off -CHCP 65001 - - -@REM 设置环境变量,避免部分服务器没有 taskkill -set PATH = %PATH%;C:\Windows\system32;C:\Windows;C:\Windows\system32\Wbem diff --git a/modules/server/src/main/resources/bin/execTemplate.sh b/modules/server/src/main/resources/bin/execTemplate.sh deleted file mode 100644 index c147e1d1f8..0000000000 --- a/modules/server/src/main/resources/bin/execTemplate.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# -# The MIT License (MIT) -# -# Copyright (c) 2019 码之科技工作室 -# -# 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. -# - - -# loading env -if [ -f /etc/profile ]; then - . /etc/profile -fi -if [ -f /etc/bashrc ]; then - . /etc/bashrc -fi -if [ -f ~/.bashrc ]; then - . ~/.bashrc -fi -if [ -f ~/.bash_profile ]; then - . ~/.bash_profile -fi - -# start diff --git a/modules/server/src/main/resources/bin/extConfig.yml b/modules/server/src/main/resources/bin/extConfig.yml deleted file mode 100644 index 9859a087e2..0000000000 --- a/modules/server/src/main/resources/bin/extConfig.yml +++ /dev/null @@ -1,73 +0,0 @@ -jpom: - # jpom 数据存储路径, 如果调试模式运行默认路径为【${user.home}/jpom/】,安装运行默认为jar包文件的父级 - path: - authorize: - # api接口请求token 长度要求大于等于6位,字母数字符号组合 - token: - #登录token失效时间(单位:小时),默认为24 - expired: 24 - #登录token失效后自动续签时间(单位:分钟),默认为60,不自动续签 - renewal: 60 - # jwt 签名(加密)的key 长度建议控制到 16位 - key: - # 前端接口 超时时间 单位秒 - webApiTimeout: 20 - # 系统名称 - name: - # 副标题 - subTitle: - # 登录页名称 - loginTitle: - # logo 文件路径(绝对路径) jpg、png、gif - logoFile: -user: - # 最多能创建多少个用户 - maxCount: 10 - # 用户连续登录失败次数,超过此数将被限制登录 - alwaysLoginError: 5 - # 当ip连续登录失败,锁定对应IP时长,单位毫秒 - ipErrorLockTime: 60*60*5*1000 -consoleLog: - # 是否记录接口请求日志 - reqXss: true - # 是否记录接口响应日志 - reqResponse: true -log: - # 日志文件的编码格式,如果没有指定就自动识别,自动识别可能出现不准确的情况 - fileCharset: - # 查看日志时初始读取最后多少行(默认10,0不读取) - intiReadLine: 10 -db: - # 日志存储条数,将自动清理旧数据,配置小于等于零则不清理 - logStorageCount: 100000 - # 数据库账号 - userName: - # 数据库密码 - userPwd: - # h2 数据库缓存大小 kilobyte 1KB 1,024 megabyte 1MB 1,048,576 - cacheSize: - # 自动备份间隔天数 小于等于 0 不自动备份 - autoBackupIntervalDay: 1 -# spring 相关配置 -spring: - h2: - console: - # 是否开启 web 访问数据库(url: http://${ip}:${port}/h2-console) - enabled: false - # 是否允许远程访问,默认为 false(当部署到服务器上之后,是否可以通过其他浏览器访问数据库) - settings: - web-allow-others: false -# 构建相关配置 -build: - # 最多保存多少份历史记录 - maxHistoryCount: 1000 - # 单个最多保存多少份历史记录 - itemMaxHistoryCount: 50 - # 构建命令是否检查 存在删除命令 - checkDeleteCommand: true -ssh: - # ssh 执行命令的 默认加载的环境变量 - initEnv: source /etc/profile && source ~/.bash_profile && source ~/.bashrc -node: - # 上传文件的超时时间 单位秒,最短5秒钟 - uploadFileTimeOut: 300 diff --git a/modules/server/src/main/resources/bin/h2-db-v1.1.sql b/modules/server/src/main/resources/bin/h2-db-v1.1.sql deleted file mode 100644 index 40fc654530..0000000000 --- a/modules/server/src/main/resources/bin/h2-db-v1.1.sql +++ /dev/null @@ -1,8 +0,0 @@ --- @author bwcx_jzy - -ALTER TABLE USEROPERATELOGV1 ALTER COLUMN NODEID VARCHAR(50) COMMENT '节点ID'; - -ALTER TABLE MONITORNOTIFYLOG ALTER COLUMN NODEID VARCHAR(50) COMMENT '节点ID'; - -ALTER TABLE OUTGIVINGLOG ADD IF NOT EXISTS MODIFYUSER VARCHAR (50) comment '操作人'; - diff --git a/modules/server/src/main/resources/bin/h2-db-v1.sql b/modules/server/src/main/resources/bin/h2-db-v1.sql deleted file mode 100644 index e8f1236da6..0000000000 --- a/modules/server/src/main/resources/bin/h2-db-v1.sql +++ /dev/null @@ -1,192 +0,0 @@ --- 创建操作记录表 -CREATE TABLE IF NOT EXISTS PUBLIC.USEROPERATELOGV1 -( - ID VARCHAR(50) not null comment 'id', - REQID VARCHAR(50) COMMENT '请求ID', - IP VARCHAR(30) COMMENT '客户端IP地址', - USERID VARCHAR(30) COMMENT '操作的用户ID', - RESULTMSG CLOB COMMENT '操作的结果信息', - OPTTYPE INTEGER COMMENT '操作类型', - OPTSTATUS INTEGER COMMENT '操作状态 成功/失败', - OPTTIME BIGINT COMMENT '操作时间', - NODEID VARCHAR(30) COMMENT '节点ID', - DATAID VARCHAR(50) COMMENT '操作的数据ID', - USERAGENT CLOB COMMENT '浏览器标识', - REQDATA CLOB COMMENT '用户请求参数', - CONSTRAINT USEROPERATELOGV1_PK PRIMARY KEY (REQID) -); -COMMENT ON TABLE USEROPERATELOGV1 is '操作日志'; - --- 监控异常记录表 -CREATE TABLE IF NOT EXISTS PUBLIC.MONITORNOTIFYLOG -( - ID VARCHAR(50) NOT NULL COMMENT '记录id', - MONITORID varchar(50) COMMENT '监控id', - NODEID VARCHAR(30) COMMENT '节点id', - PROJECTID VARCHAR(30) COMMENT '项目id', - CREATETIME BIGINT COMMENT '异常时间', - TITLE VARCHAR(100) COMMENT '异常描述', - CONTENT CLOB COMMENT '异常内容', - STATUS TINYINT COMMENT '当前状态', - NOTIFYSTYLE TINYINT COMMENT '通知方式', - NOTIFYSTATUS TINYINT COMMENT '通知状态', - NOTIFYOBJECT CLOB COMMENT '通知对象', - NOTIFYERROR CLOB COMMENT '通知异常内容', - CONSTRAINT MONITORNOTIFYLOG_PK PRIMARY KEY (ID) -); -COMMENT ON TABLE MONITORNOTIFYLOG is '监控异常日志记录'; - --- 构建历史 -CREATE TABLE IF NOT EXISTS PUBLIC.BUILDHISTORYLOG -( - ID VARCHAR(50) not null COMMENT '表id', - BUILDDATAID VARCHAR(50) COMMENT '构建的数据id', - BUILDNUMBERID INTEGER COMMENT '构建编号', - STATUS TINYINT COMMENT '构建状态', - STARTTIME BIGINT COMMENT '开始时间', - ENDTIME BIGINT COMMENT '结束时间', - RESULTDIRFILE VARCHAR(200) COMMENT '构建产物目录', - //BUILDUSER VARCHAR(50) COMMENT '构建人', - RELEASEMETHOD TINYINT COMMENT '发布方式', - RELEASEMETHODDATAID VARCHAR(200) COMMENT '发布的数据id', - AFTEROPT TINYINT COMMENT '发布后操作', - CONSTRAINT BUILDHISTORYLOG_PK PRIMARY KEY (ID) -); -COMMENT ON TABLE BUILDHISTORYLOG is '构建历史记录'; - -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS CLEAROLD TINYINT COMMENT '是否清空发布'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS NAME VARCHAR(100) COMMENT '构建名称'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS RELEASECOMMAND CLOB COMMENT '发布命令'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS RELEASEPATH VARCHAR(100) COMMENT '发布到的目录'; -ALTER TABLE BUILDHISTORYLOG - ALTER COLUMN RELEASECOMMAND CLOB COMMENT '发布命令'; - --- @author jzy -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS BUILDNAME VARCHAR(100) COMMENT '构建名称'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS MODIFYUSER VARCHAR(50) comment '操作人'; - --- 分发日志 -CREATE TABLE IF NOT EXISTS PUBLIC.OUTGIVINGLOG -( - ID VARCHAR(50) not null comment 'id', - OUTGIVINGID VARCHAR(50) comment '分发id', - STATUS TINYINT comment '状态', - STARTTIME BIGINT comment '开始时间', - ENDTIME BIGINT comment '结束时间', - RESULT CLOB comment '消息', - NODEID VARCHAR(100) comment '节点id', - PROJECTID VARCHAR(100) comment '项目id', - CONSTRAINT OUTGIVINGLOG_PK PRIMARY KEY (ID) -); -comment on table OUTGIVINGLOG is '分发日志'; - --- 系统监控记录 -CREATE TABLE IF NOT EXISTS PUBLIC.SYSTEMMONITORLOG -( - ID VARCHAR(50) not null comment 'id', - NODEID VARCHAR(100) comment '节点id', - MONITORTIME BIGINT comment '监控时间', - OCCUPYCPU DOUBLE comment '占用cpu', - OCCUPYMEMORY DOUBLE comment '占用内存', - OCCUPYDISK DOUBLE comment '占用磁盘', - CONSTRAINT SYSTEMMONITORLOG_PK PRIMARY KEY (ID) -); -comment on table SYSTEMMONITORLOG is '系统监控记录'; - -ALTER TABLE SYSTEMMONITORLOG - ALTER COLUMN ID VARCHAR(50) COMMENT 'ID'; - -ALTER TABLE SYSTEMMONITORLOG - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE SYSTEMMONITORLOG - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; - -ALTER TABLE SYSTEMMONITORLOG - ALTER COLUMN MONITORTIME BIGINT; - - -CREATE UNIQUE INDEX IF NOT EXISTS SYSTEMMONITORLOG_INDEX1 ON PUBLIC.SYSTEMMONITORLOG (NODEID, MONITORTIME); - --- @author jzy -ALTER TABLE SYSTEMMONITORLOG - ADD COLUMN IF NOT EXISTS OCCUPYMEMORYUSED DOUBLE COMMENT '占用内存 (使用)'; - --- ssh 终端操作记录表 -CREATE TABLE IF NOT EXISTS PUBLIC.SSHTERMINALEXECUTELOG -( - ID VARCHAR(50) NOT NULL COMMENT 'ID', - IP VARCHAR(30) COMMENT '客户端IP地址', - USERID VARCHAR(30) COMMENT '操作的用户ID', - OPTTIME BIGINT COMMENT '操作时间', - USERAGENT CLOB COMMENT '浏览器标识', - COMMANDS CLOB comment '操作的命令', - SSHID varchar(50) comment '操作的sshid', - SSHNAME varchar(50) comment '操作的ssh name', - REFUSE INTEGER COMMENT '拒绝执行', - CONSTRAINT SSHTERMINALEXECUTELOG_PK PRIMARY KEY (ID) -); -COMMENT ON TABLE SSHTERMINALEXECUTELOG is 'ssh 终端操作记录表'; - --- @author jzy 添加基础字段 -ALTER TABLE SSHTERMINALEXECUTELOG - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE SSHTERMINALEXECUTELOG - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; - --- @author bwcx_jzy -ALTER TABLE SSHTERMINALEXECUTELOG - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; -ALTER TABLE BUILDHISTORYLOG - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; -ALTER TABLE MONITORNOTIFYLOG - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; -ALTER TABLE OUTGIVINGLOG - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; - -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS ID VARCHAR(50) not null default '' comment 'id'; - -update USEROPERATELOGV1 -set USEROPERATELOGV1.ID =USEROPERATELOGV1.REQID -where USEROPERATELOGV1.ID = ''; - -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS MODIFYUSER VARCHAR(50) comment '操作人'; -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS classFeature VARCHAR(100) comment '操作的功能'; -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS methodFeature VARCHAR(100) comment '操作的方法'; -ALTER TABLE USEROPERATELOGV1 - ADD IF NOT EXISTS reqTime BIGINT comment '请求时间'; - -ALTER TABLE MONITORNOTIFYLOG - ADD IF NOT EXISTS ID VARCHAR(50) not null default '' comment 'id'; - -ALTER TABLE MONITORNOTIFYLOG - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE MONITORNOTIFYLOG - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; - -ALTER TABLE OUTGIVINGLOG - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE OUTGIVINGLOG - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; - - - diff --git a/modules/server/src/main/resources/bin/h2-db-v2.1.sql b/modules/server/src/main/resources/bin/h2-db-v2.1.sql deleted file mode 100644 index e58fc0725f..0000000000 --- a/modules/server/src/main/resources/bin/h2-db-v2.1.sql +++ /dev/null @@ -1,4 +0,0 @@ --- @author bwcx_jzy - -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS webhook VARCHAR (255) comment 'webhook'; diff --git a/modules/server/src/main/resources/bin/h2-db-v2.sql b/modules/server/src/main/resources/bin/h2-db-v2.sql deleted file mode 100644 index d67df47941..0000000000 --- a/modules/server/src/main/resources/bin/h2-db-v2.sql +++ /dev/null @@ -1,141 +0,0 @@ --- @author Hotstrip --- @Date 2021-07-31 --- add new tables for storage build info, repo info... - --- 仓库信息 -CREATE TABLE IF NOT EXISTS PUBLIC.REPOSITORY -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - `NAME` VARCHAR(50) comment '仓库名称', - GITURL varchar(255) comment '仓库地址', - REPOTYPE int comment '仓库类型{0: GIT, 1: SVN}', - PROTOCOL int comment '拉取代码的协议{0: http, 1: ssh}', - USERNAME VARCHAR(50) comment '登录用户', - PASSWORD VARCHAR(50) comment '登录密码', - RSAPUB VARCHAR(2048) comment 'SSH RSA 公钥', - RSAPRV VARCHAR(4096) comment 'SSH RSA 私钥', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - CONSTRAINT REPOSITORY_PK PRIMARY KEY (ID) -); -comment on table REPOSITORY is '仓库信息'; - --- 构建信息 -CREATE TABLE IF NOT EXISTS PUBLIC.BUILD_INFO -( - ID VARCHAR(50) not null comment 'id', - REPOSITORYID VARCHAR(50) not null comment '仓库 ID', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - `NAME` VARCHAR(50) comment '构建名称', - BUILDID int comment '构建 ID', - `GROUP` VARCHAR(50) comment '分组名称', - BRANCHNAME VARCHAR(50) comment '分支', - SCRIPT VARCHAR(200) comment '构建命令', - RESULTDIRFILE VARCHAR(50) comment '构建产物目录', - RELEASEMETHOD int comment '发布方法{0: 不发布, 1: 节点分发, 2: 分发项目, 3: SSH}', - MODIFYUSER VARCHAR(50) comment '修改人', - `STATUS` int comment '状态', - TRIGGERTOKEN VARCHAR(20) comment '触发器token', - EXTRADATA CLOB comment '额外信息,JSON 字符串格式', - RELEASEMETHODDATAID VARCHAR(200) comment '构建关联的数据ID', - CONSTRAINT BUILD_INFO_PK PRIMARY KEY (ID) -); -comment on table BUILD_INFO is '构建信息'; - --- @author jzy 2021-08-13 添加基础字段 - -ALTER TABLE REPOSITORY - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE REPOSITORY - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; - -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS CREATETIMEMILLIS BIGINT COMMENT '数据创建时间'; -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间'; - --- @author Hotstrip -> add REPOSITORYID in case it's not exists -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS REPOSITORYID VARCHAR(50) COMMENT '仓库 ID'; - --- @author jzy 字段类型修改为 json --- @author Hotstrip 字段类型修改为 CLOB -ALTER TABLE BUILD_INFO - ALTER COLUMN EXTRADATA CLOB COMMENT '额外信息,JSON 字符串格式'; - --- @author jzy 增加构建产物字段长度 -ALTER TABLE BUILD_INFO - ALTER COLUMN RESULTDIRFILE VARCHAR(200) comment '构建产物目录'; - --- @author Hotstrip 增加 MODIFYUSER -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS MODIFYUSER VARCHAR(50) comment '修改人'; - --- @author Hotstrip 增加 STATUS -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS `STATUS` int comment '状态'; - --- @author Hotstrip 增加 TRIGGERTOKEN -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS TRIGGERTOKEN VARCHAR(20) comment '触发器token'; - --- @author bwcx_jzy 2021-12-06 -ALTER TABLE BUILD_INFO - ALTER COLUMN TRIGGERTOKEN VARCHAR(100) comment '触发器token'; - --- @author jzy 增加 RSAPRV -ALTER TABLE REPOSITORY - ADD IF NOT EXISTS RSAPRV VARCHAR(4096) comment 'SSH RSA 私钥'; - --- @author Hotstrip update RSAPRV field length -ALTER TABLE REPOSITORY - ALTER COLUMN RSAPRV VARCHAR(4096) comment 'SSH RSA 私钥'; - --- @author bwcx_jzy 增加 RELEASEMETHODDATAID -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS RELEASEMETHODDATAID VARCHAR(200) comment '构建关联的数据ID'; - --- @author lidaofu 增加 STRIKE -ALTER TABLE REPOSITORY - ADD IF NOT EXISTS STRIKE int DEFAULT 0 comment '逻辑删除'; - - --- @author bwcx_jzy 增加 MODIFYUSER -ALTER TABLE REPOSITORY - ADD IF NOT EXISTS MODIFYUSER VARCHAR(50) comment '修改人'; - --- @author jzy -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS BRANCHTAGNAME VARCHAR(50) comment '标签'; - --- @author hjk 增加字段长度,200->500 -ALTER TABLE BUILD_INFO - ALTER COLUMN SCRIPT VARCHAR(500) comment '构建命令'; - --- 备份数据库信息表 @author Hotstrip -CREATE TABLE IF NOT EXISTS PUBLIC.BACKUP_INFO -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - `NAME` VARCHAR(50) comment '备份名称', - FILEPATH VARCHAR(200) comment '文件地址', - BACKUPTYPE int comment '备份类型{0: 全量, 1: 部分}', - FILESIZE BIGINT comment '文件大小', - SHA1SUM VARCHAR(50) comment 'SHA1 信息', - `STATUS` int default '0' comment '状态{0: 默认; 1: 成功; 2: 失败}', - CONSTRAINT BACKUP_INFO_PK PRIMARY KEY (ID) -); -comment on table BACKUP_INFO is '备份数据库信息'; - --- @author Hotstrip 增加字段 status -ALTER TABLE BACKUP_INFO - ADD IF NOT EXISTS `STATUS` int default '0' comment '状态{0: 默认; 1: 成功; 2: 失败}'; - --- @author bwcx_jzy -ALTER TABLE BUILD_INFO - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; -ALTER TABLE REPOSITORY - ADD IF NOT EXISTS workspaceId VARCHAR(50) comment '所属工作空间'; diff --git a/modules/server/src/main/resources/bin/h2-db-v3.1.sql b/modules/server/src/main/resources/bin/h2-db-v3.1.sql deleted file mode 100644 index cc3802fe62..0000000000 --- a/modules/server/src/main/resources/bin/h2-db-v3.1.sql +++ /dev/null @@ -1,4 +0,0 @@ --- @author bwcx_jzy - -ALTER TABLE OUT_GIVING - ADD IF NOT EXISTS intervalTime int DEFAULT 10 comment '分发间隔时间'; diff --git a/modules/server/src/main/resources/bin/h2-db-v3.sql b/modules/server/src/main/resources/bin/h2-db-v3.sql deleted file mode 100644 index b4adc3ba94..0000000000 --- a/modules/server/src/main/resources/bin/h2-db-v3.sql +++ /dev/null @@ -1,223 +0,0 @@ --- @author bwcx_jzy --- @Date 2021-12-02 --- add new tables for storage other info... - --- 系统参数名称 -CREATE TABLE IF NOT EXISTS PUBLIC.SYSTEM_PARAMETERS -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - `VALUE` CLOB comment '参数值,JSON 字符串格式', - DESCRIPTION varchar(255) comment '参数描述', - CONSTRAINT SYSTEM_PARAMETERS_PK PRIMARY KEY (ID) -); -comment on table SYSTEM_PARAMETERS is '系统参数表'; - --- 用户信息表 -CREATE TABLE IF NOT EXISTS PUBLIC.USER_INFO -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - parent VARCHAR(50) not null comment '创建人', - `NAME` VARCHAR(50) comment '昵称', - systemUser int default 0 comment '是否为系统管理员 {1,是,0 否(默认)}', - password varchar(100) comment '密码', - salt varchar(50) comment '盐值', - pwdErrorCount int default 0 comment '密码错误次数', - lastPwdErrorTime BIGINT default 0 COMMENT '最后登录失败时间', - lockTime BIGINT default 0 comment '锁定时长', - email varchar(255) comment '邮箱地址', - dingDing varchar(255) comment '钉钉地址', - workWx varchar(255) comment '企业微信地址', - CONSTRAINT USER_INFO_PK PRIMARY KEY (ID) -); -comment on table USER_INFO is '用户信息表'; - --- 用户的盐值保证唯一 -CREATE UNIQUE INDEX IF NOT EXISTS USER_INF_SALT_INDEX1 ON PUBLIC.USER_INFO (salt); - --- 工作空间表 -CREATE TABLE IF NOT EXISTS PUBLIC.WORKSPACE -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - `NAME` VARCHAR(50) comment '名称', - DESCRIPTION varchar(255) comment '描述', - CONSTRAINT WORKSPACE_PK PRIMARY KEY (ID) -); -comment on table WORKSPACE is '用户信息表'; - --- 用户绑定工作空间表 -CREATE TABLE IF NOT EXISTS PUBLIC.USER_BIND_WORKSPACE -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - userId VARCHAR(50) not null comment '用户ID', - workspaceId VARCHAR(50) not null comment '工作空间ID', - CONSTRAINT USER_BIND_WORKSPACE_PK PRIMARY KEY (ID) -); -comment on table USER_BIND_WORKSPACE is '用户工作空间绑定表'; - --- 节点列表 -CREATE TABLE IF NOT EXISTS PUBLIC.NODE_INFO -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - `NAME` VARCHAR(50) comment '名称', - url varchar(100) not null comment '节点 url IP:PORT', - loginName varchar(100) not null comment '节点登录名', - loginPwd varchar(100) not null comment '节点密码', - protocol varchar(10) not null comment '协议 http https', - openStatus int DEFAULT 0 comment '启用状态{1,启用,0 未启用)}', - timeOut int default 0 comment '节点超时时间', - cycle int comment '监控周期', - CONSTRAINT NODE_INFO_PK PRIMARY KEY (ID) -); -comment on table NODE_INFO is '节点信息表'; - -ALTER TABLE NODE_INFO - ADD IF NOT EXISTS sshId VARCHAR(50) comment '关联的sshid'; - --- ssh 信息 -CREATE TABLE IF NOT EXISTS PUBLIC.SSH_INFO -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - `NAME` VARCHAR(50) comment '名称', - host varchar(100) not null comment 'ssh host IP', - port int not null comment '端口', - user varchar(100) not null comment '用户', - password varchar(100) comment '密码', - charset varchar(100) comment '编码格式', - fileDirs CLOB comment '文件目录', - privateKey CLOB comment '私钥', - connectType varchar(10) comment '连接方式', - notAllowedCommand CLOB comment '不允许执行的命令', - allowEditSuffix CLOB comment '允许编辑的后缀文件', - CONSTRAINT SSH_INFO_PK PRIMARY KEY (ID) -); -comment on table SSH_INFO is 'ssh信息表'; - --- 项目信息表 -CREATE TABLE IF NOT EXISTS PUBLIC.PROJECT_INFO -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - projectId varchar(50) not null comment '项目ID', - nodeId varchar(50) not null comment '节点ID', - `NAME` VARCHAR(50) not null comment '名称', - mainClass varchar(100) comment 'mainClas', - lib varchar(100) comment 'lib', - whitelistDirectory varchar(100) comment 'whitelistDirectory', - logPath varchar(100) comment 'logPath', - jvm CLOB comment 'jvm', - args CLOB comment 'args', - javaCopyItemList CLOB comment 'javaCopyItemList', - token varchar(255) comment 'token', - jdkId VARCHAR(50) comment '名称', - runMode varchar(20) comment '连接方式', - outGivingProject int DEFAULT 0 comment '分发项目{1,分发,0 独立项目}', - javaExtDirsCp CLOB comment 'javaExtDirsCp', - CONSTRAINT PROJECT_INFO_PK PRIMARY KEY (ID) -); -comment on table PROJECT_INFO is '项目信息表'; - --- 监控信息 -CREATE TABLE IF NOT EXISTS PUBLIC.MONITOR_INFO -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - `NAME` VARCHAR(50) not null comment '名称', - autoRestart TINYINT DEFAULT 0 comment '是否自动重启{1,是,0 否}', - status TINYINT DEFAULT 0 comment '启用状态{1,启用,0 未启用}', - alarm TINYINT DEFAULT 0 comment '报警状态{1,报警中,0 未报警}', - cycle int DEFAULT 0 comment '监控周期', - notifyUser CLOB comment '报警联系人', - projects CLOB comment '监控的项目', - CONSTRAINT MONITOR_INFO_PK PRIMARY KEY (ID) -); -comment on table MONITOR_INFO is '监控信息'; - --- 节点分发信息 -CREATE TABLE IF NOT EXISTS PUBLIC.OUT_GIVING -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - `NAME` VARCHAR(50) not null comment '名称', - afterOpt int DEFAULT 0 comment '分发后的操作', - clearOld TINYINT DEFAULT 0 comment '是否清空旧包发布', - outGivingProject TINYINT DEFAULT 0 comment '是否为单独创建的分发项目', - outGivingNodeProjectList CLOB comment '分发项目信息', - CONSTRAINT OUT_GIVING_PK PRIMARY KEY (ID) -); -comment on table OUT_GIVING is '节点分发信息'; - --- 操作监控 -CREATE TABLE IF NOT EXISTS PUBLIC.MONITOR_USER_OPT -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - `NAME` VARCHAR(50) not null comment '名称', - monitorUser CLOB comment '监控的人', - status TINYINT DEFAULT 0 comment '启用状态{1,启用,0 未启用}', - notifyUser CLOB comment '报警联系人', - monitorFeature CLOB comment '监控的项目', - monitorOpt CLOB comment '监控的项目', - CONSTRAINT MONITOR_USER_OPT_PK PRIMARY KEY (ID) -); -comment on table MONITOR_USER_OPT is '监控信息'; - - --- 工作空间环境变量表 -CREATE TABLE IF NOT EXISTS PUBLIC.WORKSPACE_ENV_VAR -( - ID VARCHAR(50) not null comment 'id', - CREATETIMEMILLIS BIGINT COMMENT '数据创建时间', - MODIFYTIMEMILLIS BIGINT COMMENT '数据修改时间', - MODIFYUSER VARCHAR(50) comment '修改人', - STRIKE int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', - workspaceId varchar(50) not null comment '所属工作空间', - `NAME` VARCHAR(50) comment '名称', - DESCRIPTION varchar(255) comment '参数描述', - `value` CLOB comment '值', - CONSTRAINT WORKSPACE_ENV_VAR_PK PRIMARY KEY (ID) -); -comment on table WORKSPACE_ENV_VAR is '用户信息表'; - - - - diff --git a/modules/server/src/main/resources/config_default/application.yml b/modules/server/src/main/resources/config_default/application.yml new file mode 100644 index 0000000000..5f6ce9345f --- /dev/null +++ b/modules/server/src/main/resources/config_default/application.yml @@ -0,0 +1,179 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +jpom: + # jpom 数据存储路径, 如果调试模式运行默认路径为【${user.home}/jpom/】,安装运行默认为jar包文件的父级 + path: + # 集群配置 + cluster: + # 集群Id,默认为 default 不区分大小写,只能是字母或者数字,长度小于 20 + id: default + # 心跳监控时间 (需要大于零) 单位秒 最小配置 5 秒 + heart-second: 30 + node: + # 节点心跳监控时间 (需要大于零) 单位秒 最小配置 5秒 + heart-second: 30 + # 节点统计日志保留天数,如果小于等于 0 不自动删除 + stat-log-keep-days: 3 + # 上传文件的超时时间 单位秒,最短5秒钟 + upload-file-timeout: 300 + # 节点文件分片上传大小,单位 M,建议小于 5MB(需要考虑插件端上传文件大小限制) + upload-file-slice-size: 1 + # 节点文件分片上传并发数,最小1 最大 服务端 CPU 核心数 + upload-file-concurrent: 2 + # web socket 消息最大长度 + web-socket-message-size-limit: 5MB + system: + # cron 定时器是否开启匹配秒 + timer-match-second: false + # 旧包文件保留个数 + old-jars-count: 2 + # 系统日志编码格式 + log-charset: UTF-8 + # 控制台编码格式 + console-charset: + # 在线升级允许降级-操作 + allowed-downgrade: false + # 执行系统主要命名是否填充 sudo(sudo xxx) 使用前提需要配置 sudo 免密 + command-use-sudo: false + # 系统语言:zh-CN、en-US + lang: zh-CN + user: + # 用户连续登录失败次数,超过此数将被限制登录 + always-login-error: 5 + # IP连续登录失败次数,超过此数将被限制登录 + always-ip-login-error: 10 + # 当ip连续登录失败,锁定对应IP时长,5h、1d + ip-error-lock-time: 5h + # 是否强制提醒用户开启 mfa + force-mfa: false + #登录token失效时间(单位:小时),默认为24 + token-expired: 24 + #登录token失效后自动续签时间(单位:分钟),默认为60,不自动续签 + token-renewal: 60 + # jwt 签名(加密)的key 长度建议控制到 16位 + token-jwt-key: + web: + # 前端接口 超时时间 单位秒(最小 5 秒) + api-timeout: 20 + # 禁用页面引导导航 + disabled-guide: false + # 禁用登录图形验证码 (一般用于服务器没有字体或者开启了两部验证后才关闭图形验证码) + disabled-captcha: false + # 前端消息弹出位置,可选 topLeft topRight bottomLeft bottomRight + notification-placement: + # 前端消息传输需要编码或者加密 目前支持:NONE、BASE64 + transport-encryption: NONE + # 查看日志时初始读取最后多少行(默认10,0不读取) + init-read-line: 10 + db: + # 数据库默认 支持 :H2、MYSQL、MARIADB、POSTGRESQL + mode: H2 + # 日志存储条数,将自动清理旧数据,配置小于等于零则不清理 + log-storage-count: 10000 + # H2 模式无需配置 mysql 配置 jdbc 地址 + url: + # 数据库账号 默认 jpom + user-name: jpom + # 数据库密码 默认 jpom 如果自行配置请保证密码强度 + user-pwd: jpom + # h2 数据库缓存大小 kilobyte 1KB 1,024 megabyte 1MB 1,048,576 + cache-size: 50MB + # 自动备份间隔天数 小于等于 0 不自动备份 + auto-backup-interval-day: 1 + # 自动备份保留天数 小于等于 0,不自动删除自动备份数据 + auto-backup-reserve-day: 5 + # 数据库连接池相关配置 + max-active: 500 + initial-size: 10 + max-wait: 10 + min-idle: 1 + # 控制台是否打印 sql 信息 + show-sql: false + # 构建相关配置 + build: + # 最多保存多少份历史记录 + max-history-count: 1000 + # 单个最多保存多少份历史记录 + item-max-history-count: 50 + # 构建命令是否检查 存在删除命令 + check-delete-command: true + # 构建线程池大小,小于 1 则为不限制,默认大小为 5 + pool-size: 5 + # 构建任务等待数量,超过此数量将取消构建任务,值最小为 1 + pool-wait-queue: 10 + # 日志显示 压缩折叠显示进度比例 范围 1-100 + log-reduce-progress-ratio: 5 + file-storage: + # 文件中心存储路径 + save-pah: + scan-static-dir-cron: 0 0/1 * * * + # 是否开启静态文件目录监听 + watch-monitor-static-dir: true + # 监听静态文件目录层级 + watch-monitor-max-depth: 5 + assets: + # 监控线程池大小,小于等于0 为CPU核心数 + monitor-pool-size: 0 + # 监控任务等待数量,超过此数量将取消监控任务,值最小为 1 + monitor-pool-wait-queue: 500 + # ssh 资产 + ssh: + # 监控频率 + monitor-cron: 0 0/1 * * * ? + # 指定分组不启用监控功能(如果想禁用所有配置 * 即可) + disable-monitor-group-name: + - 禁用监控 + # docker 资产 + docker: + # 监控频率 + monitor-cron: 0 0/1 * * * ? +server: + #运行端口号 + port: 2122 + servlet: + session: + cookie: + name: JPOMID-SERVER + timeout: 1H + encoding: + charset: UTF-8 + force: true + enabled: true + forceRequest: true + forceResponse: true + compression: + # gzip 压缩 + enabled: true + #mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,image/png + min-response-size: 2048 + tomcat: + uri-encoding: UTF-8 +spring: + web: + resources: + static-locations: classpath:/dist/ + cache: + period: 1D + mvc: + throw-exception-if-no-handler-found: true + log-request-details: true + servlet: + multipart: + # 上传文件大小限制 + max-request-size: 2GB + max-file-size: 1GB + h2: + console: + # 是否开启 web 访问数据库(url: http://${ip}:${port}/h2-console) + enabled: false + # 是否允许远程访问(开启此配置有安全风险),默认为 false(当部署到服务器上之后,是否可以通过其他浏览器访问数据库) + settings: + web-allow-others: false diff --git a/modules/server/src/main/resources/config_default/exec/template.bat b/modules/server/src/main/resources/config_default/exec/template.bat new file mode 100644 index 0000000000..e4f362f90f --- /dev/null +++ b/modules/server/src/main/resources/config_default/exec/template.bat @@ -0,0 +1,19 @@ +@REM +@REM Copyright (c) 2019 Of Him Code Technology Studio +@REM Jpom is licensed under Mulan PSL v2. +@REM You can use this software according to the terms and conditions of the Mulan PSL v2. +@REM You may obtain a copy of Mulan PSL v2 at: +@REM http://license.coscl.org.cn/MulanPSL2 +@REM THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +@REM See the Mulan PSL v2 for more details. +@REM + +@REM Mistakenly deleted !!!!!! +@REM Init script templates for local build, local publish, script templates and other related functions + +@echo off + +@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 new file mode 100644 index 0000000000..40ab85e8d7 --- /dev/null +++ b/modules/server/src/main/resources/config_default/exec/template.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# Mistakenly deleted !!!!!!!!!!! +# Init script templates for local build, local publish, script 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/config_default/import-repo-provider/gitea.yml b/modules/server/src/main/resources/config_default/import-repo-provider/gitea.yml new file mode 100644 index 0000000000..81faa5b1b0 --- /dev/null +++ b/modules/server/src/main/resources/config_default/import-repo-provider/gitea.yml @@ -0,0 +1,30 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +baseUrl: https://try.gitea.io +authType: 1 +authKey: Authorization +authValue: 'Bearer ${token}' +currentUserMethod: GET +currentUserUrl: /api/v1/user +userNamePath: login +repoListMethod: GET +repoListUrl: /api/v1/user/repos +repoListParam: + page: '${page}' + limit: '${pageSize}' +repoListPath: '' +repoConvertPath: + name: name + full_name: full_name + description: description + url: clone_url + private: private +repoTotalHeader: X-Total-Count diff --git a/modules/server/src/main/resources/config_default/import-repo-provider/gitee.yml b/modules/server/src/main/resources/config_default/import-repo-provider/gitee.yml new file mode 100644 index 0000000000..183073a0d9 --- /dev/null +++ b/modules/server/src/main/resources/config_default/import-repo-provider/gitee.yml @@ -0,0 +1,34 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +baseUrl: https://gitee.com +authType: 2 +authKey: access_token +authValue: '${token}' +currentUserMethod: GET +currentUserUrl: /api/v5/user +userNamePath: login +repoListMethod: GET +repoListUrl: /api/v5/user/repos +repoListParam: + type: all + sort: pushed + direction: desc + q: '${query}' + page: '${page}' + per_page: '${pageSize}' +repoListPath: '' +repoConvertPath: + name: name + full_name: full_name + description: description + url: html_url + private: private +repoTotalHeader: total_count diff --git a/modules/server/src/main/resources/config_default/import-repo-provider/github.yml b/modules/server/src/main/resources/config_default/import-repo-provider/github.yml new file mode 100644 index 0000000000..1bce42882b --- /dev/null +++ b/modules/server/src/main/resources/config_default/import-repo-provider/github.yml @@ -0,0 +1,37 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +baseUrl: https://api.github.com +authType: 1 +authKey: Authorization +authValue: 'Bearer ${token}' +extraParams: + Accept: application/vnd.github.v3+json + X-GitHub-Api-Version: 2022-11-28 +extraParamsType: 1 +currentUserMethod: GET +currentUserUrl: /user +userNamePath: login +repoListMethod: GET +repoListUrl: /user/repos +repoListParam: + type: all + sort: pushed + direction: desc + page: '${page}' + per_page: '${pageSize}' +repoListPath: '' +repoConvertPath: + name: name + full_name: full_name + description: description + url: clone_url + private: private +repoTotalHeader: Link diff --git a/modules/server/src/main/resources/config_default/import-repo-provider/gitlab.yml b/modules/server/src/main/resources/config_default/import-repo-provider/gitlab.yml new file mode 100644 index 0000000000..1763e5632a --- /dev/null +++ b/modules/server/src/main/resources/config_default/import-repo-provider/gitlab.yml @@ -0,0 +1,33 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +baseUrl: https://gitlab.com +authType: 1 +authKey: Authorization +authValue: 'Bearer ${token}' +currentUserMethod: GET +currentUserUrl: /api/v4/user +userNamePath: username +repoListMethod: GET +repoListUrl: /api/v4/projects +repoListParam: + membership: true + order_by: updated_at + search: '${query}' + page: '${page}' + per_page: '${pageSize}' +repoListPath: '' +repoConvertPath: + name: name + full_name: path_with_namespace + description: description + url: http_url_to_repo + private: $ visibility != public +repoTotalHeader: X-Total diff --git a/modules/server/src/main/resources/config_default/import-repo-provider/gitlab_v3.yml b/modules/server/src/main/resources/config_default/import-repo-provider/gitlab_v3.yml new file mode 100644 index 0000000000..1174a1a3f6 --- /dev/null +++ b/modules/server/src/main/resources/config_default/import-repo-provider/gitlab_v3.yml @@ -0,0 +1,33 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +baseUrl: https://gitlab.com +authType: 1 +authKey: Authorization +authValue: 'Bearer ${token}' +currentUserMethod: GET +currentUserUrl: /api/v3/user +userNamePath: username +repoListMethod: GET +repoListUrl: /api/v3/projects +repoListParam: + membership: true + order_by: updated_at + search: '${query}' + page: '${page}' + per_page: '${pageSize}' +repoListPath: '' +repoConvertPath: + name: name + full_name: path_with_namespace + description: description + url: http_url_to_repo + private: $ visibility != public +repoTotalHeader: X-Total diff --git a/modules/server/src/main/resources/config_default/import-repo-provider/gogs.yml b/modules/server/src/main/resources/config_default/import-repo-provider/gogs.yml new file mode 100644 index 0000000000..e3e4720aba --- /dev/null +++ b/modules/server/src/main/resources/config_default/import-repo-provider/gogs.yml @@ -0,0 +1,30 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +baseUrl: https://try.gogs.io +authType: 1 +authKey: Authorization +authValue: 'token ${token}' +currentUserMethod: GET +currentUserUrl: /api/v1/user +userNamePath: login +repoListMethod: GET +repoListUrl: /api/v1/user/repos +repoListParam: + page: '${page}' + limit: '${pageSize}' +repoListPath: '' +repoConvertPath: + name: name + full_name: full_name + description: description + url: clone_url + private: private +repoTotalHeader: X-Total-Count diff --git a/modules/server/src/main/resources/config_default/logback.xml b/modules/server/src/main/resources/config_default/logback.xml new file mode 100644 index 0000000000..2227c391a0 --- /dev/null +++ b/modules/server/src/main/resources/config_default/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + destination + server + + + + ${logPath}/${destination}.log + + + ${logPath}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log + + + 512MB + + 60 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/server/src/main/resources/config_default/ssh/monitor-script.sh b/modules/server/src/main/resources/config_default/ssh/monitor-script.sh new file mode 100644 index 0000000000..c92a683875 --- /dev/null +++ b/modules/server/src/main/resources/config_default/ssh/monitor-script.sh @@ -0,0 +1,67 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +function getPid() { + cygwin=false + linux=false + case "$(uname)" in + CYGWIN*) + cygwin=true + ;; + Linux*) + linux=true + ;; + esac + if $cygwin; then + JAVA_CMD="$JAVA_HOME\bin\java" + JAVA_CMD=$(cygpath --path --unix "$JAVA_CMD") + JAVA_PID=$(ps | grep "$JAVA_CMD" | awk '{print $1}') + else + if $linux; then + JAVA_PID=$(ps -C java -f --width 1000 | grep "$1" | grep -v grep | awk '{print $2}') + else + JAVA_PID=$(ps aux | grep "$1" | grep -v grep | awk '{print $2}') + fi + fi + echo "$JAVA_PID" +} +if [ ! -f "/usr/bin/lsb_release" ]; then + echo "os name:$(cat /etc/redhat-release)" +else + echo "os name:$(/usr/bin/lsb_release -a | grep Description | awk -F : '{print $2}' | sed 's/^[ \t]*//g')" +fi + +echo "os version:$(uname -r)" +echo "hostname:$(hostname)" +# 启动时间 +echo "uptime:$(uptime -s)" +# 系统负载 +uptime | sed 's/,/ /g' | awk '{for(i=NF-2;i<=NF;i++) print "load average:"$i }' +# cpu 型号 +LANG=C lscpu | awk -F: '/Model name/ {print$2}' | awk '$1=$1' | awk '{print "model name:"$0}' +# cpu 数量 +awk '/processor/{core++} END{print "cpu core:"core}' /proc/cpuinfo +# 内存信息 +free -k | grep -iv swap | awk -F ':' '{print $NF}' | awk -F ' ' '{for(i=1;i<=NF;i++) a[i,NR]=$i}END{for(i=1;i<=NF;i++) { printf "memory "; for(j=1;j<=NR;j++) printf a[i,j] ":";print ""}}' +# cpu 利用率 +export TERM=xterm +top -b -n 1 -d.2 | grep -i 'cpu' | awk '{for(i=NR;i<=1;i++)print "cpu usage:"$2 }' +# disk +df -k | awk 'NR>1' | awk '/^\/dev/{print "disk info:"$1":"$2":"$3}' +# jpom 相关 +jpom_agent_pid=$(getPid "${JPOM_AGENT_PID_TAG}") +echo "jpom agent pid:$jpom_agent_pid" +echo "java version:$(command -v java)" + +docker_path=$(command -v docker) +if [[ $docker_path != "" ]]; then + echo "docker path:$docker_path" + echo "docker version:$(docker -v)" +fi 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 0000000000..83dfbabc27 --- /dev/null +++ b/modules/server/src/main/resources/config_default/ssh/template.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# 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/fast-install/en-US/beta.json b/modules/server/src/main/resources/fast-install/en-US/beta.json new file mode 100644 index 0000000000..c27272c137 --- /dev/null +++ b/modules/server/src/main/resources/fast-install/en-US/beta.json @@ -0,0 +1,18 @@ +[ + { + "name": "Primary address (default installation)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "Alternative address (default installation)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "Primary address (custom installation)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk+beta" + }, + { + "name": "Alternative address (custom installation)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk+beta" + } +] diff --git a/modules/server/src/main/resources/fast-install/en-US/release.json b/modules/server/src/main/resources/fast-install/en-US/release.json new file mode 100644 index 0000000000..c690dda539 --- /dev/null +++ b/modules/server/src/main/resources/fast-install/en-US/release.json @@ -0,0 +1,18 @@ +[ + { + "name": "Primary address (default installation)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "Alternative address (default installation)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "Primary address (custom installation)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk" + }, + { + "name": "Alternative address (custom installation)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk" + } +] diff --git a/modules/server/src/main/resources/fast-install/zh-CN/beta.json b/modules/server/src/main/resources/fast-install/zh-CN/beta.json new file mode 100644 index 0000000000..4c0bed83c2 --- /dev/null +++ b/modules/server/src/main/resources/fast-install/zh-CN/beta.json @@ -0,0 +1,18 @@ +[ + { + "name": "主地址(默认安装)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "备用地址(默认安装)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "主地址(自定义安装)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk+beta" + }, + { + "name": "备用地址(自定义安装)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk+beta" + } +] diff --git a/modules/server/src/main/resources/fast-install/zh-CN/release.json b/modules/server/src/main/resources/fast-install/zh-CN/release.json new file mode 100644 index 0000000000..2c9f79a29d --- /dev/null +++ b/modules/server/src/main/resources/fast-install/zh-CN/release.json @@ -0,0 +1,18 @@ +[ + { + "name": "主地址(默认安装)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "备用地址(默认安装)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "主地址(自定义安装)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk" + }, + { + "name": "备用地址(自定义安装)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk" + } +] diff --git a/modules/server/src/main/resources/fast-install/zh-HK/beta.json b/modules/server/src/main/resources/fast-install/zh-HK/beta.json new file mode 100644 index 0000000000..19db410592 --- /dev/null +++ b/modules/server/src/main/resources/fast-install/zh-HK/beta.json @@ -0,0 +1,18 @@ +[ + { + "name": "主地址(默認安裝)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "備用地址(默認安裝)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "主地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk+beta" + }, + { + "name": "備用地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk+beta" + } +] diff --git a/modules/server/src/main/resources/fast-install/zh-HK/release.json b/modules/server/src/main/resources/fast-install/zh-HK/release.json new file mode 100644 index 0000000000..7d7d01ee22 --- /dev/null +++ b/modules/server/src/main/resources/fast-install/zh-HK/release.json @@ -0,0 +1,18 @@ +[ + { + "name": "主地址(默認安裝)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "備用地址(默認安裝)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "主地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk" + }, + { + "name": "備用地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk" + } +] diff --git a/modules/server/src/main/resources/fast-install/zh-TW/beta.json b/modules/server/src/main/resources/fast-install/zh-TW/beta.json new file mode 100644 index 0000000000..c845426666 --- /dev/null +++ b/modules/server/src/main/resources/fast-install/zh-TW/beta.json @@ -0,0 +1,18 @@ +[ + { + "name": "主地址(預設安裝)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "備用地址(預設安裝)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default+beta" + }, + { + "name": "主地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk+beta" + }, + { + "name": "備用地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk+beta" + } +] diff --git a/modules/server/src/main/resources/fast-install/zh-TW/release.json b/modules/server/src/main/resources/fast-install/zh-TW/release.json new file mode 100644 index 0000000000..0e06bd9eab --- /dev/null +++ b/modules/server/src/main/resources/fast-install/zh-TW/release.json @@ -0,0 +1,18 @@ +[ + { + "name": "主地址(預設安裝)", + "url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "備用地址(預設安裝)", + "url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default" + }, + { + "name": "主地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk" + }, + { + "name": "備用地址(自定義安裝)", + "url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk" + } +] diff --git a/modules/server/src/main/resources/logback.xml b/modules/server/src/main/resources/logback.xml new file mode 100644 index 0000000000..f480f175de --- /dev/null +++ b/modules/server/src/main/resources/logback.xml @@ -0,0 +1,84 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%highlight(%thread)] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + destination + server + + + + ${logPath}/${destination}.log + + + ${logPath}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log + + + 512MB + + 60 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%highlight(%thread)] %-5level %logger{10} [%file:%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/server/src/main/resources/logo/favicon.ico b/modules/server/src/main/resources/logo/favicon.ico new file mode 100644 index 0000000000..cd5c496350 Binary files /dev/null and b/modules/server/src/main/resources/logo/favicon.ico differ diff --git a/modules/server/src/main/resources/logo/jpom.jpeg b/modules/server/src/main/resources/logo/jpom.jpeg deleted file mode 100644 index 434f4d7f24..0000000000 Binary files a/modules/server/src/main/resources/logo/jpom.jpeg and /dev/null differ diff --git a/modules/server/src/main/resources/logo/jpom.png b/modules/server/src/main/resources/logo/jpom.png index cbe84faa17..e955d4f2af 100644 Binary files a/modules/server/src/main/resources/logo/jpom.png and b/modules/server/src/main/resources/logo/jpom.png differ diff --git a/modules/server/src/main/resources/menus/en-US/index.json b/modules/server/src/main/resources/menus/en-US/index.json new file mode 100644 index 0000000000..9398b8c185 --- /dev/null +++ b/modules/server/src/main/resources/menus/en-US/index.json @@ -0,0 +1,176 @@ +[ + { + "title": "Overview", + "icon_v3": "laptop", + "id": "overview" + }, + { + "title": "Node & Project", + "icon_v3": "apartment", + "id": "outgiving", + "childs": [ + { + "title": "Logical node", + "id": "nodeList" + }, + { + "title": "List of projects", + "id": "projectSearch" + }, + { + "id": "outgivingList", + "title": "Distribution List" + }, + { + "id": "outgivingLog", + "title": "Distribution log" + }, + { + "id": "outgivingWhitelistDirectory", + "title": "Authorization configuration", + "role": "system" + }, + { + "id": "logRead", + "title": "Log Search" + } + ] + }, + { + "title": "Online Construction", + "icon_v3": "build", + "id": "build", + "childs": [ + { + "id": "repository", + "title": "Warehouse information" + }, + { + "id": "buildList", + "title": "Build List" + }, + { + "id": "buildHistory", + "title": "Building History" + } + ] + }, + { + "title": "SSH Management", + "icon_v3": "code", + "id": "sshManager", + "childs": [ + { + "title": "SSH List", + "id": "sshList" + }, + { + "title": "Command Management", + "id": "commandList" + }, + { + "title": "Command execution record", + "id": "commandLogList" + } + ] + }, + { + "title": "Script Management", + "icon_v3": "file-text", + "id": "scriptManager", + "childs": [ + { + "title": "List of node scripts", + "id": "scriptAllList" + }, + { + "title": "List of scripts", + "id": "serverScriptList" + }, + { + "title": "Script execution record", + "id": "serverScriptLogList" + }, + { + "title": "environement variable", + "id": "configWorkspaceEnv" + } + ] + }, + { + "title": "Docker Management", + "icon_v3": "cloud-server", + "id": "dockerManager", + "childs": [ + { + "title": "List of Docker", + "id": "dockerList" + }, + { + "title": "Cluster List", + "id": "dockerSwarm" + } + ] + }, + { + "title": "Monitoring and management", + "icon_v3": "monitor", + "id": "monitor", + "childs": [ + { + "id": "monitorList", + "title": "Watch list" + }, + { + "id": "monitorLog", + "title": "Monitor log" + }, + { + "id": "userOptLog", + "title": "Operation monitoring" + } + ] + }, + { + "title": "Document Management", + "icon_v3": "file", + "id": "file-manager", + "childs": [ + { + "id": "fileStorage", + "title": "List of files" + }, + { + "id": "staticFileStorage", + "title": "Static file" + }, + { + "id": "fileReleaseTask", + "title": "Publish task" + } + ] + }, + { + "title": "Other Management", + "icon_v3": "tool", + "id": "tools", + "childs": [ + { + "id": "certificate", + "title": "Certificate Management" + }, + { + "id": "myWorkspaceList", + "title": "My working space" + }, + { + "id": "netTools", + "title": "network tools" + }, + { + "id": "cronTools", + "title": "Cron expression" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/en-US/system.json b/modules/server/src/main/resources/menus/en-US/system.json new file mode 100644 index 0000000000..5d1df46cf0 --- /dev/null +++ b/modules/server/src/main/resources/menus/en-US/system.json @@ -0,0 +1,135 @@ +[ + { + "title": "Asset Overview", + "icon_v3": "laptop", + "id": "system-overview" + }, + { + "title": "Asset Management", + "icon_v3": "hdd", + "id": "assets-manager", + "childs": [ + { + "id": "machine_node_info", + "title": "Machine Management", + "role": "system" + }, + { + "id": "machine_ssh_info", + "title": "SSH Management", + "role": "system" + }, + { + "id": "machine_docker_info", + "title": "Docker Management", + "role": "system" + }, + { + "id": "global_repository", + "title": "Shared warehouse", + "role": "system" + }, + { + "id": "script_library", + "title": "Script Library", + "role": "system" + } + ] + }, + { + "title": "Configuration Management", + "icon_v3": "save", + "id": "config", + "childs": [ + { + "id": "authConfig", + "title": "Certification Management", + "role": "system" + }, + { + "id": "monitorConfigEmail", + "title": "Mailbox configuration", + "role": "system" + }, + { + "id": "systemExtConfig", + "title": "System configuration directory", + "role": "system" + } + ] + }, + { + "title": "User Management", + "icon_v3": "user", + "id": "user", + "childs": [ + { + "id": "userList", + "title": "User List", + "role": "system" + }, + { + "id": "permission_group", + "title": "permission group", + "role": "system" + }, + { + "id": "user_log", + "title": "operation log", + "role": "system" + }, + { + "id": "user_login_log", + "title": "Login log", + "role": "system" + } + ] + }, + { + "title": "Server Level Configuration", + "icon_v3": "setting", + "id": "setting", + "childs": [ + { + "id": "workspace", + "title": "Workspaces & Clusters", + "role": "system" + }, + { + "id": "globalEnv", + "title": "Global Variable", + "role": "system" + }, + { + "id": "cacheManage", + "title": "Cache Management", + "role": "system" + }, + { + "id": "logManage", + "title": "System log", + "role": "system" + }, + { + "id": "update", + "title": "Online upgrade", + "role": "sys" + }, + { + "id": "sysConfig", + "title": "System Configuration", + "role": "system" + }, + { + "id": "backup", + "title": "Database Backup", + "role": "system", + "storageMode": "H2" + }, + { + "id": "about", + "title": "About the System" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/index.json b/modules/server/src/main/resources/menus/index.json deleted file mode 100644 index 2ce2656cf5..0000000000 --- a/modules/server/src/main/resources/menus/index.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "title": "节点管理", - "icon_v3": "block", - "id": "nodeManager", - "childs": [ - { - "title": "节点列表", - "id": "nodeList" - }, - { - "title": "节点升级", - "id": "nodeUpdate", - "role": "sys" - }, - { - "title": "项目搜索", - "id": "projectSearch" - }, - { - "title": "SSH管理", - "id": "sshList" - } - ] - }, - { - "title": "节点分发", - "icon_v3": "apartment", - "childs": [ - { - "id": "outgiving", - "title": "分发列表" - }, - { - "id": "outgivingLog", - "title": "分发日志" - }, - { - "id": "outgivingWhitelistDirectory", - "title": "白名单配置" - } - ] - }, - { - "title": "监控管理", - "icon_v3": "monitor", - "childs": [ - { - "id": "monitor", - "title": "监控列表" - }, - { - "id": "monitorLog", - "title": "监控日志" - }, - { - "id": "userOptLog", - "title": "操作监控" - } - ] - }, - { - "title": "在线构建", - "icon_v3": "build", - "childs": [ - { - "id": "repository", - "title": "仓库信息" - }, - { - "id": "buildList", - "title": "构建列表" - }, - { - "id": "buildHistory", - "title": "构建历史" - } - ] - }, - { - "title": "用户管理", - "icon_v3": "user", - "childs": [ - { - "id": "user", - "title": "用户列表", - "role": "system" - }, - { - "id": "user_log", - "title": "操作日志", - "role": "system" - } - ] - }, - { - "title": "系统管理", - "icon_v3": "setting", - "role": "sys", - "childs": [ - { - "id": "workspace", - "title": "工作空间", - "role": "system" - }, - { - "id": "monitorConfigEmail", - "title": "邮箱配置", - "role": "system" - }, - { - "id": "cacheManage", - "title": "缓存管理", - "role": "system" - }, - { - "id": "logManage", - "title": "系统日志", - "role": "system" - }, - { - "id": "update", - "title": "在线升级", - "role": "sys" - }, - { - "id": "sysConfig", - "title": "系统配置", - "role": "system" - }, - { - "id": "backup", - "title": "数据库备份", - "role": "system" - } - ] - } -] 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 92300db663..0000000000 --- a/modules/server/src/main/resources/menus/node-index.json +++ /dev/null @@ -1,90 +0,0 @@ -[ - { - "id": "welcome", - "title": "首页", - "icon_v3": "dashboard" - }, - { - "id": "manage", - "title": "项目管理", - "icon_v3": "project", - "childs": [ - { - "id": "manageList", - "title": "项目列表" - }, - { - "id": "jdkList", - "title": "JDK管理", - "role": "system" - } - ] - }, - { - "id": "otherM", - "title": "其他功能", - "icon_v3": "more", - "childs": [ - { - "id": "tomcatManage", - "title": "Tomcat管理", - "role": "system" - }, - { - "id": "script", - "title": "脚本模板", - "role": "system" - } - ] - }, - { - "id": "nginx", - "title": "nginx管理", - "icon_v3": "gateway", - "childs": [ - { - "id": "nginxList", - "title": "nginx列表", - "role": "system" - }, - { - "id": "certificate", - "title": "证书管理", - "role": "system" - } - ] - }, - { - "id": "systemConfig", - "title": "系统管理", - "icon_v3": "setting", - "role": "sys", - "childs": [ - { - "id": "whitelistDirectory", - "title": "白名单配置", - "role": "system" - }, - { - "id": "cacheManage", - "title": "缓存管理", - "role": "system" - }, - { - "id": "logManage", - "title": "系统日志", - "role": "system" - }, - { - "id": "update", - "title": "在线升级", - "role": "sys" - }, - { - "id": "sysConfig", - "title": "系统配置", - "role": "system" - } - ] - } -] diff --git a/modules/server/src/main/resources/menus/zh-CN/index.json b/modules/server/src/main/resources/menus/zh-CN/index.json new file mode 100644 index 0000000000..6815637250 --- /dev/null +++ b/modules/server/src/main/resources/menus/zh-CN/index.json @@ -0,0 +1,176 @@ +[ + { + "title": "概括总览", + "icon_v3": "laptop", + "id": "overview" + }, + { + "title": "节点&项目", + "icon_v3": "apartment", + "id": "outgiving", + "childs": [ + { + "title": "逻辑节点", + "id": "nodeList" + }, + { + "title": "项目列表", + "id": "projectSearch" + }, + { + "id": "outgivingList", + "title": "分发列表" + }, + { + "id": "outgivingLog", + "title": "分发日志" + }, + { + "id": "outgivingWhitelistDirectory", + "title": "授权配置", + "role": "system" + }, + { + "id": "logRead", + "title": "日志搜索" + } + ] + }, + { + "title": "在线构建", + "icon_v3": "build", + "id": "build", + "childs": [ + { + "id": "repository", + "title": "仓库信息" + }, + { + "id": "buildList", + "title": "构建列表" + }, + { + "id": "buildHistory", + "title": "构建历史" + } + ] + }, + { + "title": "SSH管理", + "icon_v3": "code", + "id": "sshManager", + "childs": [ + { + "title": "SSH列表", + "id": "sshList" + }, + { + "title": "命令管理", + "id": "commandList" + }, + { + "title": "命令执行记录", + "id": "commandLogList" + } + ] + }, + { + "title": "脚本管理", + "icon_v3": "file-text", + "id": "scriptManager", + "childs": [ + { + "title": "节点脚本列表", + "id": "scriptAllList" + }, + { + "title": "脚本列表", + "id": "serverScriptList" + }, + { + "title": "脚本执行记录", + "id": "serverScriptLogList" + }, + { + "title": "环境变量", + "id": "configWorkspaceEnv" + } + ] + }, + { + "title": "Docker管理", + "icon_v3": "cloud-server", + "id": "dockerManager", + "childs": [ + { + "title": "Docker列表", + "id": "dockerList" + }, + { + "title": "集群列表", + "id": "dockerSwarm" + } + ] + }, + { + "title": "监控管理", + "icon_v3": "monitor", + "id": "monitor", + "childs": [ + { + "id": "monitorList", + "title": "监控列表" + }, + { + "id": "monitorLog", + "title": "监控日志" + }, + { + "id": "userOptLog", + "title": "操作监控" + } + ] + }, + { + "title": "文件管理", + "icon_v3": "file", + "id": "file-manager", + "childs": [ + { + "id": "fileStorage", + "title": "文件列表" + }, + { + "id": "staticFileStorage", + "title": "静态文件" + }, + { + "id": "fileReleaseTask", + "title": "发布任务" + } + ] + }, + { + "title": "其他管理", + "icon_v3": "tool", + "id": "tools", + "childs": [ + { + "id": "certificate", + "title": "证书管理" + }, + { + "id": "myWorkspaceList", + "title": "我的工作空间" + }, + { + "id": "netTools", + "title": "网络工具" + }, + { + "id": "cronTools", + "title": "Cron表达式" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/zh-CN/system.json b/modules/server/src/main/resources/menus/zh-CN/system.json new file mode 100644 index 0000000000..69776e5ac5 --- /dev/null +++ b/modules/server/src/main/resources/menus/zh-CN/system.json @@ -0,0 +1,135 @@ +[ + { + "title": "资产总览", + "icon_v3": "laptop", + "id": "system-overview" + }, + { + "title": "资产管理", + "icon_v3": "hdd", + "id": "assets-manager", + "childs": [ + { + "id": "machine_node_info", + "title": "机器管理", + "role": "system" + }, + { + "id": "machine_ssh_info", + "title": "SSH管理", + "role": "system" + }, + { + "id": "machine_docker_info", + "title": "Docker管理", + "role": "system" + }, + { + "id": "global_repository", + "title": "共享仓库", + "role": "system" + }, + { + "id": "script_library", + "title": "脚本库", + "role": "system" + } + ] + }, + { + "title": "配置管理", + "icon_v3": "save", + "id": "config", + "childs": [ + { + "id": "authConfig", + "title": "认证管理", + "role": "system" + }, + { + "id": "monitorConfigEmail", + "title": "邮箱配置", + "role": "system" + }, + { + "id": "systemExtConfig", + "title": "系统配置目录", + "role": "system" + } + ] + }, + { + "title": "用户管理", + "icon_v3": "user", + "id": "user", + "childs": [ + { + "id": "userList", + "title": "用户列表", + "role": "system" + }, + { + "id": "permission_group", + "title": "权限组", + "role": "system" + }, + { + "id": "user_log", + "title": "操作日志", + "role": "system" + }, + { + "id": "user_login_log", + "title": "登录日志", + "role": "system" + } + ] + }, + { + "title": "服务端配置", + "icon_v3": "setting", + "id": "setting", + "childs": [ + { + "id": "workspace", + "title": "工作空间&集群", + "role": "system" + }, + { + "id": "globalEnv", + "title": "全局变量", + "role": "system" + }, + { + "id": "cacheManage", + "title": "缓存管理", + "role": "system" + }, + { + "id": "logManage", + "title": "系统日志", + "role": "system" + }, + { + "id": "update", + "title": "在线升级", + "role": "sys" + }, + { + "id": "sysConfig", + "title": "系统配置", + "role": "system" + }, + { + "id": "backup", + "title": "数据库备份", + "role": "system", + "storageMode": "H2" + }, + { + "id": "about", + "title": "关于系统" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/zh-HK/index.json b/modules/server/src/main/resources/menus/zh-HK/index.json new file mode 100644 index 0000000000..47b851137c --- /dev/null +++ b/modules/server/src/main/resources/menus/zh-HK/index.json @@ -0,0 +1,176 @@ +[ + { + "title": "概括總覽", + "icon_v3": "laptop", + "id": "overview" + }, + { + "title": "節點&項目", + "icon_v3": "apartment", + "id": "outgiving", + "childs": [ + { + "title": "邏輯節點", + "id": "nodeList" + }, + { + "title": "項目列表", + "id": "projectSearch" + }, + { + "id": "outgivingList", + "title": "分發列表" + }, + { + "id": "outgivingLog", + "title": "分發日誌" + }, + { + "id": "outgivingWhitelistDirectory", + "title": "授權配置", + "role": "system" + }, + { + "id": "logRead", + "title": "日誌搜索" + } + ] + }, + { + "title": "在線構建", + "icon_v3": "build", + "id": "build", + "childs": [ + { + "id": "repository", + "title": "倉庫信息" + }, + { + "id": "buildList", + "title": "構建列表" + }, + { + "id": "buildHistory", + "title": "構建歷史" + } + ] + }, + { + "title": "SSH管理", + "icon_v3": "code", + "id": "sshManager", + "childs": [ + { + "title": "SSH列表", + "id": "sshList" + }, + { + "title": "命令管理", + "id": "commandList" + }, + { + "title": "命令執行記錄", + "id": "commandLogList" + } + ] + }, + { + "title": "腳本管理", + "icon_v3": "file-text", + "id": "scriptManager", + "childs": [ + { + "title": "節點腳本列表", + "id": "scriptAllList" + }, + { + "title": "腳本列表", + "id": "serverScriptList" + }, + { + "title": "腳本執行記錄", + "id": "serverScriptLogList" + }, + { + "title": "環境變量", + "id": "configWorkspaceEnv" + } + ] + }, + { + "title": "Docker管理", + "icon_v3": "cloud-server", + "id": "dockerManager", + "childs": [ + { + "title": "Docker列表", + "id": "dockerList" + }, + { + "title": "集羣列表", + "id": "dockerSwarm" + } + ] + }, + { + "title": "監控管理", + "icon_v3": "monitor", + "id": "monitor", + "childs": [ + { + "id": "monitorList", + "title": "監控列表" + }, + { + "id": "monitorLog", + "title": "監控日誌" + }, + { + "id": "userOptLog", + "title": "操作監控" + } + ] + }, + { + "title": "文件管理", + "icon_v3": "file", + "id": "file-manager", + "childs": [ + { + "id": "fileStorage", + "title": "文件列表" + }, + { + "id": "staticFileStorage", + "title": "靜態文件" + }, + { + "id": "fileReleaseTask", + "title": "發佈任務" + } + ] + }, + { + "title": "其他管理", + "icon_v3": "tool", + "id": "tools", + "childs": [ + { + "id": "certificate", + "title": "證書管理" + }, + { + "id": "myWorkspaceList", + "title": "我的工作空間" + }, + { + "id": "netTools", + "title": "網絡工具" + }, + { + "id": "cronTools", + "title": "Cron表達式" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/zh-HK/system.json b/modules/server/src/main/resources/menus/zh-HK/system.json new file mode 100644 index 0000000000..c8c8bc4215 --- /dev/null +++ b/modules/server/src/main/resources/menus/zh-HK/system.json @@ -0,0 +1,135 @@ +[ + { + "title": "資產總覽", + "icon_v3": "laptop", + "id": "system-overview" + }, + { + "title": "資產管理", + "icon_v3": "hdd", + "id": "assets-manager", + "childs": [ + { + "id": "machine_node_info", + "title": "機器管理", + "role": "system" + }, + { + "id": "machine_ssh_info", + "title": "SSH管理", + "role": "system" + }, + { + "id": "machine_docker_info", + "title": "Docker管理", + "role": "system" + }, + { + "id": "global_repository", + "title": "共享倉庫", + "role": "system" + }, + { + "id": "script_library", + "title": "腳本庫", + "role": "system" + } + ] + }, + { + "title": "配置管理", + "icon_v3": "save", + "id": "config", + "childs": [ + { + "id": "authConfig", + "title": "認證管理", + "role": "system" + }, + { + "id": "monitorConfigEmail", + "title": "郵箱配置", + "role": "system" + }, + { + "id": "systemExtConfig", + "title": "系統配置目錄", + "role": "system" + } + ] + }, + { + "title": "用户管理", + "icon_v3": "user", + "id": "user", + "childs": [ + { + "id": "userList", + "title": "用户列表", + "role": "system" + }, + { + "id": "permission_group", + "title": "權限組", + "role": "system" + }, + { + "id": "user_log", + "title": "操作日誌", + "role": "system" + }, + { + "id": "user_login_log", + "title": "登錄日誌", + "role": "system" + } + ] + }, + { + "title": "服務端配置", + "icon_v3": "setting", + "id": "setting", + "childs": [ + { + "id": "workspace", + "title": "工作空間&集羣", + "role": "system" + }, + { + "id": "globalEnv", + "title": "全局變量", + "role": "system" + }, + { + "id": "cacheManage", + "title": "緩存管理", + "role": "system" + }, + { + "id": "logManage", + "title": "系統日誌", + "role": "system" + }, + { + "id": "update", + "title": "在線升級", + "role": "sys" + }, + { + "id": "sysConfig", + "title": "系統配置", + "role": "system" + }, + { + "id": "backup", + "title": "數據庫備份", + "role": "system", + "storageMode": "H2" + }, + { + "id": "about", + "title": "關於系統" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/zh-TW/index.json b/modules/server/src/main/resources/menus/zh-TW/index.json new file mode 100644 index 0000000000..a3c77e0c12 --- /dev/null +++ b/modules/server/src/main/resources/menus/zh-TW/index.json @@ -0,0 +1,176 @@ +[ + { + "title": "概括總覽", + "icon_v3": "laptop", + "id": "overview" + }, + { + "title": "節點&專案", + "icon_v3": "apartment", + "id": "outgiving", + "childs": [ + { + "title": "邏輯節點", + "id": "nodeList" + }, + { + "title": "專案列表", + "id": "projectSearch" + }, + { + "id": "outgivingList", + "title": "分發列表" + }, + { + "id": "outgivingLog", + "title": "分發日誌" + }, + { + "id": "outgivingWhitelistDirectory", + "title": "授權配置", + "role": "system" + }, + { + "id": "logRead", + "title": "日誌搜尋" + } + ] + }, + { + "title": "線上構建", + "icon_v3": "build", + "id": "build", + "childs": [ + { + "id": "repository", + "title": "倉庫資訊" + }, + { + "id": "buildList", + "title": "構建列表" + }, + { + "id": "buildHistory", + "title": "構建歷史" + } + ] + }, + { + "title": "SSH管理", + "icon_v3": "code", + "id": "sshManager", + "childs": [ + { + "title": "SSH列表", + "id": "sshList" + }, + { + "title": "命令管理", + "id": "commandList" + }, + { + "title": "命令執行記錄", + "id": "commandLogList" + } + ] + }, + { + "title": "指令碼管理", + "icon_v3": "file-text", + "id": "scriptManager", + "childs": [ + { + "title": "節點指令碼列表", + "id": "scriptAllList" + }, + { + "title": "指令碼列表", + "id": "serverScriptList" + }, + { + "title": "指令碼執行記錄", + "id": "serverScriptLogList" + }, + { + "title": "環境變數", + "id": "configWorkspaceEnv" + } + ] + }, + { + "title": "Docker管理", + "icon_v3": "cloud-server", + "id": "dockerManager", + "childs": [ + { + "title": "Docker列表", + "id": "dockerList" + }, + { + "title": "叢集列表", + "id": "dockerSwarm" + } + ] + }, + { + "title": "監控管理", + "icon_v3": "monitor", + "id": "monitor", + "childs": [ + { + "id": "monitorList", + "title": "監控列表" + }, + { + "id": "monitorLog", + "title": "監控日誌" + }, + { + "id": "userOptLog", + "title": "操作監控" + } + ] + }, + { + "title": "檔案管理", + "icon_v3": "file", + "id": "file-manager", + "childs": [ + { + "id": "fileStorage", + "title": "檔案列表" + }, + { + "id": "staticFileStorage", + "title": "靜態檔案" + }, + { + "id": "fileReleaseTask", + "title": "釋出任務" + } + ] + }, + { + "title": "其他管理", + "icon_v3": "tool", + "id": "tools", + "childs": [ + { + "id": "certificate", + "title": "證書管理" + }, + { + "id": "myWorkspaceList", + "title": "我的工作空間" + }, + { + "id": "netTools", + "title": "網路工具" + }, + { + "id": "cronTools", + "title": "Cron表示式" + } + ] + } +] diff --git a/modules/server/src/main/resources/menus/zh-TW/system.json b/modules/server/src/main/resources/menus/zh-TW/system.json new file mode 100644 index 0000000000..d81227365d --- /dev/null +++ b/modules/server/src/main/resources/menus/zh-TW/system.json @@ -0,0 +1,135 @@ +[ + { + "title": "資產總覽", + "icon_v3": "laptop", + "id": "system-overview" + }, + { + "title": "資產管理", + "icon_v3": "hdd", + "id": "assets-manager", + "childs": [ + { + "id": "machine_node_info", + "title": "機器管理", + "role": "system" + }, + { + "id": "machine_ssh_info", + "title": "SSH管理", + "role": "system" + }, + { + "id": "machine_docker_info", + "title": "Docker管理", + "role": "system" + }, + { + "id": "global_repository", + "title": "共享倉庫", + "role": "system" + }, + { + "id": "script_library", + "title": "指令碼庫", + "role": "system" + } + ] + }, + { + "title": "配置管理", + "icon_v3": "save", + "id": "config", + "childs": [ + { + "id": "authConfig", + "title": "認證管理", + "role": "system" + }, + { + "id": "monitorConfigEmail", + "title": "郵箱配置", + "role": "system" + }, + { + "id": "systemExtConfig", + "title": "系統配置目錄", + "role": "system" + } + ] + }, + { + "title": "使用者管理", + "icon_v3": "user", + "id": "user", + "childs": [ + { + "id": "userList", + "title": "使用者列表", + "role": "system" + }, + { + "id": "permission_group", + "title": "許可權組", + "role": "system" + }, + { + "id": "user_log", + "title": "操作日誌", + "role": "system" + }, + { + "id": "user_login_log", + "title": "登入日誌", + "role": "system" + } + ] + }, + { + "title": "服務端配置", + "icon_v3": "setting", + "id": "setting", + "childs": [ + { + "id": "workspace", + "title": "工作空間&叢集", + "role": "system" + }, + { + "id": "globalEnv", + "title": "全域性變數", + "role": "system" + }, + { + "id": "cacheManage", + "title": "快取管理", + "role": "system" + }, + { + "id": "logManage", + "title": "系統日誌", + "role": "system" + }, + { + "id": "update", + "title": "線上升級", + "role": "sys" + }, + { + "id": "sysConfig", + "title": "系統配置", + "role": "system" + }, + { + "id": "backup", + "title": "資料庫備份", + "role": "system", + "storageMode": "H2" + }, + { + "id": "about", + "title": "關於系統" + } + ] + } +] diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv new file mode 100644 index 0000000000..2fe0c33e75 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv @@ -0,0 +1,20 @@ +alterType,tableName,name,type,len,defaultValue,comment,notNull +DROP,NODE_INFO,cycle +ADD,PROJECT_INFO,triggerToken,String,100,,触发器token +ADD,OUT_GIVING,secondaryDirectory,String,200,,二级目录 +ADD,OUT_GIVING,uploadCloseFirst,TINYINT,,0,是否清空旧包发布 +ADD,USEROPERATELOGV1,dataName,String,200,,数据名称 +DROP,SSHTERMINALEXECUTELOG,optTime +DROP,USEROPERATELOGV1,optType +DROP,USEROPERATELOGV1,reqTime +DROP,BUILDHISTORYLOG,releaseMethodDataId +DROP,BUILDHISTORYLOG,afterOpt +DROP,BUILDHISTORYLOG,clearOld +DROP,BUILDHISTORYLOG,releaseCommand +DROP,BUILDHISTORYLOG,releasePath +DROP,BUILDHISTORYLOG,diffSync +ADD,OUT_GIVING,statusMsg,String,255,,分发状态信息 +ADD,OUTGIVINGLOG,fileSize,Long,,,文件大小 +ADD,OUTGIVINGLOG,progressSize,Long,,,当前进度 +ADD,MONITORNOTIFYLOG,modifyUser,String,50,,操作人, +DROP,MONITORNOTIFYLOG,logId diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv new file mode 100644 index 0000000000..7fd2c5d6b8 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv @@ -0,0 +1,31 @@ +alterType,tableName,name,type,len,defaultValue,comment,notNull +ADD,PROJECT_INFO,group,String,50,,项目分组 +ADD,OUT_GIVING,group,String,50,,项目分组 +ADD,BUILD_INFO,buildEnvParameter,TEXT,,,构建环境变量 +ADD,BUILDHISTORYLOG,buildEnvCache,TEXT,,,构建环境变量 +ADD,OUT_GIVING,webhook,String,255,,webhook +DROP,NODE_INFO,unLockType +ADD,NODE_INFO,machineId,String,50,,机器id +ADD,MACHINE_NODE_INFO,templateNode,TINYINT,,,模板节点 ,1 模板节点 0 非模板节点 +ADD,REPOSITORY,timeout,Integer,,,仓库超时连接 +ADD,SSH_INFO,group,String,50,,分组 +ADD,SSH_INFO,machineSshId,String,50,,机器sshid +ADD,SSHTERMINALEXECUTELOG,machineSshId,String,50,,机器sshid +ADD,SSHTERMINALEXECUTELOG,machineSshName,String,50,,机器ssh 名称 +ADD,MACHINE_SSH_INFO,allowEditSuffix,TEXT,,,允许编辑的后缀文件 +ADD,BUILDHISTORYLOG,resultFileSize,Long,,,产物文件大小 +ADD,BUILDHISTORYLOG,buildLogFileSize,Long,,,构建日志文件大小 +DROP-TABLE,NODE_STAT, +DROP-TABLE,SYSTEMMONITORLOG, +ADD,MACHINE_NODE_INFO,installId,String,50,,机器安装 id +ADD,MACHINE_NODE_STAT_LOG,occupySwapMemory,Double,,,交互内存 +ADD,MACHINE_NODE_STAT_LOG,occupyVirtualMemory,Double,,,虚拟内存 +ADD,MACHINE_NODE_INFO,osSwapTotal,Long,,,虚拟总内存 +ADD,MACHINE_NODE_INFO,osVirtualMax,Long,,,交互总内存 +ADD,DOCKER_INFO,machineDockerId,String,50,,机器id +DROP,DOCKER_SWARM_INFO,machineDockerId, +DROP,DOCKER_SWARM_INFO,status, +DROP,DOCKER_SWARM_INFO,failureMsg, +DROP,DOCKER_SWARM_INFO,dockerId, +DROP,DOCKER_SWARM_INFO,dockerId, +ADD,MACHINE_DOCKER_INFO,swarmControlAvailable,TINYINT,,,集群管理员 diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.2.1.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.2.1.csv new file mode 100644 index 0000000000..41fe14595d --- /dev/null +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.2.1.csv @@ -0,0 +1,2 @@ +alterType,tableName,name,type,len,defaultValue,comment,notNull +ADD,CLUSTER_INFO,statusMsg,TEXT,,,状态消息 diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.2.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.2.csv new file mode 100644 index 0000000000..ef001128b5 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.2.csv @@ -0,0 +1,28 @@ +alterType,tableName,name,type,len,defaultValue,comment,notNull +DROP,SCRIPT_INFO,strike +DROP,SSH_INFO,strike +DROP,SCRIPT_EXECUTE_LOG,strike +DROP,MONITOR_INFO,strike +DROP,DOCKER_INFO,strike +DROP,WORKSPACE_ENV_VAR,strike +DROP,NODE_INFO,strike +DROP,WORKSPACE,strike +DROP,MONITOR_USER_OPT,strike +DROP,SYSTEM_PARAMETERS,strike +DROP,COMMAND_EXEC_LOG,strike +DROP,PROJECT_INFO,strike +DROP,DOCKER_SWARM_INFO,strike +DROP,SERVER_SCRIPT_INFO,strike +DROP,USER_INFO,strike +DROP,COMMAND_INFO,strike +DROP,OUT_GIVING,strike +DROP,REPOSITORY,strike +DROP,SERVER_SCRIPT_EXECUTE_LOG,strike +DROP,LOG_READ,strike +DROP,USEROPERATELOGV1,strike +DROP,SSHTERMINALEXECUTELOG,strike +DROP,BUILD_INFO,strike +DROP,OUTGIVINGLOG,strike +DROP,BUILDHISTORYLOG,strike +DROP,MONITORNOTIFYLOG,strike +DROP,MACHINE_NODE_INFO,strike diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv new file mode 100644 index 0000000000..98777189d6 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv @@ -0,0 +1,66 @@ +alterType,tableName,name,type,len,defaultValue,comment,notNull +ADD,MACHINE_DOCKER_INFO,swarmCreatedAt,Long,,,集群的创建时间 +ADD,MACHINE_DOCKER_INFO,swarmUpdatedAt,Long,,,集群的更新时间 +ADD,MACHINE_DOCKER_INFO,swarmNodeAddr,String,50,,节点 地址 +DROP,DOCKER_INFO,apiVersion +DROP,DOCKER_INFO,dockerVersion +DROP,DOCKER_SWARM_INFO,dockerId, +DROP,DOCKER_SWARM_INFO,swarmCreatedAt, +DROP,DOCKER_SWARM_INFO,swarmUpdatedAt, +DROP,DOCKER_SWARM_INFO,nodeAddr, +DROP,DOCKER_SWARM_INFO,status, +DROP,DOCKER_SWARM_INFO,failureMsg, +DROP,DOCKER_INFO,status +DROP,DOCKER_INFO,failureMsg +DROP,USEROPERATELOGV1,reqId +ADD,USEROPERATELOGV1,workspaceName,String,50,,工作空间名 +ADD,USEROPERATELOGV1,username,String,50,,用户名 +ADD,MACHINE_NODE_INFO,transportEncryption,TINYINT,,,传输加密方式 0 不加密 1 BASE64 2 AES +DROP,COMMAND_INFO,type, +ADD,MACHINE_SSH_INFO,osName,String,50,,系统名称,false +ADD,MACHINE_SSH_INFO,hostName,String,255,,机器主机名,false +ADD,MACHINE_SSH_INFO,osLoadAverage,String,100,,系统负载,false +ADD,MACHINE_SSH_INFO,osSystemUptime,Long,,,系统运行时间(自启动以来的时间),false +ADD,MACHINE_SSH_INFO,osVersion,String,255,,系统版本,false +ADD,MACHINE_SSH_INFO,osCpuCores,Integer,,,CPU数,false +ADD,MACHINE_SSH_INFO,osMoneyTotal,Long,,,总内存,false +ADD,MACHINE_SSH_INFO,osFileStoreTotal,Long,,,硬盘大小,false +ADD,MACHINE_SSH_INFO,osCpuIdentifierName,String,255,,CPU 型号,false +ADD,MACHINE_SSH_INFO,osOccupyCpu,Double,,,占用cpu,false +ADD,MACHINE_SSH_INFO,osOccupyMemory,Double,,,占用内存,false +ADD,MACHINE_SSH_INFO,osMaxOccupyDisk,Double,,,占用磁盘,false +ADD,MACHINE_SSH_INFO,osMaxOccupyDiskName,String,255,,占用磁盘 分区名,false +ADD,MACHINE_SSH_INFO,javaVersion,String,255,,java版本,false +ADD,MACHINE_SSH_INFO,jpomAgentPid,Integer,,,jpom agent进程号 +ADD,FILE_STORAGE,status,TINYINT,,,0 下载中 1 下载完成 3 下载异常,false +ADD,FILE_STORAGE,progressDesc,String,255,,进度描述,false +ADD,FILE_STORAGE,triggerToken,String,200,,触发器token,false +ALTER,FILE_STORAGE,name,String,255,,名称,true +ADD,BUILD_INFO,aliasCode,String,50,,别名码,false +ADD,FILE_STORAGE,aliasCode,String,50,,别名码,false +ADD,PROJECT_INFO,dslContent,TEXT,,,dslContent,false +DROP,PROJECT_INFO,jdkId, +ADD,PROJECT_INFO,autoStart,TINYINT,,,在启动,false +ADD,MACHINE_DOCKER_INFO,certInfo,String,100,,证书信息,false +ADD,CERTIFICATE_INFO,fingerprint,String,50,,证书指纹,false +ADD,MACHINE_DOCKER_INFO,certExist,TINYINT,,,证书是否存在,false +ADD,REPOSITORY,createUser,String,50,,创建人,false +ADD,SERVER_SCRIPT_INFO,createUser,String,50,,创建人,false +ADD,BUILD_INFO,statusMsg,TEXT,,,状态信息 +ADD,BUILDHISTORYLOG,statusMsg,TEXT,,,状态信息 +ADD,SCRIPT_INFO,createUser,String,50,,创建人,false +ADD,SCRIPT_INFO,nodeName,String,50,,节点名称,false +ADD,PROJECT_INFO,nodeName,String,50,,节点名称,false +ADD,SCRIPT_EXECUTE_LOG,nodeName,String,50,,节点名称,false +ADD,SCRIPT_INFO,workspaceName,String,50,,工作空间名称,false +ADD,PROJECT_INFO,workspaceName,String,50,,工作空间名称,false +ADD,SCRIPT_EXECUTE_LOG,workspaceName,String,50,,工作空间名称,false +ALTER,REPOSITORY,password,String,255,,登录密码,false +ADD,WORKSPACE,group,String,50,,分组,false +ADD,MACHINE_SSH_INFO,dockerInfo,String,255,,服务器中的 docker 信息,false +ADD,MACHINE_DOCKER_INFO,enableSsh,tinyint,0,,是否开启SSH连接,false +ADD,MACHINE_DOCKER_INFO,machineSshId,String,255,,SSH连接信息,false +ADD,BUILD_INFO,resultKeepDay,Integer,,,产物保留天数,false +ADD,REPOSITORY,group,String,50,,分组,false +ADD,WORKSPACE,clusterInfoId,String,50,,绑定集群id,false +ADD,BUILDHISTORYLOG,fromBuildNumberId,Integer,,,来自的构建编号,回滚时存在 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 new file mode 100644 index 0000000000..18e8664072 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.4.csv @@ -0,0 +1,20 @@ +alterType,tableName,name,type,len,defaultValue,comment,notNull +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脚本数, +ALTER,STATIC_FILE_STORAGE,parentAbsolutePath,String,300,,父级文件路径,false +ALTER,STATIC_FILE_STORAGE,absolutePath,String,300,,文件路径,false +ALTER,STATIC_FILE_STORAGE,name,String,100,,文件名,false +ADD,TRIGGER_TOKEN_LOG,triggerCount,Integer,,,触发次数 +ADD,USER_INFO,source,String,100,,账号来源 +ADD,MACHINE_NODE_INFO,extendInfo,TEXT,,,扩展信息 +ADD,BUILD_INFO,createUser,String,50,,创建人,false +ADD,SCRIPT_LIBRARY,createUser,String,50,,创建人,false 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 new file mode 100644 index 0000000000..7f74c35ce5 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/index.all.v1.0.csv @@ -0,0 +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.0.csv b/modules/server/src/main/resources/sql-view/table.all.v1.0.csv new file mode 100644 index 0000000000..6091d091e5 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/table.all.v1.0.csv @@ -0,0 +1,370 @@ +tableName,name,type,len,defaultValue,notNull,primaryKey,comment,tableComment +SCRIPT_INFO,id,String,50,,true,true,id,节点脚本模版 +SCRIPT_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +SCRIPT_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +SCRIPT_INFO,modifyUser,String,50,,false,false,修改人, +SCRIPT_INFO,workspaceId,String,50,,true,false,所属工作空间, +SCRIPT_INFO,nodeId,String,50,,true,false,节点ID, +SCRIPT_INFO,scriptId,String,50,,true,false,脚本ID, +SCRIPT_INFO,name,String,50,,true,false,名称, +SCRIPT_INFO,lastRunUser,String,50,,false,false,最后执行人, +SCRIPT_INFO,autoExecCron,String,100,,false,false,自动执行表达式, +SCRIPT_INFO,defArgs,TEXT,,,false,false,默认参数, +SCRIPT_INFO,description,String,255,,false,false,描述, +SCRIPT_INFO,scriptType,String,100,,false,false,脚本类型, +SCRIPT_INFO,triggerToken,String,200,,false,false,触发器token, +SSH_INFO,id,String,50,,true,true,id,ssh信息表 +SSH_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +SSH_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +SSH_INFO,modifyUser,String,50,,false,false,修改人, +SSH_INFO,workspaceId,String,50,,true,false,所属工作空间, +SSH_INFO,name,String,50,,false,false,名称, +SSH_INFO,host,String,100,,true,false,ssh host IP, +SSH_INFO,port,Integer,,,true,false,端口, +SSH_INFO,user,String,100,,true,false,用户, +SSH_INFO,password,String,100,,false,false,密码, +SSH_INFO,charset,String,100,,false,false,编码格式, +SSH_INFO,fileDirs,TEXT,,,false,false,文件目录, +SSH_INFO,privateKey,TEXT,,,false,false,私钥, +SSH_INFO,connectType,String,10,,false,false,连接方式, +SSH_INFO,notAllowedCommand,TEXT,,,false,false,不允许执行的命令, +SSH_INFO,allowEditSuffix,TEXT,,,false,false,允许编辑的后缀文件, +SSH_INFO,timeout,Integer,,0,false,false,节点超时时间, +SSHTERMINALEXECUTELOG,id,String,50,,true,true,id,ssh 终端操作记录表 +SSHTERMINALEXECUTELOG,ip,String,80,,false,false,客户端IP地址, +SSHTERMINALEXECUTELOG,userId,String,30,,false,false,操作的用户ID, +SSHTERMINALEXECUTELOG,userAgent,TEXT,,,false,false,浏览器标识, +SSHTERMINALEXECUTELOG,commands,TEXT,,,false,false,操作的命令, +SSHTERMINALEXECUTELOG,sshId,String,50,,false,false,操作的sshid, +SSHTERMINALEXECUTELOG,sshName,String,50,,false,false,操作的ssh name, +SSHTERMINALEXECUTELOG,refuse,Integer,,,false,false,拒绝执行, +SSHTERMINALEXECUTELOG,createTimeMillis,Long,,,false,false,数据创建时间, +SSHTERMINALEXECUTELOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +SSHTERMINALEXECUTELOG,workspaceId,String,50,,false,false,所属工作空间, +SSHTERMINALEXECUTELOG,modifyUser,String,50,,false,false,操作人, +TRIGGER_TOKEN_LOG,id,String,50,,true,true,id,触发器 token +TRIGGER_TOKEN_LOG,createTimeMillis,Long,,,false,false,数据创建时间, +TRIGGER_TOKEN_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +TRIGGER_TOKEN_LOG,triggerToken,String,100,,true,false,triggerToken, +TRIGGER_TOKEN_LOG,type,String,50,,true,false,token 类型, +TRIGGER_TOKEN_LOG,dataId,String,50,,true,false,关联数据ID, +TRIGGER_TOKEN_LOG,userId,String,50,,true,false,用户ID, +OUTGIVINGLOG,id,String,50,,true,true,id,分发日志 +OUTGIVINGLOG,outGivingId,String,50,,false,false,分发id, +OUTGIVINGLOG,status,TINYINT,,,false,false,状态, +OUTGIVINGLOG,startTime,Long,,,false,false,开始时间, +OUTGIVINGLOG,endTime,Long,,,false,false,结束时间, +OUTGIVINGLOG,result,TEXT,,,false,false,消息, +OUTGIVINGLOG,nodeId,String,100,,false,false,节点id, +OUTGIVINGLOG,projectId,String,100,,false,false,项目id, +OUTGIVINGLOG,workspaceId,String,50,,false,false,所属工作空间, +OUTGIVINGLOG,createTimeMillis,Long,,,false,false,数据创建时间, +OUTGIVINGLOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +OUTGIVINGLOG,modifyUser,String,50,,false,false,操作人, +SCRIPT_EXECUTE_LOG,id,String,50,,true,true,id,节点脚本模版执行记录 +SCRIPT_EXECUTE_LOG,createTimeMillis,Long,,,false,false,数据创建时间, +SCRIPT_EXECUTE_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +SCRIPT_EXECUTE_LOG,modifyUser,String,50,,false,false,修改人, +SCRIPT_EXECUTE_LOG,workspaceId,String,50,,true,false,所属工作空间, +SCRIPT_EXECUTE_LOG,nodeId,String,50,,true,false,节点ID, +SCRIPT_EXECUTE_LOG,scriptId,String,50,,true,false,脚本ID, +SCRIPT_EXECUTE_LOG,scriptName,String,100,,false,false,脚本名称, +SCRIPT_EXECUTE_LOG,triggerExecType,Integer,,0,false,false,触发类型{0,手动,1 自动触发}, +MONITOR_INFO,id,String,50,,true,true,id,监控信息 +MONITOR_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +MONITOR_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +MONITOR_INFO,modifyUser,String,50,,false,false,修改人, +MONITOR_INFO,workspaceId,String,50,,true,false,所属工作空间, +MONITOR_INFO,name,String,50,,true,false,名称, +MONITOR_INFO,autoRestart,TINYINT,,0,false,false,是否自动重启{1,是,0 否}, +MONITOR_INFO,status,TINYINT,,0,false,false,启用状态{1,启用,0 未启用}, +MONITOR_INFO,alarm,TINYINT,,0,false,false,报警状态{1,报警中,0 未报警}, +MONITOR_INFO,cycle,Integer,,0,false,false,监控周期, +MONITOR_INFO,notifyUser,TEXT,,,false,false,报警联系人, +MONITOR_INFO,projects,TEXT,,,false,false,监控的项目, +MONITOR_INFO,execCron,String,100,,false,false,自动执行表达式, +MONITOR_INFO,webhook,String,255,,false,false,webhook, +DOCKER_INFO,id,String,50,,true,true,id,docker 信息 +DOCKER_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +DOCKER_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +DOCKER_INFO,modifyUser,String,50,,false,false,修改人, +DOCKER_INFO,workspaceId,String,50,,true,false,所属工作空间, +DOCKER_INFO,name,String,255,,true,false,名称, +DOCKER_INFO,host,String,255,,false,false,docker host, +DOCKER_INFO,tlsVerify,TINYINT,,0,false,false,tls 认证{1,启用,0 未启用}, +DOCKER_INFO,status,TINYINT,,0,false,false,状态{1,启用,0 未启用}, +DOCKER_INFO,failureMsg,String,255,,false,false,错误消息, +DOCKER_INFO,heartbeatTimeout,Integer,,,false,false,心跳超时时间, +DOCKER_INFO,lastHeartbeatTime,Long,,,false,false,最后心跳时间, +DOCKER_INFO,tags,String,255,,false,false,容器标签, +DOCKER_INFO,swarmId,String,50,,false,false,集群ID, +DOCKER_INFO,swarmNodeId,String,50,,false,false,集群 节点ID, +DOCKER_INFO,registryUsername,String,255,,false,false,仓库账号, +DOCKER_INFO,registryPassword,String,255,,false,false,仓库密码, +DOCKER_INFO,registryEmail,String,255,,false,false,仓库邮箱, +DOCKER_INFO,registryUrl,String,255,,false,false,仓库地址, +USER_BIND_WORKSPACE,id,String,50,,true,true,id,用户工作空间绑定表 +USER_BIND_WORKSPACE,createTimeMillis,Long,,,false,false,数据创建时间, +USER_BIND_WORKSPACE,modifyTimeMillis,Long,,,false,false,数据修改时间, +USER_BIND_WORKSPACE,userId,String,50,,true,false,用户ID, +USER_BIND_WORKSPACE,workspaceId,String,100,,true,false,工作空间ID, +WORKSPACE_ENV_VAR,id,String,50,,true,true,id,用户信息表 +WORKSPACE_ENV_VAR,createTimeMillis,Long,,,false,false,数据创建时间, +WORKSPACE_ENV_VAR,modifyTimeMillis,Long,,,false,false,数据修改时间, +WORKSPACE_ENV_VAR,modifyUser,String,50,,false,false,修改人, +WORKSPACE_ENV_VAR,workspaceId,String,50,,true,false,所属工作空间, +WORKSPACE_ENV_VAR,name,String,50,,false,false,名称, +WORKSPACE_ENV_VAR,description,String,255,,false,false,参数描述, +WORKSPACE_ENV_VAR,value,TEXT,,,false,false,值, +WORKSPACE_ENV_VAR,nodeIds,TEXT,,,false,false,绑定的节点 id, +WORKSPACE_ENV_VAR,privacy,TINYINT,,0,false,false,隐私变量{1,隐私变量,0 非隐私变量(明文回显)}, +NODE_INFO,id,String,50,,true,true,id,节点信息表 +NODE_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +NODE_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +NODE_INFO,modifyUser,String,50,,false,false,修改人, +NODE_INFO,workspaceId,String,50,,true,false,所属工作空间, +NODE_INFO,name,String,50,,false,false,名称, +NODE_INFO,url,String,100,,true,false,节点 url IP:PORT, +NODE_INFO,loginName,String,100,,true,false,节点登录名, +NODE_INFO,loginPwd,String,100,,true,false,节点密码, +NODE_INFO,protocol,String,10,,true,false,协议 http https, +NODE_INFO,openStatus,Integer,,0,false,false,启用状态{1,启用,0 未启用)}, +NODE_INFO,timeOut,Integer,,0,false,false,节点超时时间, +NODE_INFO,sshId,String,50,,false,false,关联的sshid, +NODE_INFO,unLockType,String,50,,false,false,锁定类型, +NODE_INFO,group,String,50,,false,false,分组名称, +NODE_INFO,httpProxy,String,200,,false,false,http 代理, +NODE_INFO,httpProxyType,String,20,,false,false,http 代理类型, +NODE_INFO,sortValue,Float,,,false,false,排序值, +WORKSPACE,id,String,50,,true,true,id,工作空间表 +WORKSPACE,createTimeMillis,Long,,,false,false,数据创建时间, +WORKSPACE,modifyTimeMillis,Long,,,false,false,数据修改时间, +WORKSPACE,modifyUser,String,50,,false,false,修改人, +WORKSPACE,name,String,50,,false,false,名称, +WORKSPACE,description,String,255,,false,false,描述, +MONITORNOTIFYLOG,id,String,50,,true,true,记录id,监控异常日志记录 +MONITORNOTIFYLOG,monitorId,String,50,,false,false,监控id, +MONITORNOTIFYLOG,nodeId,String,50,,false,false,节点ID, +MONITORNOTIFYLOG,projectId,String,30,,false,false,项目id, +MONITORNOTIFYLOG,createTime,Long,,,false,false,异常时间, +MONITORNOTIFYLOG,title,String,100,,false,false,异常描述, +MONITORNOTIFYLOG,content,TEXT,,,false,false,异常内容, +MONITORNOTIFYLOG,status,TINYINT,,,false,false,当前状态, +MONITORNOTIFYLOG,notifyStyle,TINYINT,,,false,false,通知方式, +MONITORNOTIFYLOG,notifyStatus,TINYINT,,,false,false,通知状态, +MONITORNOTIFYLOG,notifyObject,TEXT,,,false,false,通知对象, +MONITORNOTIFYLOG,notifyError,TEXT,,,false,false,通知异常内容, +MONITORNOTIFYLOG,workspaceId,String,50,,false,false,所属工作空间, +MONITORNOTIFYLOG,createTimeMillis,Long,,,false,false,数据创建时间, +MONITORNOTIFYLOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +BUILD_INFO,id,String,50,,true,true,id,构建信息 +BUILD_INFO,repositoryId,String,50,,true,false,仓库 id, +BUILD_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +BUILD_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +BUILD_INFO,name,String,50,,false,false,构建名称, +BUILD_INFO,buildId,Integer,,,false,false,构建 id, +BUILD_INFO,group,String,50,,false,false,分组名称, +BUILD_INFO,branchName,String,50,,false,false,分支, +BUILD_INFO,script,TEXT,,,false,false,构建命令, +BUILD_INFO,resultDirFile,String,200,,false,false,构建产物目录, +BUILD_INFO,releaseMethod,Integer,,,false,false,"发布方法{0: 不发布, 1: 节点分发, 2: 分发项目, 3: SSH}", +BUILD_INFO,modifyUser,String,50,,false,false,修改人, +BUILD_INFO,status,Integer,,,false,false,状态, +BUILD_INFO,triggerToken,String,100,,false,false,触发器token, +BUILD_INFO,extraData,TEXT,,,false,false,额外信息,JSON 字符串格式, +BUILD_INFO,releaseMethodDataId,TEXT,,,false,false,构建关联的数据ID, +BUILD_INFO,branchTagName,String,50,,false,false,标签, +BUILD_INFO,workspaceId,String,50,,false,false,所属工作空间, +BUILD_INFO,webhook,String,255,,false,false,webhook, +BUILD_INFO,autoBuildCron,String,100,,false,false,自动构建表达式, +BUILD_INFO,buildMode,Integer,,,false,false,"构建方式 {0 本地构建, 1 docker 构建}", +BUILD_INFO,repositoryLastCommitId,String,255,,false,false,"仓库代码最后一次变动信息(ID,git 为 commit hash, svn 最后的版本号)", +BUILD_INFO,sortValue,Float,,,false,false,排序值, +MONITOR_USER_OPT,id,String,50,,true,true,id,监控信息 +MONITOR_USER_OPT,createTimeMillis,Long,,,false,false,数据创建时间, +MONITOR_USER_OPT,modifyTimeMillis,Long,,,false,false,数据修改时间, +MONITOR_USER_OPT,modifyUser,String,50,,false,false,修改人, +MONITOR_USER_OPT,workspaceId,String,50,,true,false,所属工作空间, +MONITOR_USER_OPT,name,String,50,,true,false,名称, +MONITOR_USER_OPT,monitorUser,TEXT,,,false,false,监控的人, +MONITOR_USER_OPT,status,TINYINT,,0,false,false,启用状态{1,启用,0 未启用}, +MONITOR_USER_OPT,notifyUser,TEXT,,,false,false,报警联系人, +MONITOR_USER_OPT,monitorFeature,TEXT,,,false,false,监控的项目, +MONITOR_USER_OPT,monitorOpt,TEXT,,,false,false,监控的项目, +SYSTEM_PARAMETERS,id,String,100,,true,true,ID,系统参数表 +SYSTEM_PARAMETERS,createTimeMillis,Long,,,false,false,数据创建时间, +SYSTEM_PARAMETERS,modifyTimeMillis,Long,,,false,false,数据修改时间, +SYSTEM_PARAMETERS,modifyUser,String,50,,false,false,修改人, +SYSTEM_PARAMETERS,value,TEXT,,,false,false,参数值,JSON 字符串格式, +SYSTEM_PARAMETERS,description,String,255,,false,false,参数描述, +COMMAND_EXEC_LOG,id,String,50,,true,true,id,命令执行记录 +COMMAND_EXEC_LOG,createTimeMillis,Long,,,false,false,数据创建时间, +COMMAND_EXEC_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +COMMAND_EXEC_LOG,modifyUser,String,50,,false,false,修改人, +COMMAND_EXEC_LOG,workspaceId,String,50,,true,false,所属工作空间, +COMMAND_EXEC_LOG,commandId,String,50,,true,false,命令ID, +COMMAND_EXEC_LOG,sshId,String,50,,true,false,ssh id, +COMMAND_EXEC_LOG,batchId,String,50,,true,false,批次ID, +COMMAND_EXEC_LOG,commandName,String,100,,true,false,命令名称, +COMMAND_EXEC_LOG,sshName,String,100,,true,false,ssh 名称, +COMMAND_EXEC_LOG,status,Integer,,,true,false,状态 {0,执行中,1 执行结束,2 执行错误}, +COMMAND_EXEC_LOG,params,TEXT,,,false,false,命令参数, +COMMAND_EXEC_LOG,triggerExecType,Integer,,0,false,false,触发类型{0,手动,1 自动触发}, +PROJECT_INFO,id,String,50,,true,true,id,项目信息表 +PROJECT_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +PROJECT_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +PROJECT_INFO,modifyUser,String,50,,false,false,修改人, +PROJECT_INFO,workspaceId,String,50,,true,false,所属工作空间, +PROJECT_INFO,projectId,String,50,,true,false,项目ID, +PROJECT_INFO,nodeId,String,50,,true,false,节点ID, +PROJECT_INFO,name,String,50,,true,false,名称, +PROJECT_INFO,mainClass,String,100,,false,false,mainClas, +PROJECT_INFO,lib,String,100,,false,false,lib, +PROJECT_INFO,whitelistDirectory,String,100,,false,false,whitelistDirectory, +PROJECT_INFO,logPath,String,100,,false,false,logPath, +PROJECT_INFO,jvm,TEXT,,,false,false,jvm, +PROJECT_INFO,args,TEXT,,,false,false,args, +PROJECT_INFO,javaCopyItemList,TEXT,,,false,false,javaCopyItemList, +PROJECT_INFO,token,String,255,,false,false,token, +PROJECT_INFO,runMode,String,20,,false,false,连接方式, +PROJECT_INFO,outGivingProject,TINYINT,,0,false,false,分发项目{1,分发,0 独立项目}, +PROJECT_INFO,javaExtDirsCp,TEXT,,,false,false,javaExtDirsCp, +PROJECT_INFO,sortValue,Float,,,false,false,排序值, +PROJECT_INFO,triggerToken,String,100,,false,false,触发器token, +DOCKER_SWARM_INFO,id,String,50,,true,true,id,docker 集群信息 +DOCKER_SWARM_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +DOCKER_SWARM_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +DOCKER_SWARM_INFO,modifyUser,String,50,,false,false,修改人, +DOCKER_SWARM_INFO,workspaceId,String,50,,true,false,所属工作空间, +DOCKER_SWARM_INFO,name,String,255,,true,false,名称, +DOCKER_SWARM_INFO,swarmId,String,50,,true,false,swarm Id, +DOCKER_SWARM_INFO,tag,String,255,,false,false,容器标签, +USER_PERMISSION_GROUP,id,String,50,,true,true,id,用户权限组 +USER_PERMISSION_GROUP,createTimeMillis,Long,,,false,false,数据创建时间, +USER_PERMISSION_GROUP,modifyTimeMillis,Long,,,false,false,数据修改时间, +USER_PERMISSION_GROUP,modifyUser,String,50,,false,false,修改人, +USER_PERMISSION_GROUP,name,String,250,,true,false,名称, +USER_PERMISSION_GROUP,description,String,255,,false,false,描述, +USER_PERMISSION_GROUP,prohibitExecute,TEXT,,,false,false,禁止执行的配置, +USER_PERMISSION_GROUP,allowExecute,TEXT,,,false,false,允许执行的配置, +SERVER_SCRIPT_INFO,id,String,50,,true,true,id,脚本模版 +SERVER_SCRIPT_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +SERVER_SCRIPT_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +SERVER_SCRIPT_INFO,modifyUser,String,50,,false,false,修改人, +SERVER_SCRIPT_INFO,workspaceId,String,50,,true,false,所属工作空间, +SERVER_SCRIPT_INFO,name,String,50,,true,false,名称, +SERVER_SCRIPT_INFO,lastRunUser,String,50,,false,false,最后执行人, +SERVER_SCRIPT_INFO,autoExecCron,String,100,,false,false,自动执行表达式, +SERVER_SCRIPT_INFO,defArgs,String,100,,false,false,默认参数, +SERVER_SCRIPT_INFO,context,TEXT,,,false,false,内容, +SERVER_SCRIPT_INFO,description,String,255,,false,false,描述, +SERVER_SCRIPT_INFO,nodeIds,TEXT,,,false,false,绑定的节点 id, +SERVER_SCRIPT_INFO,triggerToken,String,200,,false,false,触发器token, +USER_INFO,id,String,50,,true,true,id,用户信息表 +USER_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +USER_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +USER_INFO,modifyUser,String,50,,false,false,修改人, +USER_INFO,parent,String,50,,true,false,创建人, +USER_INFO,name,String,50,,false,false,昵称, +USER_INFO,systemUser,Integer,,0,false,false,是否为系统管理员 {1,是,0 否(默认)}, +USER_INFO,password,String,100,,false,false,密码, +USER_INFO,salt,String,50,,false,false,盐值, +USER_INFO,pwdErrorCount,Integer,,0,false,false,密码错误次数, +USER_INFO,lastPwdErrorTime,Long,,0,false,false,最后登录失败时间, +USER_INFO,lockTime,Long,,0,false,false,锁定时长, +USER_INFO,email,String,255,,false,false,邮箱地址, +USER_INFO,dingDing,String,255,,false,false,钉钉地址, +USER_INFO,workWx,String,255,,false,false,企业微信地址, +USER_INFO,twoFactorAuthKey,String,100,,false,false,两步验证, +USER_INFO,status,TINYINT,,,false,false,状态 0 禁用 null、1 启用, +USER_INFO,permissionGroup,TEXT,,,false,false,权限组, +COMMAND_INFO,id,String,50,,true,true,id,命令行信息 +COMMAND_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +COMMAND_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +COMMAND_INFO,modifyUser,String,50,,false,false,修改人, +COMMAND_INFO,workspaceId,String,50,,true,false,所属工作空间, +COMMAND_INFO,name,String,100,,false,false,命令名称, +COMMAND_INFO,desc,String,500,,false,false,命令描述, +COMMAND_INFO,command,TEXT,,,false,false,指令内容, +COMMAND_INFO,defParams,TEXT,,,false,false,命令参数, +COMMAND_INFO,sshIds,TEXT,,,false,false,绑定的ssh id, +COMMAND_INFO,autoExecCron,String,100,,false,false,自动执行表达式, +COMMAND_INFO,triggerToken,String,200,,false,false,触发器token, +OUT_GIVING,id,String,50,,true,true,id,节点分发信息 +OUT_GIVING,createTimeMillis,Long,,,false,false,数据创建时间, +OUT_GIVING,modifyTimeMillis,Long,,,false,false,数据修改时间, +OUT_GIVING,modifyUser,String,50,,false,false,修改人, +OUT_GIVING,workspaceId,String,50,,true,false,所属工作空间, +OUT_GIVING,name,String,50,,true,false,名称, +OUT_GIVING,afterOpt,Integer,,0,false,false,分发后的操作, +OUT_GIVING,clearOld,TINYINT,,0,false,false,是否清空旧包发布, +OUT_GIVING,outGivingProject,TINYINT,,0,false,false,是否为单独创建的分发项目, +OUT_GIVING,outGivingNodeProjectList,TEXT,,,false,false,分发项目信息, +OUT_GIVING,intervalTime,Integer,,10,false,false,分发间隔时间, +OUT_GIVING,status,Integer,,0,false,false,状态{0: 未分发; 1: 分发中; 2: 分发结束}, +OUT_GIVING,secondaryDirectory,String,200,,false,false,二级目录, +OUT_GIVING,uploadCloseFirst,TINYINT,,0,false,false,是否清空旧包发布, +USEROPERATELOGV1,id,String,50,,true,true,id,操作日志 +USEROPERATELOGV1,ip,String,80,,false,false,客户端IP地址, +USEROPERATELOGV1,userId,String,30,,false,false,操作的用户ID, +USEROPERATELOGV1,resultMsg,TEXT,,,false,false,操作的结果信息, +USEROPERATELOGV1,optStatus,Integer,,,false,false,操作状态 成功/失败, +USEROPERATELOGV1,optTime,Long,,,false,false,操作时间, +USEROPERATELOGV1,nodeId,String,50,,false,false,节点ID, +USEROPERATELOGV1,dataId,String,200,,false,false,操作的数据ID, +USEROPERATELOGV1,userAgent,TEXT,,,false,false,浏览器标识, +USEROPERATELOGV1,reqData,TEXT,,,false,false,用户请求参数, +USEROPERATELOGV1,workspaceId,String,50,,false,false,所属工作空间, +USEROPERATELOGV1,createTimeMillis,Long,,,false,false,数据创建时间, +USEROPERATELOGV1,modifyTimeMillis,Long,,,false,false,数据修改时间, +USEROPERATELOGV1,modifyUser,String,50,,false,false,操作人, +USEROPERATELOGV1,classFeature,String,100,,false,false,操作的功能, +USEROPERATELOGV1,methodFeature,String,100,,false,false,操作的方法, +USEROPERATELOGV1,dataName,String,200,,false,false,数据名称, +REPOSITORY,id,String,50,,true,true,id,仓库信息 +REPOSITORY,createTimeMillis,Long,,,false,false,数据创建时间, +REPOSITORY,modifyTimeMillis,Long,,,false,false,数据修改时间, +REPOSITORY,name,String,50,,false,false,仓库名称, +REPOSITORY,gitUrl,String,255,,false,false,仓库地址, +REPOSITORY,repoType,Integer,,,false,false,"仓库类型{0: GIT, 1: SVN}", +REPOSITORY,protocol,Integer,,,false,false,"拉取代码的协议{0: http, 1: ssh}", +REPOSITORY,userName,String,50,,false,false,登录用户, +REPOSITORY,password,String,50,,false,false,登录密码, +REPOSITORY,rsaPub,String,2048,,false,false,SSH RSA 公钥, +REPOSITORY,rsaPrv,String,4096,,false,false,SSH RSA 私钥, +REPOSITORY,modifyUser,String,50,,false,false,修改人, +REPOSITORY,workspaceId,String,50,,false,false,所属工作空间, +REPOSITORY,sortValue,Float,,,false,false,排序值, +SERVER_SCRIPT_EXECUTE_LOG,id,String,50,,true,true,id,脚本模版执行记录 +SERVER_SCRIPT_EXECUTE_LOG,createTimeMillis,Long,,,false,false,数据创建时间, +SERVER_SCRIPT_EXECUTE_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +SERVER_SCRIPT_EXECUTE_LOG,modifyUser,String,50,,false,false,修改人, +SERVER_SCRIPT_EXECUTE_LOG,workspaceId,String,50,,true,false,所属工作空间, +SERVER_SCRIPT_EXECUTE_LOG,scriptId,String,50,,true,false,脚本ID, +SERVER_SCRIPT_EXECUTE_LOG,scriptName,String,100,,false,false,脚本名称, +SERVER_SCRIPT_EXECUTE_LOG,triggerExecType,Integer,,0,false,false,触发类型{0,手动,1 自动触发}, +BUILDHISTORYLOG,id,String,50,,true,true,表id,构建历史记录 +BUILDHISTORYLOG,buildDataId,String,50,,false,false,构建的数据id, +BUILDHISTORYLOG,buildNumberId,Integer,,,false,false,构建编号, +BUILDHISTORYLOG,status,TINYINT,,,false,false,构建状态, +BUILDHISTORYLOG,startTime,Long,,,false,false,开始时间, +BUILDHISTORYLOG,endTime,Long,,,false,false,结束时间, +BUILDHISTORYLOG,resultDirFile,String,200,,false,false,构建产物目录, +BUILDHISTORYLOG,releaseMethod,TINYINT,,,false,false,发布方式, +BUILDHISTORYLOG,name,String,100,,false,false,构建名称, +BUILDHISTORYLOG,buildName,String,100,,false,false,构建名称, +BUILDHISTORYLOG,createTimeMillis,Long,,,false,false,数据创建时间, +BUILDHISTORYLOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +BUILDHISTORYLOG,modifyUser,String,50,,false,false,操作人, +BUILDHISTORYLOG,workspaceId,String,50,,false,false,所属工作空间, +BUILDHISTORYLOG,triggerBuildType,Integer,,0,false,false,"触发类型{0,手动,1 触发器,2 自动触发}", +BUILDHISTORYLOG,buildRemark,String,255,,false,false,构建备注, +BUILDHISTORYLOG,extraData,TEXT,,,false,false,额外信息,JSON 字符串格式, +LOG_READ,id,String,50,,true,true,id,日志阅读 +LOG_READ,createTimeMillis,Long,,,false,false,数据创建时间, +LOG_READ,modifyTimeMillis,Long,,,false,false,数据修改时间, +LOG_READ,modifyUser,String,50,,false,false,修改人, +LOG_READ,workspaceId,String,50,,true,false,所属工作空间, +LOG_READ,name,String,50,,true,false,日志项目名称, +LOG_READ,nodeProject,TEXT,,,false,false,节点下的项目列表, +LOG_READ,cacheData,TEXT,,,false,false,缓存操作数据, diff --git a/modules/server/src/main/resources/sql-view/table.all.v1.1.csv b/modules/server/src/main/resources/sql-view/table.all.v1.1.csv new file mode 100644 index 0000000000..9b1e4e7169 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/table.all.v1.1.csv @@ -0,0 +1,142 @@ +tableName,name,type,len,defaultValue,notNull,primaryKey,comment,tableComment +MACHINE_NODE_INFO,id,String,50,,true,true,id,机器节点信息 +MACHINE_NODE_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +MACHINE_NODE_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +MACHINE_NODE_INFO,modifyUser,String,50,,false,false,修改人, +MACHINE_NODE_INFO,name,String,50,,true,false,机器名称, +MACHINE_NODE_INFO,groupName,String,50,,true,false,分组名称, +MACHINE_NODE_INFO,hostName,String,255,,false,false,机器主机名, +MACHINE_NODE_INFO,hostIpv4s,TEXT,,,false,false,机器所有 IP, +MACHINE_NODE_INFO,osLoadAverage,String,100,,false,false,系统负载, +MACHINE_NODE_INFO,osSystemUptime,Long,,,false,false,系统运行时间(自启动以来的时间), +MACHINE_NODE_INFO,osVersion,String,255,,false,false,系统版本, +MACHINE_NODE_INFO,osHardwareVersion,String,255,,false,false,硬件版本, +MACHINE_NODE_INFO,osCpuCores,Integer,,,false,false,CPU数, +MACHINE_NODE_INFO,osMoneyTotal,Long,,,false,false,总内存, +MACHINE_NODE_INFO,osFileStoreTotal,Long,,,false,false,硬盘大小, +MACHINE_NODE_INFO,osCpuIdentifierName,String,255,,false,false,CPU 型号, +MACHINE_NODE_INFO,osName,String,50,,false,false,系统名称, +MACHINE_NODE_INFO,status,TINYINT,,,true,false,节点连接状态:0 未连接,1 连接中, +MACHINE_NODE_INFO,statusMsg,TEXT,,,false,false,状态消息, +MACHINE_NODE_INFO,transportMode,TINYINT,,,true,false,传输方式。0 服务器拉取,1 节点机器推送, +MACHINE_NODE_INFO,jpomUrl,String,100,,false,false,节点 url IP:PORT, +MACHINE_NODE_INFO,jpomUsername,String,100,,false,false,节点登录名, +MACHINE_NODE_INFO,jpomPassword,String,100,,false,false,节点密码, +MACHINE_NODE_INFO,jpomProtocol,String,10,,false,false,协议 http https, +MACHINE_NODE_INFO,jpomTimeout,Integer,,,false,false,节点超时时间, +MACHINE_NODE_INFO,jpomHttpProxy,String,200,,false,false,http 代理, +MACHINE_NODE_INFO,jpomHttpProxyType,String,20,,false,false,http 代理类型, +MACHINE_NODE_INFO,jpomVersion,String,50,,false,false,jpom版本号, +MACHINE_NODE_INFO,jpomUptime,Long,,,false,false,jpom运行时间, +MACHINE_NODE_INFO,jpomBuildTime,String,50,,false,false,Jpom 打包时间, +MACHINE_NODE_INFO,jpomProjectCount,Integer,,,false,false,jpom项目数, +MACHINE_NODE_INFO,jpomScriptCount,Integer,,,false,false,jpom脚本数, +MACHINE_NODE_INFO,networkDelay,Integer,,,false,false,网络耗时(延迟), +MACHINE_NODE_INFO,javaVersion,String,50,,false,false,java版本, +MACHINE_NODE_INFO,jvmTotalMemory,Long,,,false,false,jvm 总内存, +MACHINE_NODE_INFO,jvmFreeMemory,Long,,,false,false,jvm 剩余内存, +MACHINE_NODE_INFO,osOccupyCpu,Double,,,false,false,占用cpu, +MACHINE_NODE_INFO,osOccupyMemory,Double,,,false,false,占用内存, +MACHINE_NODE_INFO,osOccupyDisk,Double,,,false,false,占用磁盘, +MACHINE_NODE_STAT_LOG,id,String,50,,true,true,id,资产机器节点统计 +MACHINE_NODE_STAT_LOG,createTimeMillis,Long,,,false,false,数据创建时间, +MACHINE_NODE_STAT_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +MACHINE_NODE_STAT_LOG,machineId,String,50,,true,true,机器id, +MACHINE_NODE_STAT_LOG,occupyCpu,Double,,,false,false,占用cpu, +MACHINE_NODE_STAT_LOG,occupyMemory,Double,,,false,false,占用内存, +MACHINE_NODE_STAT_LOG,occupyDisk,Double,,,false,false,占用磁盘, +MACHINE_NODE_STAT_LOG,networkDelay,Integer,,0,false,false,网络耗时, +MACHINE_NODE_STAT_LOG,monitorTime,Long,,,true,false,监控的时间, +MACHINE_NODE_STAT_LOG,netTxBytes,Long,,,false,false,"每秒发送的KB数,rxkB/s", +MACHINE_NODE_STAT_LOG,netRxBytes,Long,,,false,false,"每秒接收的KB数,rxkB/s", +MACHINE_SSH_INFO,id,String,50,,true,true,id,ssh信息表 +MACHINE_SSH_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +MACHINE_SSH_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +MACHINE_SSH_INFO,modifyUser,String,50,,false,false,修改人, +MACHINE_SSH_INFO,name,String,50,,false,false,名称, +MACHINE_SSH_INFO,groupName,String,50,,true,false,分组名称, +MACHINE_SSH_INFO,host,String,100,,true,false,ssh host IP, +MACHINE_SSH_INFO,port,Integer,,,true,false,端口, +MACHINE_SSH_INFO,user,String,100,,true,false,用户, +MACHINE_SSH_INFO,password,String,100,,false,false,密码, +MACHINE_SSH_INFO,charset,String,100,,false,false,编码格式, +MACHINE_SSH_INFO,privateKey,TEXT,,,false,false,私钥, +MACHINE_SSH_INFO,connectType,String,10,,false,false,连接方式, +MACHINE_SSH_INFO,timeout,Integer,,0,false,false,节点超时时间, +MACHINE_SSH_INFO,status,TINYINT,,,true,false,状态{0,无法连接,1 正常}, +MACHINE_SSH_INFO,statusMsg,TEXT,,,false,false,状态消息, +MACHINE_DOCKER_INFO,id,String,50,,true,true,id,机器DOCKER信息 +MACHINE_DOCKER_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +MACHINE_DOCKER_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +MACHINE_DOCKER_INFO,modifyUser,String,50,,false,false,修改人, +MACHINE_DOCKER_INFO,groupName,String,50,,true,false,分组名称, +MACHINE_DOCKER_INFO,name,String,255,,true,false,名称, +MACHINE_DOCKER_INFO,host,String,255,,false,false,docker host, +MACHINE_DOCKER_INFO,tlsVerify,TINYINT,,0,false,false,tls 认证{1,启用,0 未启用}, +MACHINE_DOCKER_INFO,status,TINYINT,,0,false,false,状态{1,启用,0 未启用}, +MACHINE_DOCKER_INFO,failureMsg,String,255,,false,false,错误消息, +MACHINE_DOCKER_INFO,heartbeatTimeout,Integer,,,false,false,心跳超时时间, +MACHINE_DOCKER_INFO,lastHeartbeatTime,Long,,,false,false,最后心跳时间, +MACHINE_DOCKER_INFO,dockerVersion,TEXT,,,false,false,docker 版本信息, +MACHINE_DOCKER_INFO,swarmId,String,50,,false,false,集群ID, +MACHINE_DOCKER_INFO,swarmNodeId,String,50,,false,false,集群 节点ID, +MACHINE_DOCKER_INFO,registryUsername,String,255,,false,false,仓库账号, +MACHINE_DOCKER_INFO,registryPassword,String,255,,false,false,仓库密码, +MACHINE_DOCKER_INFO,registryEmail,String,255,,false,false,仓库邮箱, +MACHINE_DOCKER_INFO,registryUrl,String,255,,false,false,仓库地址, +USER_LOGIN_LOG,id,String,50,,true,true,id,用户登录日志 +USER_LOGIN_LOG,createTimeMillis,Long,,,false,false,数据创建时间, +USER_LOGIN_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +USER_LOGIN_LOG,modifyUser,String,50,,false,false,修改人, +USER_LOGIN_LOG,ip,String,80,,false,false,客户端IP地址, +USER_LOGIN_LOG,userAgent,TEXT,,,false,false,浏览器标识, +USER_LOGIN_LOG,useMfa,TINYINT,,,false,false,是否使用 mfa, +USER_LOGIN_LOG,success,TINYINT,,,false,false,是否登录成功, +USER_LOGIN_LOG,operateCode,TINYINT,,,false,false,操作状态码(备注码), +USER_LOGIN_LOG,username,String,50,,false,false,昵称, +FILE_STORAGE,id,String,50,,true,true,id,文件管理中心 +FILE_STORAGE,createTimeMillis,Long,,,true,false,数据创建时间, +FILE_STORAGE,modifyTimeMillis,Long,,,false,false,数据修改时间, +FILE_STORAGE,modifyUser,String,50,,false,false,修改人, +FILE_STORAGE,createUser,String,50,,true,false,创建人, +FILE_STORAGE,workspaceId,String,50,,true,false,所属工作空间, +FILE_STORAGE,name,String,255,,true,false,名称, +FILE_STORAGE,description,String,255,,false,false,描述, +FILE_STORAGE,size,Long,,,true,false,文件大小, +FILE_STORAGE,source,TINYINT,,,true,false,文件来源, +FILE_STORAGE,validUntil,Long,,,true,false,文件有效期, +FILE_STORAGE,path,String,255,,true,false,文件路径, +FILE_STORAGE,extName,String,50,,true,false,文件后缀, +FILE_RELEASE_TASK_LOG,id,String,50,,true,true,id,文件发布任务记录 +FILE_RELEASE_TASK_LOG,createTimeMillis,Long,,,true,false,数据创建时间, +FILE_RELEASE_TASK_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间, +FILE_RELEASE_TASK_LOG,modifyUser,String,50,,false,false,修改人, +FILE_RELEASE_TASK_LOG,workspaceId,String,50,,true,false,所属工作空间, +FILE_RELEASE_TASK_LOG,name,String,255,,true,false,名称, +FILE_RELEASE_TASK_LOG,taskId,String,50,,true,false,任务id, +FILE_RELEASE_TASK_LOG,fileId,String,50,,true,false,文件 id, +FILE_RELEASE_TASK_LOG,taskDataId,String,50,,true,false,任务关联的数据id, +FILE_RELEASE_TASK_LOG,taskType,TINYINT,,,true,false,任务类型 0 ssh 1 节点, +FILE_RELEASE_TASK_LOG,status,TINYINT,,,true,false,任务状态, 0 等待开始 1 进行中 2 任务结束 3 失败 4 取消任务, +FILE_RELEASE_TASK_LOG,statusMsg,TEXT,,,false,false,状态消息, +FILE_RELEASE_TASK_LOG,releasePath,String,255,,true,false,发布路径, +FILE_RELEASE_TASK_LOG,beforeScript,TEXT,,,false,false,发布之前的脚本, +FILE_RELEASE_TASK_LOG,afterScript,TEXT,,,false,false,发布后的脚本, +CERTIFICATE_INFO,id,String,50,,true,true,id,证书信息表 +CERTIFICATE_INFO,createTimeMillis,Long,,,true,false,数据创建时间, +CERTIFICATE_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +CERTIFICATE_INFO,modifyUser,String,50,,false,false,修改人, +CERTIFICATE_INFO,createUser,String,50,,true,false,创建人, +CERTIFICATE_INFO,workspaceId,String,50,,true,false,所属工作空间, +CERTIFICATE_INFO,description,String,255,,false,false,描述, +CERTIFICATE_INFO,keyAlias,String,50,,false,false,别名, +CERTIFICATE_INFO,keyType,String,50,,false,false,证书类型, +CERTIFICATE_INFO,certPassword,String,50,,false,false,证书密码, +CERTIFICATE_INFO,serialNumberStr,String,50,,true,false,证书序列号, +CERTIFICATE_INFO,issuerDnName,String,255,,false,false,颁发者 DN 名称, +CERTIFICATE_INFO,subjectDnName,String,255,,false,false, 主题 DN 名称, +CERTIFICATE_INFO,sigAlgOid,String,255,,false,false,算法OID, +CERTIFICATE_INFO,sigAlgName,String,255,,false,false,算法名, +CERTIFICATE_INFO,expirationTime,String,255,,false,false,证书到期时间, +CERTIFICATE_INFO,effectiveTime,Long,,,false,false,证书生效日期, +CERTIFICATE_INFO,certVersion,Integer,,,false,false,版本号, 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 new file mode 100644 index 0000000000..f1653b810d --- /dev/null +++ b/modules/server/src/main/resources/sql-view/table.all.v1.2.csv @@ -0,0 +1,41 @@ +tableName,name,type,len,defaultValue,notNull,primaryKey,comment,tableComment +CLUSTER_INFO,id,String,50,,true,true,id,机器节点信息 +CLUSTER_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +CLUSTER_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +CLUSTER_INFO,modifyUser,String,50,,false,false,修改人, +CLUSTER_INFO,name,String,50,,true,false,集群名称, +CLUSTER_INFO,clusterId,String,50,,true,false,集群Id, +CLUSTER_INFO,url,String,255,,false,false,集群地址, +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,300,,false,false,文件路径, +STATIC_FILE_STORAGE,parentAbsolutePath,String,300,,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, + +SCRIPT_LIBRARY,id,String,50,,true,true,id,静态文件管理 +SCRIPT_LIBRARY,createTimeMillis,Long,,,false,false,数据创建时间, +SCRIPT_LIBRARY,modifyTimeMillis,Long,,,false,false,数据修改时间, +SCRIPT_LIBRARY,modifyUser,String,50,,false,false,修改人, +SCRIPT_LIBRARY,createUser,String,50,,true,false,创建人, +SCRIPT_LIBRARY,tag,String,50,,true,false,标签, +SCRIPT_LIBRARY,description,String,255,,false,false,描述, +SCRIPT_LIBRARY,script,text,,,false,false,描述, +SCRIPT_LIBRARY,machineIds,text,,,false,false,关联的机器节点, +SCRIPT_LIBRARY,version,String,50,,true,false,版本, diff --git a/modules/server/src/main/resources/sql-view/table.h2.v1.0.csv b/modules/server/src/main/resources/sql-view/table.h2.v1.0.csv new file mode 100644 index 0000000000..7a8f480790 --- /dev/null +++ b/modules/server/src/main/resources/sql-view/table.h2.v1.0.csv @@ -0,0 +1,13 @@ +tableName,name,type,len,defaultValue,notNull,primaryKey,comment,tableComment +BACKUP_INFO,id,String,50,,true,true,id,备份数据库信息 +BACKUP_INFO,createTimeMillis,Long,,,false,false,数据创建时间, +BACKUP_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间, +BACKUP_INFO,name,String,50,,false,false,备份名称, +BACKUP_INFO,filePath,String,200,,false,false,文件地址, +BACKUP_INFO,backupType,Integer,,,false,false,"备份类型{0: 全量, 1: 部分}", +BACKUP_INFO,fileSize,Long,,,false,false,文件大小, +BACKUP_INFO,sha1Sum,String,50,,false,false,SHA1 信息, +BACKUP_INFO,status,Integer,,0,false,false,状态{0: 默认; 1: 成功; 2: 失败}, +BACKUP_INFO,baleTimeStamp,Long,,,false,false,打包时间, +BACKUP_INFO,version,String,255,,false,false,服务版本, +BACKUP_INFO,modifyUser,String,50,,false,false,操作人, diff --git a/modules/server/src/main/resources/thank-dependency.json b/modules/server/src/main/resources/thank-dependency.json new file mode 100644 index 0000000000..18e946c606 --- /dev/null +++ b/modules/server/src/main/resources/thank-dependency.json @@ -0,0 +1,169 @@ +[ + { + "name": "Spring Boot", + "link": "https://spring.io/", + "license": "Apache-2.0" + }, + { + "name": "Vue", + "link": "https://vuejs.org/", + "license": "MIT" + }, + { + "name": "vite", + "link": "https://vitejs.dev/", + "license": "MIT" + }, + { + "name": "hutool", + "link": "https://hutool.cn/", + "license": "MulanPSL-2.0" + }, + { + "name": "ant-design-vue", + "link": "https://antdv.com/", + "license": "MIT" + }, + { + "name": "fastjson", + "link": "https://github.com/alibaba/fastjson2", + "license": "Apache-2.0" + }, + { + "name": "commons-compress", + "link": "https://commons.apache.org/proper/commons-compress/", + "license": "Apache-2.0" + }, + { + "name": "commons-exec", + "link": "https://commons.apache.org/proper/commons-exec/", + "license": "Apache-2.0" + }, + { + "name": "oshi", + "link": "https://github.com/oshi/", + "license": "MIT" + }, + { + "name": "bouncycastle", + "link": "https://www.bouncycastle.org/java.html", + "license": "Bouncy Castle Licence" + }, + { + "name": "JustAuth", + "link": "https://gitee.com/yadong.zhang/JustAuth", + "license": "MIT" + }, + { + "name": "h2", + "link": "https://h2database.com", + "license": [ + "MPL 2.0", + "EPL 1.0" + ] + }, + { + "name": "mariadb", + "link": "https://mariadb.com/kb/en/mariadb/about-mariadb-connector-j/", + "license": "LGPL-2.1" + }, + { + "name": "mysql", + "link": "http://dev.mysql.com/doc/connector-j/en/", + "license": "The GNU General Public License, v2 with Universal FOSS Exception, v1.0" + }, + { + "name": "postgresql", + "link": "https://jdbc.postgresql.org/", + "license": "BSD-2-Clause" + }, + { + "name": "docker-java", + "link": "https://github.com/docker-java/docker-java", + "license": "Apache-2.0" + }, + { + "name": "javax.mail", + "link": "https://javaee.github.io/javamail/", + "license": [ + "CDDL", + "GPL 2.0" + ] + }, + { + "name": "jgit", + "link": "https://www.eclipse.org/jgit/", + "license": [ + "BSD", + "EDL" + ] + }, + { + "name": "mwiede(jsch)", + "link": "https://github.com/mwiede/jsch", + "license": [ + "BSD", + "ISC" + ] + }, + { + "name": "svnkit", + "link": "https://svnkit.com/", + "license": "TMate" + }, + { + "name": "xtermjs", + "link": "https://github.com/xtermjs/xterm.js", + "license": "MIT" + }, + { + "name": "codemirror", + "link": "https://codemirror.net/", + "license": "MIT" + }, + { + "name": "echarts", + "link": "https://echarts.apache.org/zh/index.html", + "license": "Apache-2.0" + }, + { + "name": "dayjs", + "link": "https://day.js.org/", + "license": "MIT" + }, + { + "name": "axios", + "link": "https://github.com/axios/axios", + "license": "MIT" + }, + { + "name": "base64-js", + "link": "https://github.com/beatgammit/base64-js", + "license": "MIT" + }, + { + "name": "js-sha1", + "link": "https://github.com/emn178/js-sha1", + "license": "MIT" + }, + { + "name": "pinia", + "link": "https://pinia.vuejs.org/", + "license": "MIT" + }, + { + "name": "prismjs", + "link": "https://prismjs.com/", + "license": "MIT" + }, + { + "name": "qs", + "link": "https://github.com/ljharb/qs", + "license": "BSD 3-Clause" + }, + { + "name": "spark-md5", + "link": "https://github.com/satazor/js-spark-md5", + "license": "WTFPL" + } +] diff --git a/modules/server/src/test/java/JpomTestJvm.java b/modules/server/src/test/java/JpomTestJvm.java index e549020706..296b42c0fd 100644 --- a/modules/server/src/test/java/JpomTestJvm.java +++ b/modules/server/src/test/java/JpomTestJvm.java @@ -1,47 +1,12 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -///* -// * The MIT License (MIT) -// * -// * Copyright (c) 2019 码之科技工作室 -// * -// * 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 com.sun.tools.attach.AttachNotSupportedException; //import com.sun.tools.attach.VirtualMachine; //import com.sun.tools.attach.VirtualMachineDescriptor; @@ -51,7 +16,7 @@ //import java.util.Properties; // ///** -// * Created by jiangzeyin on 2019/4/19. +// * Created by bwcx_jzy on 2019/4/19. // */ //public class JpomTestJvm { // diff --git a/modules/server/src/test/java/RunFile.java b/modules/server/src/test/java/RunFile.java index 6ed3802666..908b6a5c7c 100644 --- a/modules/server/src/test/java/RunFile.java +++ b/modules/server/src/test/java/RunFile.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.text.StrSplitter; import cn.hutool.core.util.StrUtil; @@ -26,7 +13,7 @@ import java.util.List; /** - * Created by jiangzeyin on 2019/2/26. + * Created by bwcx_jzy on 2019/2/26. */ public class RunFile { public static void main(String[] args) { diff --git a/modules/server/src/test/java/SysInfoAcquirerService.java b/modules/server/src/test/java/SysInfoAcquirerService.java index 5b547b49e1..f9eed349c9 100644 --- a/modules/server/src/test/java/SysInfoAcquirerService.java +++ b/modules/server/src/test/java/SysInfoAcquirerService.java @@ -1,27 +1,14 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -//import com.alibaba.fastjson.JSON; -//import com.alibaba.fastjson.JSONObject; +//import com.alibaba.fastjson2.JSON; +//import com.alibaba.fastjson2.JSONObject; //import com.alibaba.fastjson.serializer.SerializerFeature; //import com.sun.management.OperatingSystemMXBean; //import org.springframework.stereotype.Service; @@ -40,7 +27,7 @@ // * 网管信息信息信息采集类 // * // * @author liming.cen -// * @date 2017-4-11 +// * @since 2017-4-11 // */ //@Service //public class SysInfoAcquirerService { @@ -814,4 +801,4 @@ // public void destroy() { // log.info("do nothing in @PreDestroy method"); // } -//} \ No newline at end of file +//} diff --git a/modules/server/src/test/java/TestA/Test.java b/modules/server/src/test/java/TestA/Test.java index 0597f73867..0d67d308db 100644 --- a/modules/server/src/test/java/TestA/Test.java +++ b/modules/server/src/test/java/TestA/Test.java @@ -1,60 +1,25 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -///* -// * The MIT License (MIT) -// * -// * Copyright (c) 2019 码之科技工作室 -// * -// * 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 TestA; // //import cn.hutool.core.io.FileUtil; //import io.jpom.permission.CacheControllerFeature; -//import io.jpom.plugin.ClassFeature; -//import io.jpom.plugin.MethodFeature; +//import io.jpom.permission.ClassFeature; +//import io.jpom.permission.MethodFeature; // //import java.util.Map; //import java.util.Set; // ///** // * @author bwcx_jzy -// * @date 2019/8/14 +// * @since 2019/8/14 // */ //public class Test { // public static void main(String[] args) { diff --git a/modules/server/src/test/java/TestA/TestVersion.java b/modules/server/src/test/java/TestA/TestVersion.java index 86a8b98fa1..01a668a933 100644 --- a/modules/server/src/test/java/TestA/TestVersion.java +++ b/modules/server/src/test/java/TestA/TestVersion.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package TestA; @@ -29,7 +16,7 @@ /** * @author bwcx_jzy - * @date 2019/8/28 + * @since 2019/8/28 */ public class TestVersion { diff --git a/modules/server/src/test/java/TestCert.java b/modules/server/src/test/java/TestCert.java deleted file mode 100644 index 168f40602a..0000000000 --- a/modules/server/src/test/java/TestCert.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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.resource.ResourceUtil; -import cn.hutool.crypto.PemUtil; -import cn.hutool.crypto.asymmetric.KeyType; -import cn.hutool.crypto.asymmetric.RSA; - -import java.security.PrivateKey; -import java.security.PublicKey; - -/** - * Created by jiangzeyin on 2019/3/7. - */ -public class TestCert { - public static void main(String[] args) { -// HttpRequest request = HttpUtil.createPost("https://myssl.com/api/v1/tools/cert_decode"); -// request.form("certfile", new File("D:\\SystemDocument\\Desktop\\web_hulianwangjia\\full_chain.pem")); -// request.form("type", "upload"); -// HttpResponse response = request.execute(); -// System.out.println(response.body()); -// D:\SystemDocument\Desktop - - PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("D:\\SystemDocument\\Desktop\\1979263_jpom.keepbx.cn.key")); - PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("D:\\SystemDocument\\Desktop\\1979263_jpom.keepbx.cn.pem")); - - RSA rsa = new RSA(privateKey, publicKey); - String str = "你好,Hutool";//测试字符串 - - String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); - String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); - System.out.println(encryptStr); - System.out.println(decryptStr); - System.out.println(str.equals(decryptStr)); - } -} diff --git a/modules/server/src/test/java/TestCommandWget.java b/modules/server/src/test/java/TestCommandWget.java new file mode 100644 index 0000000000..113c589e8c --- /dev/null +++ b/modules/server/src/test/java/TestCommandWget.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.util.CommandUtil; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.function.BiConsumer; + +public class TestCommandWget { + + + @Test + public void test() throws IOException, InterruptedException { + File userHomeDir = FileUtil.getUserHomeDir(); + String url = "https://mirrors.aliyun.com/apache/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz"; + File file = FileUtil.file(userHomeDir, ".jpom", "test-curl." + CommandUtil.SUFFIX); + // curl -LfSo maven.tar.gz https://mirrors.aliyun.com/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz + FileUtil.writeUtf8String(StrUtil.format("curl -LfSo {}/maven.tar.gz {}", userHomeDir.getAbsolutePath(), url), file); +// + long time = System.currentTimeMillis(); + int waitFor = CommandUtil.execWaitFor(file, null, null, StrUtil.EMPTY, new BiConsumer() { + @Override + public void accept(String s, Process process) { + System.out.println(s); + } + }); + System.out.println(waitFor + " " + DateUtil.formatBetween(System.currentTimeMillis() - time)); + } + +} diff --git a/modules/server/src/test/java/TestCpu.java b/modules/server/src/test/java/TestCpu.java index 82f446b4db..9794f8a6ef 100644 --- a/modules/server/src/test/java/TestCpu.java +++ b/modules/server/src/test/java/TestCpu.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import com.sun.management.OperatingSystemMXBean; @@ -27,7 +14,7 @@ import java.lang.management.ManagementFactory; /** - * Created by jiangzeyin on 2019/3/15. + * Created by bwcx_jzy on 2019/3/15. */ public class TestCpu { public static void main(String[] args) { @@ -49,8 +36,8 @@ public static String getMemery() { // 剩余的物理内存 long freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize(); - Double compare = (1 - freePhysicalMemorySize * 1.0 / totalvirtualMemory) * 100; - return "内存已使用:" + compare.intValue() + "%"; + double compare = (1 - freePhysicalMemorySize * 1.0 / totalvirtualMemory) * 100; + return "内存已使用:" + (int) compare + "%"; } public static String getCpuRatio() { @@ -102,16 +89,16 @@ private static long[] readCpu(final Process proc) { String s1 = substring(line, kmtidx, rocidx - 1).trim(); String s2 = substring(line, umtidx, wocidx - 1).trim(); if (caption.equals("System Idle Process") || caption.equals("System")) { - if (s1.length() > 0) - idletime += Long.valueOf(s1); - if (s2.length() > 0) - idletime += Long.valueOf(s2); + if (!s1.isEmpty()) + idletime += Long.parseLong(s1); + if (!s2.isEmpty()) + idletime += Long.parseLong(s2); continue; } - if (s1.length() > 0) - kneltime += Long.valueOf(s1); - if (s2.length() > 0) - usertime += Long.valueOf(s2); + if (!s1.isEmpty()) + kneltime += Long.parseLong(s1); + if (!s2.isEmpty()) + usertime += Long.parseLong(s2); } retn[0] = idletime; retn[1] = kneltime + usertime; diff --git a/modules/server/src/test/java/TestCrawlingAdoptiumJdk.java b/modules/server/src/test/java/TestCrawlingAdoptiumJdk.java new file mode 100644 index 0000000000..7607957faa --- /dev/null +++ b/modules/server/src/test/java/TestCrawlingAdoptiumJdk.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2022/8/9 + */ +public class TestCrawlingAdoptiumJdk { + + @Test + public void test() throws Exception { + HttpRequest httpRequest = HttpUtil.createGet("https://mirrors.tuna.tsinghua.edu.cn/Adoptium/"); + String html = httpRequest.thenFunction(HttpResponse::body); + //使用正则获取所有 url + List urls = ReUtil.findAll("(.*?)", html, 1); + List versions = urls.stream() + .map(s -> { + List all = ReUtil.findAllGroup0("title=\"\\d+\"", s); + return CollUtil.getFirst(all); + }) + .filter(StrUtil::isNotEmpty) + .map(ReUtil::getFirstNumber) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + + StringBuilder builder = StrUtil.builder(); + builder.append("case \"${JAVA_VERSION}_${ARCH}\" in").append(StrUtil.LF); + StringBuilder tipBuilder = StrUtil.builder(); + for (Integer version : versions) { + Map jdkItem = jdkItem(version); + Set> entries = jdkItem.entrySet(); + // + + for (Map.Entry entry : entries) { + builder.append(StrUtil.format("{}_{})", version, entry.getKey())).append(StrUtil.LF); + builder.append(StrUtil.format("\tdownload_url=\"{}\"", entry.getValue())).append(StrUtil.LF); + builder.append("\t;;").append(StrUtil.LF); + } + tipBuilder.append(version).append("(").append(CollUtil.join(jdkItem.keySet(), StrUtil.COMMA)).append(")").append(StrUtil.SPACE); + } + builder.append("*)\n" + "\techo \"目前只支持 ").append(tipBuilder).append("\"\n").append("\texit 1\n").append("\t;;\n").append("esac"); + System.out.println(builder); +// for (String title : urls) { +// //打印标题 +// Console.log(title); +// } + } + + private Map jdkItem(int version) { + String versionUrl = StrUtil.format("https://mirrors.tuna.tsinghua.edu.cn/Adoptium/{}/jdk/", version); + HttpRequest httpRequest = HttpUtil.createGet(versionUrl); + String html = httpRequest.thenFunction(HttpResponse::body); + + List urls = ReUtil.findAll("(.*?)", html, 1); + return urls.stream() + .map(s -> { + List all = ReUtil.findAll("title=\"(.*?)\"", s, 1); + return CollUtil.getFirst(all); + }) + .filter(StrUtil::isNotEmpty) + .map(s -> { + String typeUrl = StrUtil.format("https://mirrors.tuna.tsinghua.edu.cn/Adoptium/{}/jdk/{}/linux/", version, s); + HttpRequest httpRequest1 = HttpUtil.createGet(typeUrl); + String html1 = httpRequest1.thenFunction(HttpResponse::body); + List urls1 = ReUtil.findAll("(.*?)", html1, 1); + List durl = urls1.stream() + .map(s1 -> { + List all = ReUtil.findAll("title=\"OpenJDK(.*?).tar.gz\"", s1, 0); + all = all.stream() + .filter(Objects::nonNull) + .map(s2 -> { + List all1 = ReUtil.findAll("title=\"(.*?)\"", s2, 1); + return CollUtil.getFirst(all1); + }) + .collect(Collectors.toList()); + return CollUtil.getFirst(all); + }) + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toList()); + + String first = CollUtil.getFirst(durl); + if (StrUtil.isEmpty(first)) { + return null; + } + return new Tuple(s, StrUtil.format("{}{}", typeUrl, first)); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(o -> o.get(0), objects -> objects.get(1))); + } +} diff --git a/modules/server/src/test/java/TestCron.java b/modules/server/src/test/java/TestCron.java index f3b75cdebd..266cd48dc5 100644 --- a/modules/server/src/test/java/TestCron.java +++ b/modules/server/src/test/java/TestCron.java @@ -1,39 +1,55 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; import cn.hutool.cron.CronUtil; +import cn.hutool.cron.pattern.CronPattern; +import cn.hutool.cron.pattern.CronPatternUtil; +import org.junit.Test; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; /** - * Created by jiangzeyin on 2019/3/4. + * Created by bwcx_jzy on 2019/3/4. */ public class TestCron { public static void main(String[] args) { String CRON_ID = "test"; CronUtil.remove(CRON_ID); CronUtil.setMatchSecond(true); - CronUtil.schedule(CRON_ID, "0/5 * * * * ?", () -> { - System.out.println("123"); - }); + CronUtil.schedule(CRON_ID, "0/5 * * * * ?", () -> System.out.println("123")); CronUtil.restart(); // System.out.println(JpomApplicationEvent.getPid()); } + + @Test + public void test() { + String cron = "0 0 23 ? * 5 "; + + CronPattern cronPattern = CronPattern.of(cron); + +// Date date = CronPatternUtil.nextDateAfter(cronPattern, DateUtil.offsetDay(DateTime.now(), -1), false); + + List dateList = CronPatternUtil.matchedDates(cron, DateUtil.offsetDay(DateTime.now(), -1), 10, true); + for (Date date1 : dateList) { + System.out.println(DateUtil.format(date1, DatePattern.NORM_DATETIME_FORMAT)); + } + + } + + @Test + public void test2() { + Calendar calendar = DateTime.now().toCalendar(); + System.out.println(calendar.get(Calendar.DAY_OF_WEEK) - 1); + } } diff --git a/modules/server/src/test/java/TestFile.java b/modules/server/src/test/java/TestFile.java index 83c5f30a53..3cc3a0e6c1 100644 --- a/modules/server/src/test/java/TestFile.java +++ b/modules/server/src/test/java/TestFile.java @@ -1,33 +1,26 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.io.CharsetDetector; import cn.hutool.core.io.FileUtil; +import cn.hutool.crypto.SecureUtil; +import org.dromara.jpom.util.CommandUtil; import org.junit.Test; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.FileSystems; +import java.nio.file.Path; /** - * Created by jiangzeyin on 2019/3/15. + * Created by bwcx_jzy on 2019/3/15. */ public class TestFile { public static void main(String[] args) throws IOException { @@ -43,10 +36,55 @@ public static void main(String[] args) throws IOException { } + @Test + public void testDel() { + // xxx.pdf + // xxx (1).pdf + CommandUtil.systemFastDel(new File("/Users/user/Downloads/xxx (1).pdf")); + } + + @Test public void testFile() { File file = FileUtil.file("D:\\Idea\\hutool\\.git"); System.out.println(file.isHidden()); + + System.out.println(new File("../").getAbsoluteFile()); + } + + @Test + public void testSystemFile() { + Path path = FileSystems.getDefault().getPath("~", "runs", "../Dockerfile"); + System.out.println(path); + + Path path1 = FileSystems.getDefault().getPath("~/runs/../Dockerfile"); + System.out.println(path1); + + System.out.println(FileUtil.file("~", "runs/../", "/Dockerfile")); + + } + + @Test + public void testFileExt() { + String extName = FileUtil.extName("canal.deployer-1.1.7-SNAPSHOT.tar.gz"); + System.out.println(extName); + } + + @Test + public void testMd5() { + File file = FileUtil.file("D:\\迅雷下载\\zh-cn_windows_11_business_editions_version_22h2_updated_sep_2022_x64_dvd_515a832b.iso"); + File file1 = FileUtil.file("D:\\迅雷下载\\zh-cn_windows_11_business_editions_version_22h2_updated_sep_2022_x64_dvd_515a832b (1).iso"); + File file2 = FileUtil.file("D:\\迅雷下载\\zh-cn_windows_11_business_editions_version_22h2_updated_sep_2022_x64_dvd_515a832b (2).iso"); + System.out.println(SecureUtil.md5(file)); + System.out.println(SecureUtil.md5(file1)); + System.out.println(SecureUtil.md5(file2)); + } + + @Test + public void testFilecd() { + File file = FileUtil.file("D:\\System-Data\\Downloads\\导出的 ssh 数据 2023-12-25.csv"); + Charset detect = CharsetDetector.detect(file); + System.out.println(detect); } } diff --git a/modules/server/src/test/java/TestFileModify.java b/modules/server/src/test/java/TestFileModify.java index 3277450b88..9c33e6ffce 100644 --- a/modules/server/src/test/java/TestFileModify.java +++ b/modules/server/src/test/java/TestFileModify.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.watch.SimpleWatcher; @@ -31,7 +18,7 @@ /** * @author bwcx_jzy - * @date 2019/8/26 + * @since 2019/8/26 */ public class TestFileModify { public static void main(String[] args) { diff --git a/modules/server/src/test/java/TestFileWatch.java b/modules/server/src/test/java/TestFileWatch.java index 7e6c0422a9..4336521780 100644 --- a/modules/server/src/test/java/TestFileWatch.java +++ b/modules/server/src/test/java/TestFileWatch.java @@ -1,37 +1,58 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +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; import java.nio.file.WatchEvent; /** - * Created by jiangzeyin on 2018/10/2. + * 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 new file mode 100644 index 0000000000..5cd5299a6b --- /dev/null +++ b/modules/server/src/test/java/TestFun.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.util.StrUtil; +import org.junit.Test; +import org.springframework.util.Assert; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; + +/** + * @author bwcx_jzy + * @since 2022/6/15 + */ +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"); + return false; + }); + AFTER_CALLBACK.add(() -> { + System.out.println("2"); + return true; + }); + AFTER_CALLBACK.add(() -> { + System.out.println("3"); + return false; + }); + long count = AFTER_CALLBACK.stream().mapToInt(value -> value.get() ? 1 : 0).count(); + System.out.println("123+" + count); + } +} diff --git a/modules/server/src/test/java/TestHuUpload.java b/modules/server/src/test/java/TestHuUpload.java index 0efe7089bc..5ba335f74d 100644 --- a/modules/server/src/test/java/TestHuUpload.java +++ b/modules/server/src/test/java/TestHuUpload.java @@ -1,28 +1,15 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ /** * @author bwcx_jzy - * @date 2019/9/6 + * @since 2019/9/6 */ public class TestHuUpload { diff --git a/modules/server/src/test/java/TestIntegral.java b/modules/server/src/test/java/TestIntegral.java new file mode 100644 index 0000000000..5e4fa0f6e6 --- /dev/null +++ b/modules/server/src/test/java/TestIntegral.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.IntSummaryStatistics; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/6/14 + */ +public class TestIntegral { + + public static void main(String[] args) { + List list = FileUtil.readLines("D:\\System-Data\\Documents\\WeChat Files\\A22838106\\FileStorage\\File\\2023-06\\积分签到.ini", CharsetUtil.CHARSET_GBK); + List> collect = list.stream() + .filter(s -> StrUtil.contains(s, "目前积分")) + .map(s -> StrUtil.splitTrim(s, "=")) + .collect(Collectors.toList()); + // + Map statisticsMap = CollStreamUtil.groupBy(collect, strings -> strings.get(0), Collectors.summarizingInt(value -> Convert.toInt(value.get(1)))); + // + List> sortedList = new ArrayList<>(statisticsMap.entrySet()); + sortedList.sort(Map.Entry.comparingByValue((o1, o2) -> CompareUtil.compare(o2.getSum(), o1.getSum()))); + // + List> subbed = CollUtil.sub(sortedList, 0, 10); + for (Map.Entry statisticsEntry : subbed) { + System.out.println(statisticsEntry.getKey() + " " + statisticsEntry.getValue().getSum()); + } + } +} diff --git a/modules/server/src/test/java/TestIp.java b/modules/server/src/test/java/TestIp.java index 8f29036192..bf25dd8e77 100644 --- a/modules/server/src/test/java/TestIp.java +++ b/modules/server/src/test/java/TestIp.java @@ -1,42 +1,118 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.net.Ipv4Util; import cn.hutool.core.net.NetUtil; import org.junit.Test; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.LinkedHashSet; + /** * @author bwcx_jzy * @since Created Time 2021/8/4 */ public class TestIp { - @Test - public void test() { - System.out.println(NetUtil.getLocalhostStr()); - System.out.println(NetUtil.getLocalhost().getHostAddress()); - } + @Test + public void test() { + System.out.println(NetUtil.getLocalhostStr()); +// System.out.println(NetUtil.getLocalhost().getHostAddress()); + System.out.println("------"); + final LinkedHashSet localAddressList = NetUtil.localAddressList(networkInterface -> { + System.out.println(networkInterface.isVirtual()); + System.out.println(networkInterface.getIndex()); + return true; + }, address -> { + // 非loopback地址,指127.*.*.*的地址 + return !address.isLoopbackAddress() + // 需为IPV4地址 + && address instanceof Inet4Address; + }); + for (InetAddress inetAddress : localAddressList) { + System.out.println(inetAddress.getHostAddress()); + //System.out.println(((Inet4Address) inetAddress).toString()); + //System.out.println("-"); + } + } + + @Test + public void test1() { + String localHostName = NetUtil.getLocalHostName(); + System.out.println(localHostName); + } + + @Test + public void testConnect() { + { + InetSocketAddress address = NetUtil.createAddress("127.0.0.1", 2322); + boolean open = NetUtil.isOpen(address, 5 * 1000); + System.out.println(open); + } + { + InetSocketAddress address = NetUtil.createAddress("baidu.com", 80); + boolean open = NetUtil.isOpen(address, 5 * 1000); + System.out.println(open); + } + { + InetSocketAddress address = NetUtil.createAddress("baidu.com", 443); + boolean open = NetUtil.isOpen(address, 5 * 1000); + System.out.println(open); + } + } + + @Test + public void testPublic() { + long ipNum = Ipv4Util.ipv4ToLong("222.67.57.32"); + + // + long pBegin = Ipv4Util.ipv4ToLong("20.0.0.0"); + long pEnd = Ipv4Util.ipv4ToLong("223.255.255.255"); + System.out.println(isInner(ipNum, pBegin, pEnd)); + } + + @Test + public void testPing() { + boolean ping = NetUtil.ping("baidu.com", 2 * 1000); + System.out.println(ping); + System.out.println(NetUtil.ping("192.168.30.1", 2 * 1000)); + } + + @Test + public void ICMPPing() { + try { + InetAddress address = InetAddress.getByName("192.168.30.1"); + Socket socket = new Socket(address, 0); + + // 发送Ping请求 + String request = "Ping请求"; + socket.getOutputStream().write(request.getBytes()); + + // 接收Ping响应 + byte[] buffer = new byte[1024]; + int length = socket.getInputStream().read(buffer); + String response = new String(buffer, 0, length); + + // 处理Ping响应 + System.out.println("Ping响应: " + response); - @Test - public void test1() { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } - } + private static boolean isInner(long userIp, long begin, long end) { + return (userIp >= begin) && (userIp <= end); + } } diff --git a/modules/server/src/test/java/TestJarClass.java b/modules/server/src/test/java/TestJarClass.java index 3b64141411..c5dc889862 100644 --- a/modules/server/src/test/java/TestJarClass.java +++ b/modules/server/src/test/java/TestJarClass.java @@ -1,29 +1,16 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -import cn.hutool.Hutool; import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import io.jpom.common.JpomManifest; +import org.dromara.jpom.common.JpomManifest; import org.junit.Test; import java.io.IOException; @@ -33,20 +20,21 @@ import java.util.jar.Manifest; /** - * Created by jiangzeyin on 2019/4/13. + * Created by bwcx_jzy on 2019/4/13. */ public class TestJarClass { - @Test - public void test(){ - JarFile jarFile = URLUtil.getJarFile(Hutool.class.getResource("")); - System.out.println(jarFile.getName()); - URL location = ClassUtil.getLocation(JpomManifest.class); - System.out.println(location); - String location1 = ClassUtil.getLocationPath(JpomManifest.class); - System.out.println(location1); - } + @Test + public void test() { + JarFile jarFile = URLUtil.getJarFile(StrUtil.class.getResource("")); + System.out.println(jarFile.getName()); + URL location = ClassUtil.getLocation(JpomManifest.class); + System.out.println(location); + + String location1 = ClassUtil.getLocationPath(JpomManifest.class); + System.out.println(location1); + } public static void main(String[] args) throws IOException { diff --git a/modules/server/src/test/java/TestJarLoader.java b/modules/server/src/test/java/TestJarLoader.java index 88b469d0fb..3a806f0ded 100644 --- a/modules/server/src/test/java/TestJarLoader.java +++ b/modules/server/src/test/java/TestJarLoader.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import java.io.File; import java.io.IOException; @@ -27,14 +14,13 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.*; -import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; /** * @author bwcx_jzy - * @date 2019/7/23 + * @since 2019/7/23 **/ public class TestJarLoader { public static void main(String[] args) throws ClassNotFoundException, IOException { @@ -42,9 +28,9 @@ public static void main(String[] args) throws ClassNotFoundException, IOExceptio ZipEntry zipEntry = jarFile1.getEntry("BOOT-INF/classes/cn/keepbx/jpom/common/interceptor/NotAuthorize.class"); System.out.println(zipEntry); String path = "D:\\Idea\\Jpom\\modules\\agent\\target\\agent-2.4.2.jar";//外部jar包的路径 - Set> classes = new LinkedHashSet>();//所有的Class对象 - Map, Annotation[]> classAnnotationMap = new HashMap, Annotation[]>();//每个Class对象上的注释对象 - Map, Map> classMethodAnnoMap = new HashMap, Map>();//每个Class对象中每个方法上的注释对象 + Set> classes = new LinkedHashSet<>();//所有的Class对象 + Map, Annotation[]> classAnnotationMap = new HashMap<>();//每个Class对象上的注释对象 + Map, Map> classMethodAnnoMap = new HashMap<>();//每个Class对象中每个方法上的注释对象 try { JarFile jarFile = new JarFile(new File(path)); URL url = new URL("file:" + path); @@ -64,18 +50,16 @@ public static void main(String[] args) throws ClassNotFoundException, IOExceptio Annotation[] classAnnos = c.getDeclaredAnnotations(); classAnnotationMap.put(c, classAnnos); Method[] classMethods = c.getDeclaredMethods(); - Map methodAnnoMap = new HashMap(); - for (int i = 0; i < classMethods.length; i++) { - Annotation[] a = classMethods[i].getDeclaredAnnotations(); - methodAnnoMap.put(classMethods[i], a); + Map methodAnnoMap = new HashMap<>(); + for (Method classMethod : classMethods) { + Annotation[] a = classMethod.getDeclaredAnnotations(); + methodAnnoMap.put(classMethod, a); } classMethodAnnoMap.put(c, methodAnnoMap); } } System.out.println(classes.size()); - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { + } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } diff --git a/modules/server/src/test/java/TestJavaInfo.java b/modules/server/src/test/java/TestJavaInfo.java index 9d775c8a72..3a14c2f928 100644 --- a/modules/server/src/test/java/TestJavaInfo.java +++ b/modules/server/src/test/java/TestJavaInfo.java @@ -1,47 +1,12 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -///* -// * The MIT License (MIT) -// * -// * Copyright (c) 2019 码之科技工作室 -// * -// * 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.system.SystemUtil; //import io.jpom.util.JvmUtil; //import com.sun.tools.attach.AgentInitializationException; @@ -51,7 +16,7 @@ //import java.io.IOException; // ///** -// * Created by jiangzeyin on 2019/3/20. +// * Created by bwcx_jzy on 2019/3/20. // */ //public class TestJavaInfo { // public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, ClassNotFoundException { diff --git a/modules/server/src/test/java/TestJavaPath.java b/modules/server/src/test/java/TestJavaPath.java index 071bbfbec2..e26c84818f 100644 --- a/modules/server/src/test/java/TestJavaPath.java +++ b/modules/server/src/test/java/TestJavaPath.java @@ -1,33 +1,20 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -import io.jpom.util.CommandUtil; +import org.dromara.jpom.util.CommandUtil; import org.junit.Test; import java.util.Map; /** * @author bwcx_jzy - * @date 2019/9/28 + * @since 2019/9/28 */ public class TestJavaPath { diff --git a/modules/server/src/test/java/TestJavaTail.java b/modules/server/src/test/java/TestJavaTail.java index 9e26b1f18c..8acb1f96d2 100644 --- a/modules/server/src/test/java/TestJavaTail.java +++ b/modules/server/src/test/java/TestJavaTail.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.watch.SimpleWatcher; @@ -31,7 +18,7 @@ import java.nio.file.WatchEvent; /** - * Created by jiangzeyin on 2019/3/15. + * Created by bwcx_jzy on 2019/3/15. */ public class TestJavaTail { public static void main(String[] args) throws IOException, InterruptedException { diff --git a/modules/server/src/test/java/TestJna.java b/modules/server/src/test/java/TestJna.java new file mode 100644 index 0000000000..7d5f9b1246 --- /dev/null +++ b/modules/server/src/test/java/TestJna.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; +import org.junit.Test; + +/** + * @author bwcx_jzy + * @since 24/3/5 005 + */ +public class TestJna { + + @Test + public void test() { + // 设置你要执行的命令 + String command = "your_command_here arg1 arg2"; + + // 使用 JNA 的 Native 类加载运行时库 + // 在不同的平台上,可能需要不同的库名称 + // 例如,在 Windows 上,可以使用 "kernel32";在 Linux 上,可以使用 "c" + // 你可能需要根据实际情况进行调整 + String libraryName = "kernel32"; + CLibrary libc = (CLibrary) Native.load(libraryName, CLibrary.class); + + // 执行命令 + int result = libc.system(command); + + // 输出执行结果 + System.out.println("Exit Code: " + result); + } + + // 定义一个接口,用于加载运行时库 + public interface CLibrary extends com.sun.jna.Library { + int system(String command); + } + + + public interface CLibrary2 extends Library { + CLibrary2 INSTANCE = (CLibrary2) + Native.load((Platform.isWindows() ? "msvcrt" : "c"), + CLibrary2.class); + + void printf(String format, Object... args); + } + + @Test + public void test2() { + CLibrary2 instance = CLibrary2.INSTANCE; + System.out.println(instance); + instance.printf("Hello, World\n"); + } +} diff --git a/modules/server/src/test/java/TestJpomLogin.java b/modules/server/src/test/java/TestJpomLogin.java index bb4de4e8e9..acc351be59 100644 --- a/modules/server/src/test/java/TestJpomLogin.java +++ b/modules/server/src/test/java/TestJpomLogin.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import org.junit.Test; @@ -30,7 +17,7 @@ public class TestJpomLogin { @Test public void test(){ -// HttpRequest post = HttpUtil.createPost("https://jpom.keepbx.cn/userLogin"); +// HttpRequest post = HttpUtil.createPost("https://demo.jpom.top/userLogin"); } } diff --git a/modules/server/src/test/java/TestJschExec.java b/modules/server/src/test/java/TestJschExec.java index bd8287b1b5..4f43fa9d49 100644 --- a/modules/server/src/test/java/TestJschExec.java +++ b/modules/server/src/test/java/TestJschExec.java @@ -1,36 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.NioUtil; import cn.hutool.core.thread.SyncFinisher; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.ChannelType; import cn.hutool.extra.ssh.JschUtil; -import cn.jiangzeyin.common.DefaultSystemLog; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; import org.junit.Test; import java.io.IOException; @@ -43,8 +31,9 @@ /** * @author bwcx_jzy - * @date 2021/7/29 + * @since 2021/7/29 */ +@Slf4j public class TestJschExec { //private static final String cmd = "java -Dappliction=jpom-test-jar -jar /home/data/test/springboot-test-jar-0.0.1-SNAPSHOT.jar"; @@ -89,7 +78,7 @@ public void testShell2() throws Exception { //System.out.println(s + " " + command.length); } catch (Exception e) { e.printStackTrace(); - DefaultSystemLog.getLog().error("写入错误", e); + log.error("写入错误", e); } } }).addRepeatWorker(() -> { @@ -97,12 +86,12 @@ public void testShell2() throws Exception { byte[] buffer = new byte[1024]; int i; //如果没有数据来,线程会一直阻塞在这个地方等待数据。 - while ((i = finalInputStream.read(buffer)) != -1) { + while ((i = finalInputStream.read(buffer)) != NioUtil.EOF) { //System.out.println(i); stringBuilder.append(new String(Arrays.copyOfRange(buffer, 0, i), CharsetUtil.CHARSET_UTF_8)); } } catch (Exception e) { - DefaultSystemLog.getLog().error("读取错误", e); + log.error("读取错误", e); } }).start(); System.out.println(stringBuilder); @@ -136,7 +125,7 @@ public void test() throws IOException, JSchException { } catch (Exception e) { e.printStackTrace(); if (!StrUtil.contains(e.getMessage(), "Pipe closed")) { - DefaultSystemLog.getLog().error("读取 exec err 流发生异常", e); + log.error("读取 exec err 流发生异常", e); error[0] = "读取 exec err 流发生异常" + e.getMessage(); } } finally { @@ -148,7 +137,7 @@ public void test() throws IOException, JSchException { } catch (Exception e) { e.printStackTrace(); if (!StrUtil.contains(e.getMessage(), "Pipe closed")) { - DefaultSystemLog.getLog().error("读取 exec 流发生异常", e); + log.error("读取 exec 流发生异常", e); result[0] = "读取 exec 流发生异常" + e.getMessage(); } } finally { @@ -184,7 +173,7 @@ public void test1() throws IOException, JSchException { } catch (Exception e) { e.printStackTrace(); if (!StrUtil.contains(e.getMessage(), "Pipe closed")) { - DefaultSystemLog.getLog().error("读取 exec err 流发生异常", e); + log.error("读取 exec err 流发生异常", e); error[0] = "读取 exec err 流发生异常" + e.getMessage(); } } @@ -194,7 +183,7 @@ public void test1() throws IOException, JSchException { } catch (Exception e) { e.printStackTrace(); if (!StrUtil.contains(e.getMessage(), "Pipe closed")) { - DefaultSystemLog.getLog().error("读取 exec 流发生异常", e); + log.error("读取 exec 流发生异常", e); result[0] = "读取 exec 流发生异常" + e.getMessage(); } } @@ -225,7 +214,7 @@ public void test2() throws IOException, JSchException { } catch (Exception e) { e.printStackTrace(); if (!StrUtil.contains(e.getMessage(), "Pipe closed")) { - DefaultSystemLog.getLog().error("读取 exec 流发生异常", e); + log.error("读取 exec 流发生异常", e); result = "读取 exec 流发生异常" + e.getMessage(); } } @@ -235,7 +224,7 @@ public void test2() throws IOException, JSchException { } catch (Exception e) { e.printStackTrace(); if (!StrUtil.contains(e.getMessage(), "Pipe closed")) { - DefaultSystemLog.getLog().error("读取 exec err 流发生异常", e); + log.error("读取 exec err 流发生异常", e); error = "读取 exec err 流发生异常" + e.getMessage(); } } diff --git a/modules/server/src/test/java/TestJvm.java b/modules/server/src/test/java/TestJvm.java index 49abda3122..0ab733fefe 100644 --- a/modules/server/src/test/java/TestJvm.java +++ b/modules/server/src/test/java/TestJvm.java @@ -1,47 +1,12 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ -///* -// * The MIT License (MIT) -// * -// * Copyright (c) 2019 码之科技工作室 -// * -// * 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.system.SystemUtil; //import com.sun.tools.attach.AttachNotSupportedException; //import sun.jvmstat.monitor.*; @@ -54,7 +19,7 @@ //import java.util.Set; // ///** -// * Created by jiangzeyin on 2019/4/4. +// * Created by bwcx_jzy on 2019/4/4. // */ //public class TestJvm { // public static void main(String[] args) throws IOException, AttachNotSupportedException, MonitorException, URISyntaxException { diff --git a/modules/server/src/test/java/TestLogin.java b/modules/server/src/test/java/TestLogin.java index 22d0b2f197..4553c06b7b 100644 --- a/modules/server/src/test/java/TestLogin.java +++ b/modules/server/src/test/java/TestLogin.java @@ -1,30 +1,17 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.http.HttpUtil; /** * @author bwcx_jzy - * @date 2019/7/15 + * @since 2019/7/15 */ public class TestLogin { public static void main(String[] args) { diff --git a/modules/server/src/test/java/TestMaven.java b/modules/server/src/test/java/TestMaven.java new file mode 100644 index 0000000000..c424b75545 --- /dev/null +++ b/modules/server/src/test/java/TestMaven.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.ReUtil; +import cn.hutool.http.HttpUtil; + +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/3/21 + */ +public class TestMaven { + + public static void main(String[] args) { + String html = HttpUtil.get("https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/"); + //使用正则获取所有可用版本 + List titles = ReUtil.findAll("(.*?)", html, 1); + for (String title : titles) { + //打印标题 + Console.log(title); + } + } +} diff --git a/modules/server/src/test/java/TestPath.java b/modules/server/src/test/java/TestPath.java index ade93c34b2..530ac47c10 100644 --- a/modules/server/src/test/java/TestPath.java +++ b/modules/server/src/test/java/TestPath.java @@ -1,27 +1,13 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.comparator.VersionComparator; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; @@ -36,80 +22,91 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; -import java.util.function.Consumer; -import java.util.function.Predicate; +import java.util.ArrayList; +import java.util.List; /** * @author bwcx_jzy - * @date 2019/8/24 + * @since 2019/8/24 */ public class TestPath { - public static void main(String[] args) { - AntPathMatcher antPathMatcher = new AntPathMatcher(); - System.out.println(antPathMatcher.match("/s/**/sss.html", "//s/s/s/sss.html")); - System.out.println(antPathMatcher.match("/s/*.html", "/s/sss.html")); - System.out.println(antPathMatcher.match("2.*", "2.5")); - } - - @Test - public void testSort() { - ArrayList list = CollUtil.newArrayList("dev", "master", "v1.1", "v0.4", "v.1", "v3.5.2", "v3.6", "v3.5.3"); - list.sort((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)); - list.forEach(System.out::println); - } - - @Test - public void testFilePath() { + public static void main(String[] args) { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + System.out.println(antPathMatcher.match("/s/**/sss.html", "//s/s/s/sss.html")); + System.out.println(antPathMatcher.match("/s/*.html", "/s/sss.html")); + System.out.println(antPathMatcher.match("2.*", "2.5")); + + +// StrUtil.similar() + + + System.out.println(antPathMatcher.matchStart("/s/**/sss.html", "/s")); + } + + @Test + public void testSort() { + ArrayList list = CollUtil.newArrayList("dev", "master", "v1.1", "v0.4", "v.1", "v3.5.2", "v3.6", "v3.5.3"); + list.sort((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)); + list.forEach(System.out::println); + } + + @Test + public void testFilePath() { // List strings = FileUtil.loopFiles("~/jpom"); // for (File string : strings) { // System.out.println(string); // } - AntPathMatcher antPathMatcher = new AntPathMatcher(); - List paths = new ArrayList<>(); - File rootFile = FileUtil.file("~/jpom"); - String matchStr = "/agent/**/log"; - matchStr = FileUtil.normalize(StrUtil.SLASH + matchStr); - String finalMatchStr = matchStr; - FileUtil.walkFiles(rootFile.toPath(), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - return this.test(file); - } - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes exc) throws IOException { - return this.test(dir); - } - - private FileVisitResult test(Path path) { - String subPath = FileUtil.subPath(FileUtil.getAbsolutePath(rootFile), path.toFile()); - subPath = FileUtil.normalize(StrUtil.SLASH + subPath); - if (antPathMatcher.match(finalMatchStr, subPath)) { - paths.add(subPath); - return FileVisitResult.TERMINATE; - } - return FileVisitResult.CONTINUE; - } - }); - paths.forEach(System.out::println); - - // - //System.out.println(antPathMatcher.isPattern("sdfsadf")); - //Optional first = paths.stream().filter(s -> antPathMatcher.match("agent/**/log", s)).findFirst(); - //System.out.println(first.get()); - } - - @Test - public void test() throws MalformedURLException { - URL url = new URL("jar:file:/home/jpom/server/lib/server-2.4.8.jar!/BOOT-INF/classes!/"); - String file = url.getFile(); - String x = StrUtil.subBefore(file, "!", false); - System.out.println(x); - System.out.println(FileUtil.file(x)); - - System.out.println(StrUtil.subBefore("sssddsf", "!", false)); - - } + AntPathMatcher antPathMatcher = new AntPathMatcher(); + List paths = new ArrayList<>(); + File rootFile = FileUtil.file("~/jpom"); + String matchStr = "/agent/**/log"; + matchStr = FileUtil.normalize(StrUtil.SLASH + matchStr); + String finalMatchStr = matchStr; + FileUtil.walkFiles(rootFile.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + return this.test(file); + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes exc) throws IOException { + return this.test(dir); + } + + private FileVisitResult test(Path path) { + String subPath = FileUtil.subPath(FileUtil.getAbsolutePath(rootFile), path.toFile()); + subPath = FileUtil.normalize(StrUtil.SLASH + subPath); + if (antPathMatcher.match(finalMatchStr, subPath)) { + paths.add(subPath); + return FileVisitResult.TERMINATE; + } + return FileVisitResult.CONTINUE; + } + }); + paths.forEach(System.out::println); + + // + //System.out.println(antPathMatcher.isPattern("sdfsadf")); + //Optional first = paths.stream().filter(s -> antPathMatcher.match("agent/**/log", s)).findFirst(); + //System.out.println(first.get()); + } + + @Test + public void test() throws MalformedURLException { + URL url = new URL("jar:file:/home/jpom/server/lib/server-2.4.8.jar!/BOOT-INF/classes!/"); + String file = url.getFile(); + String x = StrUtil.subBefore(file, "!", false); + System.out.println(x); + System.out.println(FileUtil.file(x)); + + 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 0000000000..ae74d8827c --- /dev/null +++ b/modules/server/src/test/java/TestProcess.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +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/server/src/test/java/TestReadBigFile.java b/modules/server/src/test/java/TestReadBigFile.java new file mode 100644 index 0000000000..20b0b1f0a9 --- /dev/null +++ b/modules/server/src/test/java/TestReadBigFile.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.NioUtil; +import cn.hutool.core.io.unit.DataSize; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestAlgorithm; +import cn.hutool.crypto.digest.Digester; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.util.FileUtils; +import org.dromara.jpom.util.StrictSyncFinisher; +import org.junit.Test; +import org.springframework.util.Assert; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicLong; + +/** + * https://www.dandelioncloud.cn/article/details/1513013511700692994 + * https://www.cnblogs.com/yjmyzz/p/how-to-split-a-large-file-into-small-files-fastly.html + * + * @author bwcx_jzy + * @since 2023/1/10 + */ +@Slf4j +public class TestReadBigFile { + + + private File file = new File("D:\\迅雷下载\\zh-cn_windows_11_business_editions_version_22h2_updated_sep_2022_x64_dvd_515a832b.iso"); + + private File partDir = new File("D:\\test\\part"); + + private File saveDir = new File("D:\\test\\"); + + private File testFile = new File("D:\\test\\zh-cn_windows_11_business_editions_version_22h2_updated_sep_2022_x64_dvd_515a832b.iso"); + + @Test + public void test() throws IOException { + long start = System.currentTimeMillis(); + FileUtil.del(partDir); + long length = file.length(); + Assert.state(length > 0, "空文件不能上传"); + //如果小数点大于1,整数加一 例如4.1 =》5 + long chunkSize = DataSize.ofMegabytes(1).toBytes(); + int total = (int) Math.ceil((double) length / chunkSize); + Queue queueList = new ConcurrentLinkedDeque<>(); + for (int i = 0; i < total; i++) { + queueList.offer(i); + } + String fileSize = FileUtil.readableFileSize(length); + // 并发数 + int concurrent = 2; + List success = Collections.synchronizedList(new ArrayList<>(total)); + AtomicLong atomicProgressSize = new AtomicLong(0); + // 需要计算 并发数和最大任务数,如果任务数小于并发数则使用任务数 + int threadSize = Math.min(concurrent, total); + System.out.println("线程数:" + threadSize); + try (StrictSyncFinisher syncFinisher = new StrictSyncFinisher(threadSize, total)) { + Runnable runnable = () -> { + // 取出任务 + Integer currentChunk = queueList.poll(); + if (currentChunk == null) { + return; + } + try { + try (FileInputStream inputStream = new FileInputStream(file)) { + try (FileChannel inputChannel = inputStream.getChannel()) { + //分配缓冲区,设定每次读的字节数 + ByteBuffer byteBuffer = ByteBuffer.allocate((int) chunkSize); + // 移动到指定位置开始读取 + inputChannel.position(currentChunk * chunkSize); + inputChannel.read(byteBuffer); + //上面把数据写入到了buffer,所以可知上面的buffer是写模式,调用flip把buffer切换到读模式,读取数据 + byteBuffer.flip(); + byte[] array = new byte[byteBuffer.remaining()]; + byteBuffer.get(array, 0, array.length); + success.add(currentChunk); + File partFile = FileUtil.file(partDir, file.getName() + "." + currentChunk); + FileUtil.mkParentDirs(partFile); + FileUtil.writeBytes(array, partFile); + long end = Math.min(length, ((success.size() - 1) * chunkSize) + chunkSize); + // 保存线程安全顺序回调进度信息 + atomicProgressSize.set(Math.max(end, atomicProgressSize.get())); + long size = atomicProgressSize.get(); + log.info("{} {} {},读取文件 {} 内容花费:{}", total, fileSize, FileUtil.readableFileSize(size), NumberUtil.formatPercent(((double) size / length), 0), DateUtil.formatBetween(SystemClock.now() - start)); + } + } + + } catch (Exception e) { + log.error("分片上传文件异常", e); + // 终止上传 + queueList.clear(); + } + }; + for (int i = 0; i < total; i++) { + syncFinisher.addWorker(runnable); + } + syncFinisher.start(); + } + log.info("读取文件内容花费:" + DateUtil.formatBetween(SystemClock.now() - start)); + } + + @Test + public void testMerge() throws IOException { + long start = SystemClock.now(); + File[] files = partDir.listFiles(); + + long start2 = SystemClock.now(); + String fileSumMd5 = SecureUtil.md5(file); + log.info("解析文件耗时:{}", DateUtil.formatBetween(SystemClock.now() - start2)); + assert files != null; + String name = files[0].getName(); + name = StrUtil.subBefore(name, StrUtil.DOT, true); + File successFile = FileUtil.file(saveDir, name); + FileUtil.del(successFile); + start2 = SystemClock.now(); + try (FileOutputStream fileOutputStream = new FileOutputStream(successFile)) { + try (FileChannel channel = fileOutputStream.getChannel()) { + Arrays.stream(files).sorted((o1, o2) -> { + // 排序 + Integer o1Int = Convert.toInt(FileUtil.extName(o1)); + Integer o2Int = Convert.toInt(FileUtil.extName(o2)); + return o1Int.compareTo(o2Int); + }).forEach(file12 -> { + try { + //channel.write(ByteBuffer.wrap(FileUtil.readBytes(file12))); + FileUtils.appendChannel(file12, channel); + //log.info("合并 {} 文件花费:{}", file12.getName(), DateUtil.formatBetween(SystemClock.now() - start)); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + } + } + log.info("合并文件耗时:{}", DateUtil.formatBetween(SystemClock.now() - start2)); + // 对比文件信息 + start2 = SystemClock.now(); + String newSha1 = SecureUtil.md5(successFile); + log.info("解析文件耗时:{}", DateUtil.formatBetween(SystemClock.now() - start2)); + Assert.state(StrUtil.equals(newSha1, fileSumMd5), () -> { + log.warn("文件合并异常 {}:{} -> {}", FileUtil.getAbsolutePath(successFile), newSha1, fileSumMd5); + return "文件合并后异常,文件不完成可能被损坏"; + }); + log.info("合并+解析文件花费:" + DateUtil.formatBetween(SystemClock.now() - start)); + } + + @Test + public void testMerge2() throws IOException { + long start = SystemClock.now(); + File[] files = partDir.listFiles(); + long start2 = SystemClock.now(); + String fileSumMd5 = SecureUtil.sha1(file); + log.info("解析文件耗时:{}", DateUtil.formatBetween(SystemClock.now() - start2)); + assert files != null; + String name = files[0].getName(); + name = StrUtil.subBefore(name, StrUtil.DOT, true); + File successFile = FileUtil.file(saveDir, name); + FileUtil.del(successFile); + start2 = SystemClock.now(); + // + // 预分配文件所占的磁盘空间,磁盘中会创建一个指定大小的文件 + try (RandomAccessFile raf = new RandomAccessFile(successFile, "rw")) { + // 预分配文件空间 + raf.setLength(file.length()); + } + + try (StrictSyncFinisher syncFinisher = new StrictSyncFinisher(20, files.length)) { + Arrays.stream(files).sorted((o1, o2) -> { + // 排序 + Integer o1Int = Convert.toInt(FileUtil.extName(o1)); + Integer o2Int = Convert.toInt(FileUtil.extName(o2)); + return o1Int.compareTo(o2Int); + }).forEach(file12 -> { + long chunkSize = DataSize.ofMegabytes(1).toBytes(); + syncFinisher.addWorker(() -> { + try (RandomAccessFile raf = new RandomAccessFile(successFile, "rw")) { + Integer o1Int = Convert.toInt(FileUtil.extName(file12)); + raf.seek(o1Int * chunkSize); + raf.write(FileUtil.readBytes(file12)); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + }); + }); + syncFinisher.start(); + } + + log.info("合并文件耗时:{}", DateUtil.formatBetween(SystemClock.now() - start2)); + // 对比文件信息 + start2 = SystemClock.now(); + String newSha1 = SecureUtil.sha1(successFile); + log.info("解析文件耗时:{}", DateUtil.formatBetween(SystemClock.now() - start2)); + Assert.state(StrUtil.equals(newSha1, fileSumMd5), () -> { + log.warn("文件合并异常 {}:{} -> {}", FileUtil.getAbsolutePath(successFile), newSha1, fileSumMd5); + return "文件合并后异常,文件不完成可能被损坏"; + }); + log.info("合并+解析文件花费:" + DateUtil.formatBetween(SystemClock.now() - start)); + } + + /** + * e0e4708889197545603b4631de1f4a90dbcb2435 + * + * @throws FileNotFoundException + */ + @Test + public void testSha1() throws FileNotFoundException { + Digester digester = new Digester(DigestAlgorithm.SHA1); + String digestHex = digester.digestHex(new FileInputStream(file), NioUtil.DEFAULT_LARGE_BUFFER_SIZE); + System.out.println(digestHex); + } + + @Test + public void testSha1_2() throws IOException, NoSuchAlgorithmException { +// Digester digester = new Digester(DigestAlgorithm.SHA1); + MessageDigest messagedigest = MessageDigest.getInstance("SHA-1"); + FileInputStream in = new FileInputStream(file); + + FileChannel ch = in.getChannel(); + + MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + + messagedigest.update(byteBuffer); + + String hexStr = HexUtil.encodeHexStr(messagedigest.digest()); + + System.out.println(hexStr); + } + + @Test + public void testSha1_3() throws IOException { + String sha1 = SecureUtil.sha1(testFile); + System.out.println(sha1); + Digester digester = SecureUtil.sha1(); + OutputStream is = Files.newOutputStream(testFile.toPath()); + DigestOutputStream di = new DigestOutputStream(is, digester.getDigest()); + MessageDigest messageDigest = di.getMessageDigest(); + System.out.println(HexUtil.encodeHexStr(messageDigest.digest())); + } + + @Test + public void compared_md5_sha1() { + TimeInterval timeInterval = new TimeInterval(); + // + timeInterval.start("md5"); + System.out.println(SecureUtil.md5(file)); + System.out.println(timeInterval.intervalPretty("md5")); + // + timeInterval.start("sha1"); + System.out.println(SecureUtil.sha1(file)); + System.out.println(timeInterval.intervalPretty("sha1")); + // + } +} diff --git a/modules/server/src/test/java/TestRun.java b/modules/server/src/test/java/TestRun.java index bda128fefa..cff69cd5f6 100644 --- a/modules/server/src/test/java/TestRun.java +++ b/modules/server/src/test/java/TestRun.java @@ -1,127 +1,105 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.LineHandler; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.CharsetUtil; -import io.jpom.JpomApplication; +import org.dromara.jpom.system.ExtConfigBean; import java.io.*; public class TestRun { - public static void main(String[] args) throws IOException, InterruptedException { - testProcessBuilder("D:\\jpom\\agent\\script\\test\\script.bat"); + public static void main(String[] args) throws IOException, InterruptedException { + testProcessBuilder("D:\\jpom\\agent\\script\\test\\script.bat"); - } + } - public static void testProcessBuilder(String... command) { - boolean err = false; - try { - final int[] count = {0}; - ProcessBuilder processBuilder = new ProcessBuilder(command); - //初始化ProcessBuilder对象 + public static void testProcessBuilder(String... command) { + boolean err = false; + try { + final int[] count = {0}; + ProcessBuilder processBuilder = new ProcessBuilder(command); + //初始化ProcessBuilder对象 // processBuilder.inheritIO() - Process p = processBuilder.start(); - OutputStream outputStream = p.getOutputStream(); - InputStream inputStream = p.getInputStream(); - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - InputStreamReader inputStreamReader = new InputStreamReader(inputStream, CharsetUtil.CHARSET_GBK); - //用于存储执行命令的结果 - BufferedReader results = new BufferedReader(inputStreamReader); - IoUtil.readLines(results, new LineHandler() { - @Override - public void handle(String line) { + Process p = processBuilder.start(); + OutputStream outputStream = p.getOutputStream(); + InputStream inputStream = p.getInputStream(); + ThreadUtil.execute(() -> { + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, CharsetUtil.CHARSET_GBK); + //用于存储执行命令的结果 + BufferedReader results = new BufferedReader(inputStreamReader); + IoUtil.readLines(results, (LineHandler) line -> { // String result = CharsetUtil.convert(line, CharsetUtil.CHARSET_ISO_8859_1, CharsetUtil.CHARSET_GBK); - System.out.println(line); - count[0]++; - if (count[0] == 10) { - processBuilder.inheritIO(); - try { - outputStream.write(2); - outputStream.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - results.close(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - inputStreamReader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - outputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - System.out.println("3*****"); - try { - p.waitFor(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - try { - p.getErrorStream().close(); - } catch (IOException e) { - e.printStackTrace(); - } - System.out.println(123456); - p.destroy(); - } + System.out.println(line); + count[0]++; + if (count[0] == 10) { + processBuilder.inheritIO(); + try { + outputStream.write(2); + outputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); } - }); - System.out.println("123"); - //用于存储执行命令的错误信息 - BufferedReader errors = new BufferedReader(new InputStreamReader(p.getErrorStream(), CharsetUtil.CHARSET_GBK)); - IoUtil.readLines(errors, new LineHandler() { - @Override - public void handle(String line) { - - String result = CharsetUtil.convert(line, null, JpomApplication.getCharset()); - System.out.println("error:" + result); + try { + results.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + inputStreamReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); } - }); - try { - p.waitFor(); - } catch (InterruptedException e) { - e.printStackTrace(); + System.out.println("3*****"); + try { + p.waitFor(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + try { + p.getErrorStream().close(); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println(123456); + p.destroy(); } + }); + System.out.println("123"); + //用于存储执行命令的错误信息 + BufferedReader errors = new BufferedReader(new InputStreamReader(p.getErrorStream(), ExtConfigBean.getConsoleLogCharset())); + IoUtil.readLines(errors, (LineHandler) line -> { + + String result = CharsetUtil.convert(line, null, ExtConfigBean.getConsoleLogCharset()); + System.out.println("error:" + result); + }); + try { + p.waitFor(); + } catch (InterruptedException e) { + e.printStackTrace(); } }); - System.out.println("end"); - } catch (Exception e) { - e.printStackTrace(); - } - } + System.out.println("end"); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/modules/server/src/test/java/TestSSh.java b/modules/server/src/test/java/TestSSh.java index 16394db071..984512bd16 100644 --- a/modules/server/src/test/java/TestSSh.java +++ b/modules/server/src/test/java/TestSSh.java @@ -1,40 +1,29 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.LineHandler; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.ChannelType; import cn.hutool.extra.ssh.JschUtil; -import com.jcraft.jsch.*; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.Session; +import org.junit.Test; import java.io.*; -import java.nio.charset.Charset; /** * @author bwcx_jzy - * @date 2019/8/8 + * @since 2019/8/8 */ public class TestSSh { private static String charset = "UTF-8"; // 设置编码格式 @@ -49,9 +38,7 @@ public static void main(String[] args) throws Exception { channel.setPty(true); channel.connect(); InputStream in = channel.getInputStream(); - ThreadUtil.execute(() -> { - IoUtil.readLines(in, CharsetUtil.CHARSET_UTF_8, (LineHandler) System.out::println); - }); + ThreadUtil.execute(() -> IoUtil.readLines(in, CharsetUtil.CHARSET_UTF_8, (LineHandler) System.out::println)); ThreadUtil.execute(() -> { try { IoUtil.readLines(channel.getExtInputStream(), CharsetUtil.CHARSET_UTF_8, (LineHandler) System.out::println); @@ -106,15 +93,15 @@ public static void execCmd(String command, Session session) throws Exception { // }); InputStream inputStream = channel.getInputStream(); channel.connect(); - IoUtil.readLines(inputStream, CharsetUtil.CHARSET_UTF_8, new LineHandler() { - @Override - public void handle(String line) { - System.out.println(line); - } - }); + IoUtil.readLines(inputStream, CharsetUtil.CHARSET_UTF_8, (LineHandler) System.out::println); int exitStatus = channel.getExitStatus(); System.out.println(exitStatus); channel.disconnect(); session.disconnect(); } + + @Test + public void test2() { + + } } diff --git a/modules/server/src/test/java/TestScriptLink.java b/modules/server/src/test/java/TestScriptLink.java new file mode 100644 index 0000000000..15a0aceba7 --- /dev/null +++ b/modules/server/src/test/java/TestScriptLink.java @@ -0,0 +1,42 @@ +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.util.ReUtil; +import org.junit.Test; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bwcx_jzy + * @since 2024/6/16 + */ +public class TestScriptLink { + + @Test + public void test() { + String content = "test\n" + + "\n" + + "echo \"test -12\"\n" + + "\n" + + "echo $buildNumberId\n" + + "G@(\"xxxx\")\n" + + "\n" + + "G@(\"xxxx\")\n" + + "\n" + + "G@(\"ttttt\")"; + + String reg = "G@\\(\"(.*?)\"\\)"; + Pattern pattern = PatternPool.get("G@\\(\"(.*?)\"\\)", Pattern.DOTALL); + List all = ReUtil.findAll(reg, content, 1); + all.forEach(System.out::println); + + Matcher matcher = pattern.matcher(content); + StringBuffer modifiedLine = new StringBuffer(); + while (matcher.find()) { + String group = matcher.group(1); + matcher.appendReplacement(modifiedLine, String.format("\"%s\"", group)); + } + matcher.appendTail(modifiedLine); + System.out.println(modifiedLine); + } +} diff --git a/modules/server/src/test/java/TestSe.java b/modules/server/src/test/java/TestSe.java new file mode 100644 index 0000000000..d87c35ff99 --- /dev/null +++ b/modules/server/src/test/java/TestSe.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.crypto.symmetric.DES; +import org.junit.jupiter.api.Test; + +/** + * @author bwcx_jzy + * @since 2022/7/22 + */ +public class TestSe { + + @Test + public void test() { + DES des = SecureUtil.des("KZQfFBJTW2v6obS1".getBytes()); + String uuid = IdUtil.fastSimpleUUID(); + String data = uuid + ":ad7de8fec89d48d6b12d1eeace1543ad:ad7de8fec89d4"; + String sss = des.encryptHex(data); + System.out.println(sss); + System.out.println(StrUtil.length(uuid) + " " + StrUtil.length(data) + " " + StrUtil.length(sss)); + String decryptStr = des.decryptStr(sss); + System.out.println(decryptStr); + // + AES aes = SecureUtil.aes("KZQfFBJTW2v6obS1".getBytes()); + sss = aes.encryptHex("ad7de8fec89d48d6b12d1eeace1543ad:ad7de8fec89d48d6b12d1eeace1543ad"); + System.out.println(sss); + System.out.println(StrUtil.length(sss)); + decryptStr = aes.decryptStr(sss); + System.out.println(decryptStr); + + // + } +} diff --git a/modules/server/src/test/java/TestString.java b/modules/server/src/test/java/TestString.java index fdc97f4f30..df95b3c63e 100644 --- a/modules/server/src/test/java/TestString.java +++ b/modules/server/src/test/java/TestString.java @@ -1,26 +1,20 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.url.UrlQuery; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; @@ -28,81 +22,176 @@ import cn.hutool.crypto.digest.DigestAlgorithm; import cn.hutool.crypto.digest.Digester; import org.junit.Test; +import org.springframework.boot.convert.DurationStyle; import java.nio.charset.Charset; -import java.util.Collection; -import java.util.SortedMap; +import java.time.Duration; +import java.util.*; import java.util.regex.Pattern; +import java.util.stream.Stream; /** - * Created by jiangzeyin on 2019/3/1. + * Created by bwcx_jzy on 2019/3/1. */ public class TestString { - @Test - public void test2() { - System.out.println(StrUtil.format("#{{}}", 1) - ); + @Test + public void testDate(){ + DateTime dateTime = DateUtil.parse("1940-06-01 00:00:00"); + System.out.println(dateTime); + } - String replace = "#{A}"; - replace = StrUtil.replace(replace, "#{AAAAAAA}", "1"); - System.out.println(replace); - } + @Test + public void testCtrlC() { + char c = 3; + String s = String.valueOf(c); + System.out.println(s); + } - @Test - public void test() { - String s = ReUtil.get(".*?", "aasss", 0); - System.out.println(s); - } + @Test + public void testCharset() { + Charset utf8 = CharsetUtil.charset("utf8"); + Charset utf82 = CharsetUtil.charset("utf-8"); + System.out.println(utf82 == utf8); + } - public static void main(String[] args) { + @Test + public void test2() { + System.out.println(StrUtil.format("#{{}}", 1) + ); + + String replace = "#{A}"; + replace = StrUtil.replace(replace, "#{AAAAAAA}", "1"); + System.out.println(replace); + } + + @Test + public void test() { + String s = ReUtil.get(".*?", "aasss", 0); + System.out.println(s); + } + + public static void main(String[] args) { // System.out.println(CheckPassword.checkPassword("123aA!")); // DateTime dateTime = DateUtil.parseUTC("2019-04-04T10:11:21Z"); // System.out.println(dateTime); // dateTime.setTimeZone(TimeZone.getDefault()); // System.out.println(dateTime); - Pattern pattern = Pattern.compile("(https://|http://)?([\\w-]+\\.)+[\\w-]+(:\\d+|/)+([\\w- ./?%&=]*)?"); - String url = "http://192.168.1.111:2122/node/index.html?nodeId=dyc"; - System.out.println(ReUtil.isMatch(pattern, url)); - System.out.println(ReUtil.isMatch(PatternPool.URL_HTTP, url)); + Pattern pattern = Pattern.compile("(https://|http://)?([\\w-]+\\.)+[\\w-]+(:\\d+|/)+([\\w- ./?%&=]*)?"); + String url = "http://192.168.1.111:2122/node/index.html?nodeId=dyc"; + System.out.println(ReUtil.isMatch(pattern, url)); + System.out.println(ReUtil.isMatch(PatternPool.URL_HTTP, url)); // System.out.println(FileUtil.file("/a", null, "", "ss")); - System.out.println(Math.pow(1024, 2)); - - System.out.println(Integer.MAX_VALUE); - - while (true) { - SortedMap x = Charset.availableCharsets(); - Collection values = x.values(); - boolean find = false; - for (Charset charset : values) { - String name = charset.name(); - if ("utf-8".equalsIgnoreCase(name)) { - find = true; - break; - } - } - if (!find) { - System.out.println("没有找utf-8"); - } - } + System.out.println(Math.pow(1024, 2)); + + System.out.println(Integer.MAX_VALUE); + + while (true) { + SortedMap x = Charset.availableCharsets(); + Collection values = x.values(); + boolean find = false; + for (Charset charset : values) { + String name = charset.name(); + if ("utf-8".equalsIgnoreCase(name)) { + find = true; + break; + } + } + if (!find) { + System.out.println("没有找utf-8"); + } + } // System.out.println(x); - } + } + + @Test + public void test1() { + System.out.println(SecureUtil.sha256("1")); + + System.out.println(SecureUtil.sha256("admin")); + + + int randomInt = 2; + RandomUtil.randomInt(1, 100); + System.out.println(randomInt); + String nowStr = "admin"; + nowStr = new Digester(DigestAlgorithm.SHA256).setDigestCount(2).digestHex(nowStr); + System.out.println(nowStr); + } + + @Test + public void testLen() { + System.out.println(StrUtil.EMPTY.length() + " " + StrUtil.EMPTY.isEmpty()); + } + + @Test + public void testStream() { + Stream integerStream = Stream.of(1, 2, 3); + System.out.println(integerStream.count()); + System.out.println(integerStream.count()); + } + + @Test + public void testBase64() { + String encode = Base64.decodeStr("YWJjZA"); + System.out.println(encode); + } + + @Test + public void testMapStr() { + UrlQuery urlQuery = UrlQuery.of("xx=xx&xxx=xxx", CharsetUtil.CHARSET_UTF_8); +// urlQuery.getQueryMap() + HashMap map = MapUtil.of("sss", "xxxxx"); + map.put("ss", "23"); + System.out.println(MapUtil.join(map, ";", "=")); + } + + @Test + public void testTime() { + String time = "5h"; + DurationStyle durationStyle = DurationStyle.detect(time); + Duration duration = durationStyle.parse(time); + System.out.println(duration.toHours()); + + System.out.println(Duration.ofHours(5).toHours()); +// Duration convert1 = DefaultConversionService.getSharedInstance().convert("18000", Duration.class); +// System.out.println(convert1); +// Duration convert = Convert.convert(Duration.class, "18000"); +// System.out.println(convert); + +// System.out.println(Duration.parse("18000")); +// DateUnit dateUnit = DateUnit.valueOf("18000"); + } + + @Test + public void testVersion() { + System.out.println(StrUtil.compareVersion("2.10.10", "2.10.9")); + System.out.println(StrUtil.compareVersion("2.10.37", "2.10.38")); + System.out.println(StrUtil.compareVersion("2.10.37", "2.10.37.1")); + System.out.println(StrUtil.compareVersion("2.10.38", "2.10.37.9")); + } - @Test - public void test1() { - System.out.println(SecureUtil.sha256("1")); + @Test + public void testArrayList() { + ArrayList integers = CollUtil.newArrayList(1, 2); + System.out.println(CollUtil.sub(integers, 1, integers.size())); + } - System.out.println(SecureUtil.sha256("admin")); + @Test + public void testLong() { + System.out.println(4045003435L - 5476659199L); + } + @Test + public void testMap() { + Map map = new HashMap<>(); + map.put("a", "1"); - int randomInt = 2; - RandomUtil.randomInt(1, 100); - System.out.println(randomInt); - String nowStr = "admin"; - nowStr = new Digester(DigestAlgorithm.SHA256).setDigestCount(2).digestHex(nowStr); - System.out.println(nowStr); - } + Set set = new HashSet<>(map.keySet()); + map.put("b", "2"); + System.out.println(set); + } } diff --git a/modules/server/src/test/java/TestSynchronized.java b/modules/server/src/test/java/TestSynchronized.java new file mode 100644 index 0000000000..ebeaef9470 --- /dev/null +++ b/modules/server/src/test/java/TestSynchronized.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +/** + * @author bwcx_jzy + * @since 2022/11/25 + */ +@Slf4j +public class TestSynchronized { + + + /** + * 死锁测试 + */ + @Test + void deadLockTests() throws InterruptedException { + + for (int i = 0; i < 100000; i++) { + User u1 = new User(50); + User u2 = new User(50); + log.info("第 - {} - 次", i); + Thread thread = new Thread(() -> u1.hit(u2), "A"); + Thread thread1 = new Thread(() -> u2.hit(u1), "B"); + thread1.start(); + thread.start(); + + thread1.join(); + thread.join(); + } + + } + + + private static int i = 0; + + static class User { + public int num; + private final Lock lock = new Lock(++i); + + public User(int num) { + this.num = num; + } + + public void sub(int step) { + log.info("我是{} 我的锁是: {} ,我准备拿sub锁 {} ", Thread.currentThread().getName(), lock, lock); + synchronized (lock) { + log.info("我是{} 拿到的sub锁为: {}", Thread.currentThread().getName(), lock); + this.num = this.num - step; + } + log.info("我是{} 我的锁是: {} 我释放了sub锁:{}", Thread.currentThread().getName(), lock, lock); + } + + public void hit(User user) { + log.info("我是{} 我的锁是: {},我准备拿hit锁 {} ", Thread.currentThread().getName(), lock, lock); + synchronized (lock) { + log.info("我是{} 拿到hit锁的锁为: {}", Thread.currentThread().getName(), lock); + if (user != null) { + user.sub(10); + } + } + log.info("我是{} 我的锁是: {},我释放了hit锁 {} ", Thread.currentThread().getName(), lock, lock); + } + + } + + static class Lock { + private final int i; + + Lock(int i) { + this.i = i; + } + + @Override + public String toString() { + return "LOCK-" + i; + } + } + +} diff --git a/modules/server/src/test/java/TestT.java b/modules/server/src/test/java/TestT.java new file mode 100644 index 0000000000..d3c4650aa9 --- /dev/null +++ b/modules/server/src/test/java/TestT.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.pattern.CronPatternUtil; +import org.dromara.jpom.util.AntPathUtil; +import org.dromara.jpom.util.FileUtils; +import org.junit.Test; + +import java.io.File; +import java.time.Duration; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2019/9/4 + */ +public class TestT { + + @Test + public void tset() { + List dates = CronPatternUtil + .matchedDates("0 0 0 1/3 * ?", DateTime.now(), + DateUtil.offset(DateTime.now(), DateField.YEAR, 1), 10, true); + for (Date date : dates) { + System.out.println(DateUtil.format(date, DatePattern.NORM_DATETIME_MS_PATTERN)); + } + } + + public static void main(String[] args) { + Duration parse = Duration.parse("1H"); + System.out.println(parse.getSeconds()); + } + + @Test + public void testList() { + List list = CollUtil.newArrayList(1, 2, 3, 4, 5, 6); + list.sort(Comparator.reverseOrder()); + int size = CollUtil.size(list); + list = CollUtil.sub(list, 2, size); + System.out.println(list); + } + + + @Test + public void testChar() { + String str = "abc"; + char[] chars = str.toCharArray(); + Character item = ArrayUtil.get(chars, 10); + System.out.println(item); + + item = ArrayUtil.get(chars, 1); + System.out.println(item); + + String pattern = "/sss**"; + String path = "/sss/xxx"; + + System.out.println(AntPathUtil.ANT_PATH_MATCHER.match(pattern, path)); + } + + @Test + public void testMatchPath() { + File file = FileUtil.file("C:\\Users\\bwcx_\\jpom\\server\\data\\build\\1b5c98e58e334f0e9b70b09c455d9dfd\\source"); +// String match = "/springboot-test*/s?c/**/*.java"; + String match = "/**/*.xml"; +// String match = "/springboot-test-jar/**/*.*"; + List matcher = AntPathUtil.antPathMatcher(file, match); +// String subMatch = "/springboot-test-*/src/"; + String subMatch = "/springboot-test-**/src/"; + for (String s : matcher) { + List list = StrUtil.splitTrim(s, StrUtil.SLASH); + if (!AntPathUtil.ANT_PATH_MATCHER.matchStart(subMatch + "**", s)) { + continue; + } + int notMathIndex = ArrayUtil.INDEX_NOT_FOUND; + int size = list.size(); + for (int i = size - 1; i >= 0; i--) { + String suffix = i == size - 1 ? StrUtil.EMPTY : StrUtil.SLASH; + String itemS = StrUtil.SLASH + CollUtil.join(CollUtil.sub(list, 0, i + 1), StrUtil.SLASH) + suffix; + //System.out.println(subMatch + " o " + itemS); +// System.out.println(AntPathUtil.ANT_PATH_MATCHER.extractPathWithinPattern(subMatch, itemS)); + if (AntPathUtil.ANT_PATH_MATCHER.match(subMatch, itemS)) { + System.out.println(subMatch + " " + itemS + " "); + notMathIndex = i + 1; + break; + } + } +// for (int i = 0; i < list.size(); i++) { +// String suffix = i == list.size() - 1 ? StrUtil.EMPTY : StrUtil.SLASH; +// String itemS = StrUtil.SLASH + CollUtil.join(CollUtil.sub(list, 0, i + 1), StrUtil.SLASH) + suffix; +// // String itemS = StrUtil.SLASH + CollUtil.join(CollUtil.sub(list, 0, i + 1), StrUtil.SLASH); +// if (i == 0) { +// if (!AntPathUtil.ANT_PATH_MATCHER.match(subMatch, itemS)) { +// +// } +// } +// System.out.println(subMatch + " o " + itemS); +//// System.out.println(AntPathUtil.ANT_PATH_MATCHER.extractPathWithinPattern(subMatch, itemS)); +// if (!AntPathUtil.ANT_PATH_MATCHER.match(subMatch, itemS)) { +// System.out.println(subMatch + " " + itemS + " "); +// notMathIndex = i; +// break; +// } +// } + if (notMathIndex == ArrayUtil.INDEX_NOT_FOUND) { + continue; + } + String itemEnd = CollUtil.join(CollUtil.sub(list, notMathIndex, size), StrUtil.SLASH); + System.out.println(notMathIndex + " ok: " + s + " " + itemEnd); + //break; + + } + System.out.println("123456"); + + + System.out.println(matcher); + } + + @Test + public void testFile() { + FileUtils.checkSlip("/../../../xxx/xx//aaa/../"); + FileUtils.checkSlip("/../../../xxx/xx?/&&{#}:/aaa/../"); + } + + @Test + public void testDownload() { +// HttpUtil.download() + } +} diff --git a/modules/server/src/test/java/TestThread.java b/modules/server/src/test/java/TestThread.java new file mode 100644 index 0000000000..757bcbbb02 --- /dev/null +++ b/modules/server/src/test/java/TestThread.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.thread.ExecutorBuilder; +import cn.hutool.core.thread.ThreadUtil; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.*; + +/** + * @author bwcx_jzy + * @since 2022/5/13 + */ +public class TestThread implements Callable { + + @Test + public void test1() { + ThreadPoolExecutor build = ExecutorBuilder.create().setCorePoolSize(1) + .setMaxPoolSize(1) + .useArrayBlockingQueue(1).setHandler(new ThreadPoolExecutor.DiscardPolicy() { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + if (r instanceof FutureTask) { + FutureTask futureTask = (FutureTask) r; +// System.out.println(futureTask.); + } + } + }).build(); + for (int i = 0; i <= 2; i++) { + build.execute(() -> ThreadUtil.sleep(1, TimeUnit.MINUTES)); + } + build.submit(this); + } + + @Test + public void test() throws ExecutionException, InterruptedException { + ExecutorService executorService = ThreadUtil.newSingleExecutor(); + + + for (int i = 0; i < 5; i++) { + int finalI = i; + executorService.submit((Callable) () -> { + System.out.println(finalI); + return finalI; + }); + } + + ThreadUtil.sleep(10, TimeUnit.SECONDS); + } + + @Override + public String call() throws Exception { + return null; + } +} diff --git a/modules/server/src/test/java/TestYml.java b/modules/server/src/test/java/TestYml.java index fff10903ef..c51dad250a 100644 --- a/modules/server/src/test/java/TestYml.java +++ b/modules/server/src/test/java/TestYml.java @@ -1,53 +1,69 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ +import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.ResourceUtil; -import io.jpom.system.ConfigBean; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.setting.yaml.YamlUtil; +import org.dromara.jpom.db.DbExtConfig; import org.junit.Test; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.FileUrlResource; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; import java.net.URL; +import java.nio.charset.Charset; import java.util.List; /** * @author bwcx_jzy - * @date 2021/8/1 + * @since 2021/8/1 */ public class TestYml { - @Test - public void test() throws IOException { - String path = "D:\\Idea\\Jpom\\modules\\agent\\src\\main\\resources\\bin\\extConfig.yml"; - YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); + @Test + public void testCharest() { + System.out.println(Charset.forName("UTF8")); + System.out.println(Charset.forName("UTF-8")); + } + + @Test + public void test2() { + InputStream stream = ResourceUtil.getStream("bin/extConfig.yml"); + //String s = IoUtil.readUtf8(stream); + //System.out.println(s); + Dict dict = YamlUtil.load(stream, Dict.class); + Object db = dict.get("db"); + StringWriter writer = new StringWriter(); + YamlUtil.dump(db, writer); + ByteArrayInputStream inputStream = IoUtil.toStream(writer.toString(), CharsetUtil.CHARSET_UTF_8); + DbExtConfig dbExtConfig1 = YamlUtil.load(inputStream, DbExtConfig.class); + System.out.println(dbExtConfig1); + } + + + @Test + public void test() throws IOException { + String path = "D:\\Idea\\Jpom\\modules\\agent\\src\\main\\resources\\bin\\extConfig.yml"; + YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); // ByteArrayResource resource = new ByteArrayResource(StringUtil.deleteComment(content).getBytes(StandardCharsets.UTF_8)); - URL resource = ResourceUtil.getResource(path); - List> test = yamlPropertySourceLoader.load("test", new FileUrlResource(path)); - PropertySource propertySource = test.get(0); - System.out.println(propertySource); + URL resource = ResourceUtil.getResource(path); + List> test = yamlPropertySourceLoader.load("test", new FileUrlResource(path)); + PropertySource propertySource = test.get(0); + System.out.println(propertySource); - Object user = propertySource.getProperty(ConfigBean.AUTHORIZE_USER_KEY); - System.out.println(user); - } +// Object user = propertySource.getProperty(Const.AUTHORIZE_USER_KEY); +// System.out.println(user); + } } diff --git a/modules/server/src/test/java/TopTest.java b/modules/server/src/test/java/TopTest.java index f8575c7c36..ff5dd0cd07 100644 --- a/modules/server/src/test/java/TopTest.java +++ b/modules/server/src/test/java/TopTest.java @@ -1,28 +1,16 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ import cn.hutool.core.io.IoUtil; import cn.hutool.core.thread.GlobalThreadPool; import cn.hutool.core.util.CharsetUtil; +import org.junit.Test; import java.io.IOException; import java.io.InputStream; @@ -40,8 +28,13 @@ public static void main(String[] args) { System.out.println(s); System.out.println("结束"); }); + } - + @Test + public void testFlor() { + double floor = Math.floor(((float) 103 / 103) * 100); + int floor1 = (int) Math.floor(floor / 600); + System.out.println(floor + " " + floor1); } private static String execCommand(String[] command) { @@ -56,7 +49,7 @@ private static String execCommand(String[] command) { } else { is = process.getErrorStream(); } - result = IoUtil.read(is, CharsetUtil.GBK); + result = IoUtil.read(is, CharsetUtil.CHARSET_GBK); is.close(); process.destroy(); } catch (IOException | InterruptedException e) { diff --git a/modules/server/src/test/java/TryFinally.java b/modules/server/src/test/java/TryFinally.java index f1a6511125..11a0e363c2 100644 --- a/modules/server/src/test/java/TryFinally.java +++ b/modules/server/src/test/java/TryFinally.java @@ -1,27 +1,14 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ /** - * Created by jiangzeyin on 2018/9/30. + * Created by bwcx_jzy on 2018/9/30. */ public class TryFinally { public static void main(String[] args) { diff --git a/modules/server/src/test/java/cert/CertUtil.java b/modules/server/src/test/java/cert/CertUtil.java new file mode 100644 index 0000000000..6d24a9d088 --- /dev/null +++ b/modules/server/src/test/java/cert/CertUtil.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cert; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.InputStream; +import java.security.Security; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +public class CertUtil { + static { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + public static String getSubjectDN(InputStream bIn) { + String dn = ""; + try { + +// BouncyCastleProvider provider = new BouncyCastleProvider(); +// CertificateFactory cf = CertificateFactory.getInstance("X509", +// provider); + CertificateFactory cf = CertificateFactory.getInstance("X.509", + "SUN"); + //android 需采用bcprov +// CertificateFactory cf = CertificateFactory.getInstance("X.509", +// "BC"); + X509Certificate cert = (X509Certificate) cf + .generateCertificate(bIn); + dn = cert.getSubjectDN().getName(); + bIn.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return dn; + } + + public static String parseCertDN(String dn, String type) { + type = type + "="; + String[] split = dn.split(StrUtil.COMMA); + for (String x : split) { + if (x.contains(type)) { + x = x.trim(); + return x.substring(type.length()); + } + } + return null; + } + + /** + * @param args + */ + public static void main(String[] args) { + InputStream inputStream = ResourceUtil.getStream("D:\\jpom\\agent\\data\\temp\\系统管理员\\sdfasdf\\example.com.csr"); + getSubjectDN(inputStream); + } +} diff --git a/modules/server/src/test/java/cert/CertificateValidator.java b/modules/server/src/test/java/cert/CertificateValidator.java new file mode 100644 index 0000000000..c0437d433f --- /dev/null +++ b/modules/server/src/test/java/cert/CertificateValidator.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +//package cert; +// +//import cn.hutool.core.io.FileUtil; +//import cn.hutool.core.util.CharsetUtil; +//import cn.hutool.crypto.PemUtil; +//import org.bouncycastle.jce.provider.BouncyCastleProvider; +//import org.bouncycastle.util.io.pem.PemObject; +//import org.bouncycastle.util.io.pem.PemReader; +// +//import java.io.FileInputStream; +//import java.security.*; +//import java.security.cert.CertificateFactory; +//import java.security.cert.X509Certificate; +//import java.security.spec.PKCS8EncodedKeySpec; +// +///** +// * @author bwcx_jzy +// * @since 2023/3/22 +// */ +// +//public class CertificateValidator { +// public static void main(String[] args) throws Exception { +// +// // Add Bouncy Castle provider +// Security.addProvider(new BouncyCastleProvider()); +// +// // Load public certificate and certificate chain +// CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC"); +// X509Certificate publicCert = (X509Certificate) certFactory.generateCertificate(new FileInputStream("public.crt")); +// X509Certificate[] chain = {publicCert}; +// PemUtil.readPemObject(""); +// PemReader pemReader = new PemReader(new FileInputStream("chain.crt")); +// while (true) { +// PemObject pem = pemReader.readPemObject(); +// if (pem == null) break; +// X509Certificate cert = (X509Certificate) certFactory.generateCertificate(pem.getContent()); +// chain = appendToArray(chain, cert); +// } +// pemReader.close(); +// +// // Load private key +// PemReader keyReader = new PemReader(FileUtil.getReader(FileUtil.file("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_apache\\8215371_download.jpom.top.key"), CharsetUtil.CHARSET_UTF_8)); +// byte[] keyBytes = keyReader.readPemObject().getContent(); +// PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); +// KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); +// PrivateKey privateKey = keyFactory.generatePrivate(keySpec); +// keyReader.close(); +// +// // Verify certificate chain +// publicCert.checkValidity(); +// for (int i = 1; i < chain.length; i++) { +// chain[i].checkValidity(); +// chain[i].verify(chain[i - 1].getPublicKey()); +// } +// +// // Verify private key +// PublicKey publicKey = publicCert.getPublicKey(); +// byte[] message = "test message".getBytes(); +// Signature signature = Signature.getInstance("SHA256withRSA", "BC"); +// signature.initSign(privateKey); +// signature.update(message); +// byte[] signatureBytes = signature.sign(); +// signature.initVerify(publicKey); +// signature.update(message); +// boolean verified = signature.verify(signatureBytes); +// +// // Print result +// System.out.println("Certificate and private key " + (verified ? "match" : "do not match")); +// } +// +// private static T[] appendToArray(T[] array, T item) { +// T[] newArray = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), array.length + 1); +// System.arraycopy(array, 0, newArray, 0, array.length); +// newArray[newArray.length - 1] = item; +// return newArray; +// } +//} diff --git a/modules/server/src/test/java/cert/TestCert.java b/modules/server/src/test/java/cert/TestCert.java new file mode 100644 index 0000000000..5f13f73732 --- /dev/null +++ b/modules/server/src/test/java/cert/TestCert.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package cert; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.HexUtil; +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.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import com.sun.security.cert.internal.x509.X509V1CertImpl; +import org.junit.Test; + +import javax.security.cert.X509Certificate; +import java.io.File; +import java.math.BigInteger; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.util.Date; +import java.util.Enumeration; + +/** + * Created by bwcx_jzy on 2019/3/7. + */ +public class TestCert { + + static { + //Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + GlobalBouncyCastleProvider.setUseBouncyCastle(false); + } + + @Test + public void testPfx() throws Exception { + File file = FileUtil.file("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_tomcat", "8215371_download.jpom.top.pfx"); + char[] password = "skkervmb".toCharArray(); + KeyStore keyStore = KeyUtil.readPKCS12KeyStore(file, password); + String type = keyStore.getType(); +// keyStore.load(FileUtil.getInputStream(file), password); + Enumeration aliases = keyStore.aliases(); + if (aliases.hasMoreElements())// we are readin just one certificate. + { + String keyAlias = aliases.nextElement(); + System.out.println("alias=[" + keyAlias + "]"); + Certificate certificate = keyStore.getCertificate(keyAlias); + System.out.println(certificate.getType()); + PrivateKey prikey = (PrivateKey) keyStore.getKey(keyAlias, password); + { + + PublicKey pubkey = certificate.getPublicKey(); + } + + { + X509Certificate cert = X509Certificate.getInstance(certificate.getEncoded()); + byte[] encoded = cert.getEncoded(); + System.out.println("指纹:" + SecureUtil.sha1().digestHex(encoded)); + Date notBefore = cert.getNotBefore(); + Date notAfter = cert.getNotAfter(); + BigInteger serialNumber = cert.getSerialNumber(); + if (cert instanceof X509V1CertImpl) { + X509V1CertImpl x509V1Cert = (X509V1CertImpl) cert; + java.security.cert.X509Certificate x509Certificate = x509V1Cert.getX509Certificate(); + //AuthorityKeyIdentifier + byte[] extensionValue1 = x509Certificate.getExtensionValue("2.5.29.35"); + // SubjectKeyIdentifier + byte[] extensionValue2 = x509Certificate.getExtensionValue("2.5.29.14"); + System.out.println(HexUtil.encodeHexStr(ArrayUtil.sub(extensionValue1, 6, extensionValue1.length))); + System.out.println(HexUtil.encodeHexStr(ArrayUtil.sub(extensionValue2, 4, extensionValue2.length))); + } + System.out.println(serialNumber.toString(16)); + int version = cert.getVersion(); + Principal subjectDN = cert.getSubjectDN(); + Principal issuerDN = cert.getIssuerDN(); + String issuerDNName = issuerDN.getName(); + String subjectDNName = subjectDN.getName(); + String sigAlgOID = cert.getSigAlgOID(); + String sigAlgName = cert.getSigAlgName(); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + System.out.println(sigAlgName); + System.out.println(sigAlgOID); + System.out.println(issuerDNName); + System.out.println(subjectDNName); + } + } +// KeyStore keyStore = KeyUtil.readJKSKeyStore(file, "skkervmb".toCharArray()); + System.out.println(keyStore); + } + + @Test + public void testKey() throws Exception { + File file = FileUtil.file("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_nginx", "8215371_download.jpom.top.pem"); + + Certificate certificate = KeyUtil.readX509Certificate(FileUtil.getInputStream(file)); +// KeyStore keyStore = KeyUtil.readJKSKeyStore(file, "skkervmb".toCharArray()); + { + X509Certificate cert = X509Certificate.getInstance(certificate.getEncoded()); + byte[] encoded = cert.getEncoded(); + System.out.println("指纹:" + SecureUtil.sha256().digestHex(encoded)); + Date notBefore = cert.getNotBefore(); + Date notAfter = cert.getNotAfter(); + BigInteger serialNumber = cert.getSerialNumber(); + System.out.println(certificate.getType()); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + } + } + + @Test + public void testJks() throws Exception { + File file = FileUtil.file("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_jks", "8215371_download.jpom.top.jks"); + char[] password = "hibuvbsp".toCharArray(); + KeyStore keyStore = KeyUtil.readJKSKeyStore(file, password); + keyStore.load(FileUtil.getInputStream(file), password); + Enumeration aliases = keyStore.aliases(); + if (aliases.hasMoreElements())// we are readin just one certificate. + { + String keyAlias = aliases.nextElement(); + System.out.println("alias=[" + keyAlias + "]"); + Certificate certificate = keyStore.getCertificate(keyAlias); + PrivateKey prikey = (PrivateKey) keyStore.getKey(keyAlias, password); + { + + PublicKey pubkey = certificate.getPublicKey(); + } + { + X509Certificate cert = X509Certificate.getInstance(certificate.getEncoded()); + byte[] encoded = cert.getEncoded(); + System.out.println("指纹:" + SecureUtil.sha256().digestHex(encoded)); + Date notBefore = cert.getNotBefore(); + Date notAfter = cert.getNotAfter(); + BigInteger serialNumber = cert.getSerialNumber(); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + } + } +// KeyStore keyStore = KeyUtil.readJKSKeyStore(file, "skkervmb".toCharArray()); + System.out.println(keyStore); + } + + @Test + public void testCrt() throws Exception { + PrivateKey privateKey = PemUtil.readPemPrivateKey(FileUtil.getInputStream("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_apache\\8215371_download.jpom.top.key")); + X509Certificate publicCert; + PublicKey publicKey1; + Certificate certificate1; + + { + File file = FileUtil.file("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_apache", "8215371_download.jpom.top_public.crt"); + certificate1 = KeyUtil.readX509Certificate(FileUtil.getInputStream(file)); + String certificate1Type = certificate1.getType(); + + System.out.println(certificate1Type); + { + publicCert = X509Certificate.getInstance(certificate1.getEncoded()); + byte[] encoded = publicCert.getEncoded(); + System.out.println("指纹:" + SecureUtil.sha256().digestHex(encoded)); + System.out.println(publicCert.getIssuerDN().equals(publicCert.getSubjectDN()) + " " + publicCert.getIssuerDN() + " " + publicCert.getSubjectDN()); + Date notBefore = publicCert.getNotBefore(); + Date notAfter = publicCert.getNotAfter(); + BigInteger serialNumber = publicCert.getSerialNumber(); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + } + + publicKey1 = certificate1.getPublicKey(); + RSA rsa = new RSA(privateKey, publicKey1); + String str = "您好,Hutool";//测试字符串 + + String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); + String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); + System.out.println(str.equals(decryptStr)); + + } + + X509Certificate chainCert; + { + File file = FileUtil.file("D:\\System-Data\\Downloads\\download.jpom.top\\8215371_download.jpom.top_apache", "8215371_download.jpom.top_chain.crt"); + Certificate certificate = KeyUtil.readX509Certificate(FileUtil.getInputStream(file)); + String certificate2Type = certificate.getType(); + System.out.println(certificate2Type); + { + chainCert = X509Certificate.getInstance(certificate.getEncoded()); + System.out.println(chainCert.getIssuerDN().equals(chainCert.getSubjectDN()) + " " + chainCert.getIssuerDN() + " " + chainCert.getSubjectDN()); + Date notBefore = chainCert.getNotBefore(); + Date notAfter = chainCert.getNotAfter(); + BigInteger serialNumber = chainCert.getSerialNumber(); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + } + System.out.println(certificate.equals(certificate1)); + PublicKey publicKey = certificate.getPublicKey(); + System.out.println(publicKey.equals(publicKey1)); + System.out.println(certificate1.getEncoded().equals(certificate.getEncoded())); +// RSA rsa = new RSA(privateKey, publicKey); +// String str = "您好,Hutool";//测试字符串 +// +// String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); +// String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); +// System.out.println(str.equals(decryptStr)); + } +// X509Certificate[] chain = new X509Certificate[]{publicCert, cert2}; + // Verify certificate chain + publicCert.checkValidity(); + chainCert.checkValidity(); + + publicCert.verify(chainCert.getPublicKey()); + } + + + @Test + public void testCa() throws Exception { + File ca = FileUtil.file("D:\\System-Data\\Desktop\\ca", "ca.pem"); + File crt = FileUtil.file("D:\\System-Data\\Desktop\\ca", "cert.pem"); + File key = FileUtil.file("D:\\System-Data\\Desktop\\ca", "key.pem"); + + PrivateKey privateKey = PemUtil.readPemPrivateKey(FileUtil.getInputStream(key)); + X509Certificate publicCert; + PublicKey publicKey1; + Certificate certificate1; + + { + + certificate1 = KeyUtil.readX509Certificate(FileUtil.getInputStream(crt)); + { + publicCert = X509Certificate.getInstance(certificate1.getEncoded()); + byte[] encoded = publicCert.getEncoded(); + System.out.println("指纹:" + SecureUtil.sha256().digestHex(encoded)); + System.out.println(publicCert.getIssuerDN().equals(publicCert.getSubjectDN()) + " " + publicCert.getIssuerDN() + " " + publicCert.getSubjectDN()); + Date notBefore = publicCert.getNotBefore(); + Date notAfter = publicCert.getNotAfter(); + BigInteger serialNumber = publicCert.getSerialNumber(); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + } + + publicKey1 = certificate1.getPublicKey(); + RSA rsa = new RSA(privateKey, publicKey1); + String str = "您好,Hutool";//测试字符串 + + String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); + String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); + System.out.println(str.equals(decryptStr)); + + } + + X509Certificate chainCert; + { + + Certificate certificate = KeyUtil.readX509Certificate(FileUtil.getInputStream(ca)); + { + chainCert = X509Certificate.getInstance(certificate.getEncoded()); + System.out.println(chainCert.getIssuerDN().equals(chainCert.getSubjectDN()) + " " + chainCert.getIssuerDN() + " " + chainCert.getSubjectDN()); + Date notBefore = chainCert.getNotBefore(); + Date notAfter = chainCert.getNotAfter(); + BigInteger serialNumber = chainCert.getSerialNumber(); + System.out.println(chainCert.getSubjectDN().getName()); + System.out.println(serialNumber); + System.out.println(notBefore + " " + notAfter); + } + System.out.println(certificate.equals(certificate1)); + PublicKey publicKey = certificate.getPublicKey(); + System.out.println(publicKey.equals(publicKey1)); + System.out.println(certificate1.getEncoded().equals(certificate.getEncoded())); +// RSA rsa = new RSA(privateKey, publicKey); +// String str = "您好,Hutool";//测试字符串 +// +// String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); +// String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); +// System.out.println(str.equals(decryptStr)); + } +// X509Certificate[] chain = new X509Certificate[]{publicCert, cert2}; + // Verify certificate chain + publicCert.checkValidity(); + chainCert.checkValidity(); + + publicCert.verify(chainCert.getPublicKey()); + } + + public void main(String[] args) { +// HttpRequest request = HttpUtil.createPost("https://myssl.com/api/v1/tools/cert_decode"); +// request.form("certfile", new File("D:\\SystemDocument\\Desktop\\web_hulianwangjia\\full_chain.pem")); +// request.form("type", "upload"); +// HttpResponse response = request.execute(); +// System.out.println(response.body()); +// D:\SystemDocument\Desktop + + PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("D:\\SystemDocument\\Desktop\\1979263_jpom.keepbx.cn.key")); + PublicKey publicKey = PemUtil.readPemPublicKey(ResourceUtil.getStream("D:\\SystemDocument\\Desktop\\1979263_jpom.keepbx.cn.pem")); + + RSA rsa = new RSA(privateKey, publicKey); + String str = "您好,Hutool";//测试字符串 + + String encryptStr = rsa.encryptBase64(str, KeyType.PublicKey); + String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey); + System.out.println(encryptStr); + System.out.println(decryptStr); + System.out.println(str.equals(decryptStr)); + } +} diff --git a/modules/server/src/test/java/com/example/twofactorauthdemo/TwoFactorAuthTest.java b/modules/server/src/test/java/com/example/twofactorauthdemo/TwoFactorAuthTest.java new file mode 100644 index 0000000000..345e86a83f --- /dev/null +++ b/modules/server/src/test/java/com/example/twofactorauthdemo/TwoFactorAuthTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package com.example.twofactorauthdemo; + +import org.databene.contiperf.PerfTest; +import org.databene.contiperf.junit.ContiPerfRule; +import org.dromara.jpom.util.TwoFactorAuthUtils; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @since 2022/1/27 + */ +public class TwoFactorAuthTest { + private String tfaKey; + + @Rule + public ContiPerfRule i = ContiPerfRule.createDefaultRule(); + + // @Before + public void before() { + //tfaKey = TwoFactorAuthUtils.generateTFAKey(); + //System.out.println(tfaKey); + } + + @PerfTest(invocations = 2000000, threads = 16) + @Test + public void test1() { + String tfaKey = TwoFactorAuthUtils.generateTFAKey(); + String tfaCode = TwoFactorAuthUtils.generateTFACode(tfaKey); + + boolean validateTFACode = TwoFactorAuthUtils.validateTFACode(tfaKey, tfaCode); + System.out.println(tfaKey + " " + tfaCode + " " + Thread.currentThread().getName()); + Assert.state(validateTFACode, "验证失败:" + tfaCode); + } + + @Test + public void test() { + + String generateOtpAuthUrl = TwoFactorAuthUtils.generateOtpAuthUrl("jpom@jpom.com", tfaKey); + System.out.println(generateOtpAuthUrl); + + String tfaCode = TwoFactorAuthUtils.generateTFACode(tfaKey); + + boolean validateTFACode = TwoFactorAuthUtils.validateTFACode(tfaKey, tfaCode); + System.out.println(tfaCode); + Assert.state(validateTFACode, "验证失败"); + } +} diff --git a/modules/server/src/test/java/git/TestGit.java b/modules/server/src/test/java/git/TestGit.java deleted file mode 100644 index 49670f6120..0000000000 --- a/modules/server/src/test/java/git/TestGit.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 git; - -import cn.hutool.core.io.FileUtil; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.model.data.RepositoryModel; -import io.jpom.util.GitUtil; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.ListBranchCommand; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.transport.RemoteConfig; -import org.eclipse.jgit.transport.URIish; -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -/** - * @author bwcx_jzy - * @date 2019/7/9 - **/ -public class TestGit { - - @Test - public void test() { - - //System.out.println(Files.exists(FileUtil.file("D:\\jpom\\server\\data\\temp\\gitTemp\\6323b009deeca9367a1e393acd9f26b8\\objects").toPath(), LinkOption.NOFOLLOW_LINKS)); - } - - @Test - public void test2() throws IOException { - String fileOrDirPath = "D:\\jpom\\server\\data\\temp\\"; - List strings = FileUtil.loopFiles(fileOrDirPath); -// for (File file : strings) { -// FilePermission filePermission = new FilePermission(file.getAbsolutePath(), "write"); -// System.out.println(filePermission.implies(filePermission)); -// } - System.out.println(strings.size()); - for (File string : strings) { - string.setWritable(true); - try { - Path path = Paths.get(string.getAbsolutePath()); - Files.delete(path); - } catch (Exception e) { - System.out.println(e.getMessage()); - } - } - FileUtil.clean(fileOrDirPath); - Path path = Paths.get(fileOrDirPath); - Files.delete(path); -// FilePermission filePermission = new FilePermission(fileOrDirPath, "write"); -// System.out.println(filePermission.implies(filePermission)); -// FileUtil.clean(fileOrDirPath); -// FileUtil.del(fileOrDirPath); - - } - - - public static void main(String[] args) throws GitAPIException, IOException, URISyntaxException { - - Git git = Git.init().setDirectory(new File("D:\\tttt")).call(); - RemoteConfig remoteConfig = Git.open(new File("D:\\tttt")).remoteAdd().setUri(new URIish("https://gitee.com/keepbx/Jpom.git")).call(); - System.out.println(remoteConfig); - - List call = git.remoteList().call(); - System.out.println(call); - git.pull().call(); - List list = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call(); - List all = new ArrayList<>(list.size()); - list.forEach(ref -> { - String name = ref.getName(); - if (name.startsWith(Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME)) { - all.add(name.substring((Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME).length() + 1)); - } - }); - System.out.println(all); - } - - - @Test - public void testTag() throws Exception { - - - // -// Git.cloneRepository().setTagOption(TagOpt.FETCH_TAGS) - - String uri = "https://gitee.com/dromara/Jpom.git"; - File file = FileUtil.file("~/test/jpomgit"); - String tagName = "v2.5.2"; - String branchName = "stand-alone"; - -// Git call = Git.cloneRepository() -// .setURI(uri) -// .setDirectory(file) -// .setCredentialsProvider(credentialsProvider) -// .call(); - - PrintWriter printWriter = new PrintWriter(System.out); - RepositoryModel repositoryModel = new RepositoryModel(); - repositoryModel.setGitUrl(uri); - repositoryModel.setRepoType(RepositoryModel.RepoType.Git.getCode()); - repositoryModel.setUserName("a"); - repositoryModel.setPassword("a"); - repositoryModel.setProtocol(GitProtocolEnum.HTTP.getCode()); - - - GitUtil.checkoutPullTag(repositoryModel, file, branchName, tagName, printWriter); - - //GitUtil.checkoutPull(uri, file, branchName, credentialsProvider, printWriter); - - - } - - - @Test - public void testTag2() throws Exception { - String uri = "https://gitee.com/keepbx/Jpom-demo-case.git"; - File file = FileUtil.file("~/test/jpomgit2"); - String tagName = "1.2"; - String branchName = "master"; - UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider("a", "a"); - - PrintWriter printWriter = new PrintWriter(System.out); - - RepositoryModel repositoryModel = new RepositoryModel(); - repositoryModel.setGitUrl(uri); - repositoryModel.setRepoType(0); - repositoryModel.setUserName("a"); - repositoryModel.setPassword("a"); - String msg = GitUtil.checkoutPullTag(repositoryModel, file, branchName, tagName, printWriter); - System.out.println(msg); - //GitUtil.checkoutPull(uri, file, branchName, credentialsProvider, printWriter); - - - } - -} diff --git a/modules/server/src/test/java/io/jpom/ApplicationStartTest.java b/modules/server/src/test/java/io/jpom/ApplicationStartTest.java deleted file mode 100644 index d835246f58..0000000000 --- a/modules/server/src/test/java/io/jpom/ApplicationStartTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom; - -import io.jpom.system.init.InitDb; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; - -/** - * @author Hotstrip - * Test application start, then you can use such as service instance to test your methods - */ -@SpringBootTest(classes = {JpomServerApplication.class, InitDb.class}) -@AutoConfigureMockMvc -public class ApplicationStartTest { - protected static Logger logger = LoggerFactory.getLogger(ApplicationStartTest.class); - @Autowired - protected MockMvc mockMvc; - - @Test - public void testApplicationStart() { - logger.info("Jpom Server Application started....."); - } -} diff --git a/modules/server/src/test/java/io/jpom/controller/build/RepositoryControllerTest.java b/modules/server/src/test/java/io/jpom/controller/build/RepositoryControllerTest.java deleted file mode 100644 index d4d6a2d3d0..0000000000 --- a/modules/server/src/test/java/io/jpom/controller/build/RepositoryControllerTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.controller.build; - -import io.jpom.ApplicationStartTest; -import org.junit.jupiter.api.Test; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; - - -/** - * @author Hotstrip - * Test RepositoryController - */ -public class RepositoryControllerTest extends ApplicationStartTest { - - /** - * 测试加载仓库信息列表 - * @throws Exception - */ - @Test - public void testLoadRepositoryList() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.post("/build/repository/list") - .contentType("application/json")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andDo(MockMvcResultHandlers.print()); - } -} diff --git a/modules/server/src/test/java/io/jpom/monitor/NodeMonitorTest.java b/modules/server/src/test/java/io/jpom/monitor/NodeMonitorTest.java deleted file mode 100644 index 66076ea7cf..0000000000 --- a/modules/server/src/test/java/io/jpom/monitor/NodeMonitorTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.monitor; - -import org.junit.Test; - -public class NodeMonitorTest { - - - @Test - public void test() { - - } - -} \ No newline at end of file diff --git a/modules/server/src/test/java/io/jpom/service/h2db/H2ToolTest.java b/modules/server/src/test/java/io/jpom/service/h2db/H2ToolTest.java deleted file mode 100644 index 17bb7d2a50..0000000000 --- a/modules/server/src/test/java/io/jpom/service/h2db/H2ToolTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 io.jpom.service.h2db; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; -import cn.hutool.db.ds.DSFactory; -import cn.hutool.db.sql.SqlLog; -import cn.hutool.setting.Setting; -import io.jpom.ApplicationStartTest; -import io.jpom.JpomApplication; -import io.jpom.system.ExtConfigBean; -import io.jpom.system.ServerExtConfigBean; -import io.jpom.system.db.DbConfig; -import org.h2.tools.RunScript; -import org.h2.tools.Shell; -import org.junit.jupiter.api.Test; - -import javax.sql.DataSource; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.sql.Connection; -import java.sql.SQLException; - -/** - * @author Hotstrip - * @since 2021-11-01 - * 测试 H2 数据库工具类 - */ -public class H2ToolTest extends ApplicationStartTest { - - /** - * 备份 SQL 文件 - * 如果抛出了异常或者指定的备份文件不存在就表示备份不成功 - * @throws SQLException - */ - @Test - public void testH2ShellForBackupSQL() throws SQLException { - // 数据源参数 - String url = DbConfig.getInstance().getDbUrl(); - - ServerExtConfigBean serverExtConfigBean = ServerExtConfigBean.getInstance(); - String user = serverExtConfigBean.getDbUserName(); - String pass = serverExtConfigBean.getDbUserPwd(); - - // 备份 SQL 的目录 - File file = FileUtil.file(ExtConfigBean.getInstance().getPath(), "db", JpomApplication.getAppType().name()); - String path = FileUtil.getAbsolutePath(file); - - logger.info("url: {}", url); - logger.info("user: {}", user); - logger.info("pass: {}", pass); - logger.info("backup sql path: {}", path); - - // 加载数据源 - DataSource dataSource = DSFactory.get(); - if (null == dataSource) { - dataSource = initDataSource(url, user, pass); - } - Connection connection = dataSource.getConnection(); - - // 执行 SQL 备份脚本 - Shell shell = new Shell(); - - /** - * url 表示 h2 数据库的 jdbc url - * user 表示登录的用户名 - * password 表示登录密码 - * driver 是 jdbc 驱动 - * sql 是备份的 sql 语句 - * - 案例:script drop to ${fileName1} table ${tableName1},${tableName2}... - * - script drop to 表示备份数据库,drop 表示建表之前会先删除表 - * - ${fileName1} 表示备份之后的文件名 - * - table 表示需要备份的表名称,后面跟多个表名,用英文逗号分割 - */ - String[] params = new String[] { - "-url", url, - "-user", user, - "-password", pass, - "-driver", "org.h2.Driver", - "-sql", "script DROP to '"+ path +"/backup.sql' table BUILD_INFO,USEROPERATELOGV1" - }; - shell.runTool(connection, params); - } - - /** - * 还原 SQL 文件 - */ - @Test - public void testH2RunScriptWithBackupSQL() throws SQLException, FileNotFoundException { - // 备份 SQL - testH2ShellForBackupSQL(); - - // 恢复之前先删除数据库数据,以免冲突 - testH2DropAllObjects(); - - // 备份 SQL 的目录 - File file = FileUtil.file(ExtConfigBean.getInstance().getPath(), "db", JpomApplication.getAppType().name()); - String path = FileUtil.getAbsolutePath(file) + "/backup.sql"; - - FileReader fileReader = new FileReader(path); - - // 加载数据源 - DataSource dataSource = DSFactory.get(); - Connection connection = dataSource.getConnection(); - - RunScript.execute(connection, fileReader); - } - - /** - * H2 drop all objects - * 删除 H2 数据库所有数据 - * @throws SQLException - */ - @Test - public void testH2DropAllObjects() throws SQLException { - // 数据源参数 - String url = DbConfig.getInstance().getDbUrl(); - - ServerExtConfigBean serverExtConfigBean = ServerExtConfigBean.getInstance(); - String user = serverExtConfigBean.getDbUserName(); - String pass = serverExtConfigBean.getDbUserPwd(); - - // 加载数据源 - DataSource dataSource = DSFactory.get(); - Connection connection = dataSource.getConnection(); - - // 执行 SQL 备份脚本 - Shell shell = new Shell(); - - String[] params = new String[] { - "-url", url, - "-user", user, - "-password", pass, - "-driver", "org.h2.Driver", - "-sql", "drop all objects" - }; - shell.runTool(connection, params); - } - - /** - * 初始化数据源 - * @return - */ - private DataSource initDataSource(String url, String user, String pass) { - // 数据源配置 - Setting setting = new Setting(); - setting.set("url", url); - setting.set("user", user); - setting.set("pass", pass); - // 配置连接池大小 - setting.set("maxActive", "50"); - setting.set("initialSize", "1"); - setting.set("maxWait", "10"); - setting.set("minIdle", "1"); - // show sql - setting.set(SqlLog.KEY_SHOW_SQL, "true"); - setting.set(SqlLog.KEY_SQL_LEVEL, "DEBUG"); - setting.set(SqlLog.KEY_SHOW_PARAMS, "true"); - Console.log("start load h2 db"); - // 创建连接 - DSFactory dsFactory = DSFactory.create(setting); - return dsFactory.getDataSource(); - } - -} diff --git a/modules/server/src/test/java/jdbc/conn/h2/test/H2ConnTest1.java b/modules/server/src/test/java/jdbc/conn/h2/test/H2ConnTest1.java deleted file mode 100644 index 78754b3967..0000000000 --- a/modules/server/src/test/java/jdbc/conn/h2/test/H2ConnTest1.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 jdbc.conn.h2.test; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.Statement; - -/** - *

ClassName: H2ConnTest1

- *

Description: Java通过JDBC方式连接H2数据库

- * @author xudp - * @version 1.0 V - * @createTime 2014-12-18 上午11:22:12 - */ -public class H2ConnTest1 { - //数据库连接URL,当前连接的是E:/H2目录下的gacl数据库 - private static final String JDBC_URL = "jdbc:h2:D:\\jpom\\server\\db\\Server"; - //连接数据库时使用的用户名 - private static final String USER = "jpom"; - //连接数据库时使用的密码 - private static final String PASSWORD = "jpom"; - //连接H2数据库时使用的驱动类,org.h2.Driver这个类是由H2数据库自己提供的,在H2数据库的jar包中可以找到 - private static final String DRIVER_CLASS = "org.h2.Driver"; - - public static void main(String[] args) throws Exception { - // 加载H2数据库驱动 - Class.forName(DRIVER_CLASS); - // 根据连接URL,用户名,密码获取数据库连接 - Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); - Statement stmt = conn.createStatement(); - - //查询 - ResultSet rs = stmt.executeQuery("SELECT * FROM UserOperateLogV1"); - //遍历结果集 - while (rs.next()) { - System.out.println(rs.getString("userId")); - } - //释放资源 - stmt.close(); - //关闭连接 - conn.close(); - } -} \ No newline at end of file diff --git a/modules/server/src/test/java/org/dromara/jpom/ApplicationStartTest.java b/modules/server/src/test/java/org/dromara/jpom/ApplicationStartTest.java new file mode 100644 index 0000000000..80bbeaea5f --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/ApplicationStartTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.system.db.InitDb; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import javax.annotation.Resource; + +/** + * @author Hotstrip + * Test application start, then you can use such as service instance to test your methods + */ +@SpringBootTest(classes = {JpomServerApplication.class, InitDb.class, PluginFactory.class}) +@AutoConfigureMockMvc +@EnableAutoConfiguration +@ContextConfiguration(initializers = PluginFactory.class) +@Slf4j +public class ApplicationStartTest { + + @Autowired + protected MockMvc mockMvc; + + @Resource + protected DbExtConfig dbExtConfig; + + + @Test + public void testApplicationStart() { + log.info("Jpom Server Application started....."); + } + + @Test + public void testServerExtConfigBean() { + // ServerExtConfigBean serverExtConfigBean = SpringUtil.getBean(ServerExtConfigBean.class); + //System.out.println(serverExtConfigBean); + } + +} diff --git a/modules/server/src/test/java/org/dromara/jpom/BuildTriggerTest.java b/modules/server/src/test/java/org/dromara/jpom/BuildTriggerTest.java new file mode 100644 index 0000000000..bdc41aceca --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/BuildTriggerTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import org.dromara.jpom.common.ServerOpenApi; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +/** + * @author bwcx_jzy + * @since 2021/12/14 + */ +public class BuildTriggerTest { + + @Test + public void test() { + // 8cf594526db74f0eb79cac6da141c655/219a4009a0a68173d8d643d237f2ca8ad797d41dc5bcfceb83da4f4f1d1dbe933a1 + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", "8cf594526db74f0eb79cac6da141c655"); + jsonObject.put("token", "219a4009a0a68173d8d643d237f2ca8ad797d41dc5bcfceb83da4f4f1d1dbe933a1"); + // + JSONArray jsonArray = new JSONArray(); + jsonArray.add(jsonObject); + // + HttpRequest post = HttpUtil.createPost("http://127.0.0.1:2122/" + ServerOpenApi.BUILD_TRIGGER_BUILD_BATCH); + post.body(jsonArray.toString(), MediaType.APPLICATION_JSON_VALUE); + String body = post.execute().body(); + System.out.println(body); + } +} diff --git a/modules/server/src/test/java/org/dromara/jpom/DockerInfoTest.java b/modules/server/src/test/java/org/dromara/jpom/DockerInfoTest.java new file mode 100644 index 0000000000..580b29c3dc --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/DockerInfoTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import org.dromara.jpom.common.Const; +import org.dromara.jpom.model.docker.DockerInfoModel; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.junit.jupiter.api.Test; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2022/2/6 + */ +public class DockerInfoTest extends ApplicationStartTest { + + @Resource + private DockerInfoService dockerInfoService; + + @Test + public void testQueryTag() { + int sdfsd = dockerInfoService.countByTag(Const.WORKSPACE_DEFAULT_ID, "sdfsd"); + + System.out.println(sdfsd); + } + + @Test + public void testQueryTag2() { + List models = dockerInfoService.queryByTag(Const.WORKSPACE_DEFAULT_ID, "sdfsd"); + System.out.println(models); + } +} diff --git a/modules/server/src/test/java/org/dromara/jpom/H2TableToCsv2Test.java b/modules/server/src/test/java/org/dromara/jpom/H2TableToCsv2Test.java new file mode 100644 index 0000000000..f94f4693a6 --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/H2TableToCsv2Test.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.text.csv.CsvUtil; +import cn.hutool.core.text.csv.CsvWriter; +import cn.hutool.core.util.*; +import cn.hutool.db.Db; +import cn.hutool.db.Entity; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.db.IStorageService; +import org.dromara.jpom.db.StorageServiceFactory; +import org.dromara.jpom.db.TableName; +import org.junit.jupiter.api.Test; +import org.springframework.util.Assert; + +import java.io.File; +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * SELECT * FROM INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA='PUBLIC' + *

+ * SELECT * FROM INFORMATION_SCHEMA.INDEXES where index_type_name='UNIQUE INDEX' + * + * @author bwcx_jzy + * @since 2023/1/5 + */ +@Slf4j +public class H2TableToCsv2Test extends ApplicationStartTest { + + @Test + public void init() throws SQLException { + Set> classes = ClassUtil.scanPackageByAnnotation("org.dromara.jpom", TableName.class); + Map> TABLE_NAME_MAP = CollStreamUtil.toMap(classes, aClass -> { + TableName tableName = aClass.getAnnotation(TableName.class); + return tableName.value(); + }, aClass -> { + Map fieldMap = ReflectUtil.getFieldMap(aClass); + return new CaseInsensitiveMap<>(fieldMap); + }); + IStorageService iStorageService = StorageServiceFactory.get(); + List query = Db.use(iStorageService.getDsFactory().getDataSource()).query("SELECT * FROM INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA='PUBLIC'"); + + Map> listMap = query.stream().map(entity -> { + JSONObject jsonObject = new JSONObject(); + String remarks = entity.getStr("remarks"); + String columnName = entity.getStr("column_name"); + String dataType = entity.getStr("data_type"); + String tableName = entity.getStr("table_name"); + String columnDefault = entity.getStr("column_default"); + String isNullable = entity.getStr("is_nullable"); +// String is_identity = entity.getStr("is_identity"); + Integer character_octet_length = entity.getInt("character_octet_length"); +// log.debug("{}-{}【{}】 {} {} {} {} {}", tableName, columnName, remarks, dataType, character_octet_length, columnDefault, isNullable, is_identity); + Map stringFieldMap = TABLE_NAME_MAP.get(tableName); + Assert.notNull(stringFieldMap, "没有找到对应的表名" + tableName); + Field field = stringFieldMap.get(columnName); + Assert.notNull(field, tableName + " 没有对应的字段:" + columnName); + //System.out.println(entity); + jsonObject.put("tableName", tableName); + jsonObject.put("columnName", field.getName()); + jsonObject.put("remarks", remarks); + jsonObject.put("dataType", convertDataType(dataType)); + jsonObject.put("len", character_octet_length); + jsonObject.put("defaultValue", columnDefault); + jsonObject.put("isNullable", BooleanUtil.toBoolean(isNullable)); + jsonObject.put("ordinalPosition", entity.getInt("ordinal_position")); + // dtd_identifier + String tableRemarks = null; + try { + tableRemarks = this.tableRemarks(tableName); + } catch (SQLException e) { + throw new RuntimeException(e); + } + jsonObject.put("tableRemarks", tableRemarks); + + return jsonObject; + }).collect(Collectors.groupingBy(o -> o.getString("tableName"), Collectors.toList())); + CsvWriter writer = CsvUtil.getWriter(new File("table.all.v1.0.csv"), CharsetUtil.CHARSET_UTF_8); + // tableName,name,type,len,defaultValue,notNull,primaryKey,comment,tableComment + writer.writeLine("tableName", "name", "type", "len", "defaultValue", "notNull", "primaryKey", "comment", "tableComment"); + for (List list : listMap.values()) { + list.sort(Comparator.comparing(o -> o.getInteger("ordinalPosition"))); + for (JSONObject jsonObject : list) { + String tableName = jsonObject.getString("tableName"); + String columnName = jsonObject.getString("columnName"); + String remarks = jsonObject.getString("remarks"); + String dataType = jsonObject.getString("dataType"); + Integer len = jsonObject.getInteger("len"); + String lenStr = len == null ? StrUtil.EMPTY : len <= 0 ? StrUtil.EMPTY : len + ""; + String defaultValue = jsonObject.getString("defaultValue"); + String tableRemarks = jsonObject.getString("tableRemarks"); + Boolean nullable = jsonObject.getBoolean("isNullable"); + + writer.writeLine(tableName, columnName, dataType, lenStr, defaultValue, String.valueOf(!nullable), + StrUtil.equals("id", columnName) ? Boolean.TRUE.toString() : Boolean.FALSE.toString(), remarks, + StrUtil.equals("id", columnName) ? tableRemarks : StrUtil.EMPTY); + } + } + writer.flush(); + System.out.println(listMap); + } + + private String tableRemarks(String tableName) throws SQLException { + String sql = "SELECT * FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='PUBLIC' and TABLE_NAME=?"; + IStorageService iStorageService = StorageServiceFactory.get(); + List query = Db.use(iStorageService.getDsFactory().getDataSource()).query(sql, tableName); + Entity entity = CollUtil.getFirst(query); + return entity.getStr("REMARKS"); + } + + private String convertDataType(String dataType) { + switch (dataType) { + case "BIGINT": + return Long.class.getSimpleName(); + case "CHARACTER VARYING": + return String.class.getSimpleName(); + case "CHARACTER LARGE OBJECT": + return "TEXT"; + case "INTEGER": + return Integer.class.getSimpleName(); + case "TINYINT": + return "TINYINT"; + case "REAL": + return Float.class.getSimpleName(); + case "DOUBLE PRECISION": + return Double.class.getSimpleName(); + default: + throw new IllegalArgumentException("未知的数据类型:" + dataType); + } + } +} diff --git a/modules/server/src/test/java/org/dromara/jpom/controller/build/RepositoryControllerTest.java b/modules/server/src/test/java/org/dromara/jpom/controller/build/RepositoryControllerTest.java new file mode 100644 index 0000000000..51ac4c85c2 --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/controller/build/RepositoryControllerTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.controller.build; + +import org.dromara.jpom.ApplicationStartTest; +import org.junit.jupiter.api.Test; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + + +/** + * @author Hotstrip + * Test RepositoryController + */ +public class RepositoryControllerTest extends ApplicationStartTest { + + /** + * 测试加载仓库信息列表 + * @throws Exception + */ + @Test + public void testLoadRepositoryList() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/build/repository/list") + .contentType("application/json")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()); + } +} diff --git a/modules/server/src/test/java/org/dromara/jpom/git/TestGit.java b/modules/server/src/test/java/org/dromara/jpom/git/TestGit.java new file mode 100644 index 0000000000..6fa2025aec --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/git/TestGit.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.git; + +import cn.keepbx.jpom.plugins.IPlugin; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.ApplicationStartTest; +import org.dromara.jpom.plugin.PluginFactory; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2023/4/10 + */ +@Slf4j +public class TestGit extends ApplicationStartTest { + + + @Test + public void test1() { + System.out.println(1); + } + + @Test + public void test() throws Exception { + IPlugin plugin = PluginFactory.getPlugin("git-clone"); + Map map = new HashMap<>(); +// map.put("gitProcessType", "JGit"); + map.put("gitProcessType", "SystemGit"); + map.put("url", "git@gitee.com:keepbx/Jpom-demo-case.git");//git@github.com:emqx/emqx-operator.git +// map.put("url", "https://gitee.com/keepbx/Jpom-demo-case"); +// map.put("rsaFile", FileUtil.file(FileUtil.getUserHomePath(), ".ssh", "id_rsa")); + map.put("reduceProgressRatio", 1); + map.put("timeout", 60); + map.put("protocol", 1); + map.put("username", ""); + map.put("password", ""); + Object obj = plugin.execute("branchAndTagList", map); + System.err.println(obj); + } +} diff --git a/modules/server/src/test/java/org/dromara/jpom/monitor/NodeMonitorTest.java b/modules/server/src/test/java/org/dromara/jpom/monitor/NodeMonitorTest.java new file mode 100644 index 0000000000..69a0ca5e90 --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/monitor/NodeMonitorTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.monitor; + +import org.junit.Test; + +public class NodeMonitorTest { + + + @Test + public void test() { + + } + +} diff --git a/modules/server/src/test/java/org/dromara/jpom/service/h2db/H2BackupTest.java b/modules/server/src/test/java/org/dromara/jpom/service/h2db/H2BackupTest.java new file mode 100644 index 0000000000..5d8ba84a5e --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/service/h2db/H2BackupTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.h2db; + +import org.dromara.jpom.ApplicationStartTest; +import org.dromara.jpom.service.dblog.BackupInfoService; +import org.junit.jupiter.api.Test; + +import javax.annotation.Resource; + +/** + * @author bwcx_jzy + * @since 2022/2/9 + */ +public class H2BackupTest extends ApplicationStartTest { + + @Resource + protected BackupInfoService backupInfoService; + + @Test + public void testBackup() { + backupInfoService.executeTask(); + } + + @Test + public void testAuto() { + backupInfoService.autoBackup(); + } +} diff --git a/modules/server/src/test/java/org/dromara/jpom/service/h2db/H2ToolTest.java b/modules/server/src/test/java/org/dromara/jpom/service/h2db/H2ToolTest.java new file mode 100644 index 0000000000..a8379e362d --- /dev/null +++ b/modules/server/src/test/java/org/dromara/jpom/service/h2db/H2ToolTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.service.h2db; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.sql.SqlLog; +import cn.hutool.setting.Setting; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.ApplicationStartTest; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.db.StorageServiceFactory; +import org.dromara.jpom.system.ExtConfigBean; +import org.h2.tools.RunScript; +import org.h2.tools.Shell; +import org.junit.jupiter.api.Test; + +import javax.sql.DataSource; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * @author Hotstrip + * @since 2021-11-01 + * 测试 H2 数据库工具类 + */ +@Slf4j +public class H2ToolTest extends ApplicationStartTest { + + + /** + * 备份 SQL 文件 + * 如果抛出了异常或者指定的备份文件不存在就表示备份不成功 + * + * @throws SQLException + */ + @Test + public void testH2ShellForBackupSQL() throws SQLException { + // 数据源参数 + String url = StorageServiceFactory.get().dbUrl(); + + String user = dbExtConfig.userName(); + String pass = dbExtConfig.userPwd(); + + // 备份 SQL 的目录 + File file = FileUtil.file(ExtConfigBean.getPath(), "db", JpomApplication.getAppType().name()); + String path = FileUtil.getAbsolutePath(file); + + log.info("url: {}", url); + log.info("user: {}", user); + log.info("pass: {}", pass); + log.info("backup sql path: {}", path); + + // 加载数据源 + DataSource dataSource = DSFactory.get(); + if (null == dataSource) { + dataSource = initDataSource(url, user, pass); + } + Connection connection = dataSource.getConnection(); + + // 执行 SQL 备份脚本 + Shell shell = new Shell(); + + /** + * url 表示 h2 数据库的 jdbc url + * user 表示登录的用户名 + * password 表示登录密码 + * driver 是 jdbc 驱动 + * sql 是备份的 sql 语句 + * - 案例:script drop to ${fileName1} table ${tableName1},${tableName2}... + * - script drop to 表示备份数据库,drop 表示建表之前会先删除表 + * - ${fileName1} 表示备份之后的文件名 + * - table 表示需要备份的表名称,后面跟多个表名,用英文逗号分割 + */ + String[] params = new String[]{ + "-url", url, + "-user", user, + "-password", pass, + "-driver", "org.h2.Driver", + "-sql", "script DROP to '" + path + "/backup.sql' table BUILD_INFO,USEROPERATELOGV1" + }; + shell.runTool(connection, params); + } + + /** + * 还原 SQL 文件 + */ + @Test + public void testH2RunScriptWithBackupSQL() throws SQLException, FileNotFoundException { + // 备份 SQL + testH2ShellForBackupSQL(); + + // 恢复之前先删除数据库数据,以免冲突 + testH2DropAllObjects(); + + // 备份 SQL 的目录 + File file = FileUtil.file(ExtConfigBean.getPath(), "db", JpomApplication.getAppType().name()); + String path = FileUtil.getAbsolutePath(file) + "/backup.sql"; + + FileReader fileReader = new FileReader(path); + + // 加载数据源 + DataSource dataSource = DSFactory.get(); + Connection connection = dataSource.getConnection(); + + RunScript.execute(connection, fileReader); + } + + /** + * H2 drop all objects + * 删除 H2 数据库所有数据 + * + * @throws SQLException + */ + @Test + public void testH2DropAllObjects() throws SQLException { + // 数据源参数 + String url = StorageServiceFactory.get().dbUrl(); + + String user = dbExtConfig.userName(); + String pass = dbExtConfig.userPwd(); + + // 加载数据源 + DataSource dataSource = DSFactory.get(); + Connection connection = dataSource.getConnection(); + + // 执行 SQL 备份脚本 + Shell shell = new Shell(); + + String[] params = new String[]{ + "-url", url, + "-user", user, + "-password", pass, + "-driver", "org.h2.Driver", + "-sql", "drop all objects" + }; + shell.runTool(connection, params); + } + + /** + * 初始化数据源 + * + * @return + */ + private DataSource initDataSource(String url, String user, String pass) { + // 数据源配置 + Setting setting = new Setting(); + setting.set("url", url); + setting.set("user", user); + setting.set("pass", pass); + // 配置连接池大小 + setting.set("maxActive", "50"); + setting.set("initialSize", "1"); + setting.set("maxWait", "10"); + setting.set("minIdle", "1"); + // show sql + setting.set(SqlLog.KEY_SHOW_SQL, "true"); + setting.set(SqlLog.KEY_SQL_LEVEL, "DEBUG"); + setting.set(SqlLog.KEY_SHOW_PARAMS, "true"); + log.info("start load h2 db"); + // 创建连接 + DSFactory dsFactory = DSFactory.create(setting); + return dsFactory.getDataSource(); + } + +} diff --git a/modules/server/src/test/java/org/springframework/boot/env/TestYml.java b/modules/server/src/test/java/org/springframework/boot/env/TestYml.java index 6e1c81bc65..ade7add6ac 100644 --- a/modules/server/src/test/java/org/springframework/boot/env/TestYml.java +++ b/modules/server/src/test/java/org/springframework/boot/env/TestYml.java @@ -1,24 +1,11 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package org.springframework.boot.env; @@ -30,7 +17,7 @@ /** * @author bwcx_jzy - * @date 2019/8/17 + * @since 2019/8/17 */ public class TestYml { diff --git a/modules/server/src/test/java/svn/TestSvn.java b/modules/server/src/test/java/svn/TestSvn.java deleted file mode 100644 index a8cd4636bb..0000000000 --- a/modules/server/src/test/java/svn/TestSvn.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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 svn; - -import io.jpom.model.data.RepositoryModel; -import io.jpom.model.enums.GitProtocolEnum; -import io.jpom.util.SvnKitUtil; -import org.tmatesoft.svn.core.SVNException; - -import java.io.File; - -/** - * @author bwcx_jzy - * @date 2019/8/6 - */ -public class TestSvn { - public static void main(String[] args) throws SVNException { - - RepositoryModel repositoryModel = new RepositoryModel(); - repositoryModel.setGitUrl("svn://gitee.com/keepbx/Jpom-demo-case"); - repositoryModel.setRepoType(RepositoryModel.RepoType.Svn.getCode()); - repositoryModel.setUserName("a"); - repositoryModel.setPassword("a"); - repositoryModel.setProtocol(GitProtocolEnum.SSH.getCode()); - - String s = SvnKitUtil.checkOut(repositoryModel, new File("/test/tt")); - System.out.println(s); - } -} diff --git a/modules/storage-module/pom.xml b/modules/storage-module/pom.xml new file mode 100644 index 0000000000..37bd4784b3 --- /dev/null +++ b/modules/storage-module/pom.xml @@ -0,0 +1,176 @@ + + + + + jpom-parent + org.dromara.jpom + 2.11.6.6 + ../../pom.xml + + pom + + storage-module-common + storage-module-h2 + storage-module-mysql + storage-module-postgresql + storage-module-mariadb + + 4.0.0 + 2.11.6.6 + org.dromara.jpom.storage-module + jpom-storage-module-parent + Jpom storage module + + Jpom 存储模块 + + + UTF-8 + 1.8 + + true + true + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + once + -Dfile.encoding=UTF-8 + + + + + + + + release + + + maven-repo + https://oss.sonatype.org/content/repositories/snapshots/ + + + maven-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + attach-javadoc + package + + jar + + + + + + + date + a + 创建时间 + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + verify-gpg + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + + + + + master + git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + + + + bwcx_jzy + bwcx_jzy@163.com + bwcx_jzy + + + + diff --git a/modules/storage-module/storage-module-common/pom.xml b/modules/storage-module/storage-module-common/pom.xml new file mode 100644 index 0000000000..09409a5005 --- /dev/null +++ b/modules/storage-module/storage-module-common/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + + org.dromara.jpom.storage-module + jpom-storage-module-parent + 2.11.6.6 + ../pom.xml + + + storage-module-common + + + 8 + 8 + UTF-8 + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + cn.hutool + hutool-db + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + 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 new file mode 100644 index 0000000000..1b58c87967 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/BaseDbCommonService.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.PageUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; +import cn.hutool.db.Db; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import cn.hutool.db.PageResult; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.sql.Condition; +import cn.hutool.extra.spring.SpringUtil; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.dialect.DialectUtil; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.util.Assert; + +import javax.sql.DataSource; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * 数据库基础操作 通用 service + * + * @author bwcx_jzy + * @since 2019/7/20 + */ +@Slf4j +public abstract class BaseDbCommonService { + + static { + // 配置页码是从 1 开始 + PageUtil.setFirstPageNo(1); + } + + /** + * String const + */ + public static final String ID_STR = "id"; + + /** + * 表名 + */ + @Getter + protected final String tableName; + protected final Class tClass; + protected final DbExtConfig.Mode dbMode; + + @SuppressWarnings("unchecked") + public BaseDbCommonService() { + this.tClass = (Class) TypeUtil.getTypeArgument(this.getClass()); + TableName annotation = tClass.getAnnotation(TableName.class); + Assert.notNull(annotation, I18nMessageUtil.get("i18n.configure_table_name.f6fd")); + this.tableName = annotation.value(); + this.dbMode = SpringUtil.getBean(DbExtConfig.class).getMode(); + } + + public String getDataDesc() { + TableName annotation = tClass.getAnnotation(TableName.class); + Assert.notNull(annotation, I18nMessageUtil.get("i18n.configure_table_name.f6fd")); + return I18nMessageUtil.get(annotation.nameKey()); + } + + protected DataSource getDataSource() { + DSFactory dsFactory = StorageServiceFactory.get().getDsFactory(); + return dsFactory.getDataSource(); + } + + /** + * 插入数据 + * + * @param t 数据 + */ + protected final int insertDb(T t) { + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + try { + Entity entity = this.dataBeanToEntity(t); + return db.insert(entity); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 插入数据 + * + * @param t 数据 + */ + protected final void insertDb(Collection t) { + if (CollUtil.isEmpty(t)) { + return; + } + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + try { + List entities = t.stream().map(this::dataBeanToEntity).collect(Collectors.toList()); + db.insert(entities); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 实体转 entity + * + * @param data 实体对象 + * @return entity + */ + public Entity dataBeanToEntity(T data) { + Entity entity = new Entity(tableName); + // 转换为 map + Map beanToMap = BeanUtil.beanToMap(data, new LinkedHashMap<>(), true, DialectUtil::wrapField); + entity.putAll(beanToMap); + return entity; + } + + + /** + * 修改数据 + * + * @param entity 要修改的数据 + * @param where 条件 + * @return 影响行数 + */ + protected final int updateDb(Entity entity, Entity where) { + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + if (where.isEmpty()) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.update_condition_not_found.0870")); + } + entity.setTableName(tableName); + where.setTableName(tableName); + try { + return db.update(entity, where); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 根据主键查询实体 + * + * @param keyValue 主键值 + * @param fill 是否执行填充逻辑 + * @param consumer 参数回调 + * @return 数据 + */ + public final T getByKey(String keyValue, boolean fill, Consumer consumer) { + if (StrUtil.isEmpty(keyValue)) { + return null; + } + Entity where = new Entity(tableName); + where.set(ID_STR, keyValue); + Entity entity; + try { + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + if (consumer != null) { + consumer.accept(where); + } + entity = db.get(where); + } catch (Exception e) { + throw warpException(e); + } + return this.entityToBean(entity, fill); + } + + /** + * 根据主键查询实体 + * + * @param keyValue 主键值 + * @param fill 是否执行填充逻辑 + * @param consumer 参数回调 + * @return 数据 + */ + public final List getByKey(Collection keyValue, boolean fill, Consumer consumer) { + if (CollUtil.isEmpty(keyValue)) { + return null; + } + Entity where = new Entity(tableName); + where.set(ID_STR, keyValue); + List entities; + try { + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + if (consumer != null) { + consumer.accept(where); + } + entities = db.find(where); + } catch (Exception e) { + throw warpException(e); + } + return this.entityToBeanList(entities, fill); + } + + /** + * 根据条件删除 + * + * @param where 条件 + * @return 影响行数 + */ + public final int del(Entity where) { + where.setTableName(tableName); + if (where.isEmpty()) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.no_deletion_condition.19d0")); + } + try { + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + return db.del(where); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 查询记录条数 + * + * @param where 条件 + * @return count + */ + public final long count(Entity where) { + where.setTableName(getTableName()); + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + try { + return db.count(where); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 查询记录条数 + * + * @param sql sql + * @return count + */ + public final long count(String sql, Object... params) { + try { + return Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)).count(sql, params); + } catch (Exception e) { + throw warpException(e); + } + } + + + /** + * 查询列表 + * + * @param where 条件 + * @return List + */ + public final List queryList(Entity where) { + where.setTableName(getTableName()); + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + try { + return db.find(where); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 查询列表 + * + * @param wheres 条件 + * @return List + */ + public final List findByCondition(Condition... wheres) { + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + try { + List entities = db.findBy(getTableName(), wheres); + return this.entityToBeanList(entities); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * entity 转 实体 + * + * @param entity Entity + * @param fill 是否填充 + * @return data + */ + protected T entityToBean(Entity entity, boolean fill) { + if (entity == null) { + return null; + } + CopyOptions copyOptions = new CopyOptions(); + copyOptions.setIgnoreError(true); + copyOptions.setIgnoreCase(true); + T toBean = BeanUtil.toBean(entity, this.tClass, copyOptions); + if (fill) { + this.fillSelectResult(toBean); + } + return toBean; + } + + public List entityToBeanList(List entitys) { + return this.entityToBeanList(entitys, true); + } + + public List entityToBeanList(List entitys, boolean fill) { + if (entitys == null) { + return null; + } + return entitys.stream() + .map((entity -> this.entityToBean(entity, fill))) + .collect(Collectors.toList()); + } + + /** + * 分页查询 + * + * @param where 条件 + * @param page 分页 + * @param fill 是否填充 + * @return 结果 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public final PageResultDto listPageDb(Entity where, Page page, boolean fill) { + where.setTableName(getTableName()); + PageResult pageResult; + Db db = Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)); + try { + pageResult = db.page(where, page); + } catch (Exception e) { + throw warpException(e); + } + // + List list = this.entityToBeanList(pageResult, fill); + PageResultDto pageResultDto = new PageResultDto(pageResult); + pageResultDto.setResult(list); + if (pageResultDto.isEmpty() && pageResultDto.getPage() > 1) { + Assert.state(pageResultDto.getTotal() <= 0, I18nMessageUtil.get("i18n.pagination_error.6759")); + } + return pageResultDto; + } + + + /** + * sql 查询 + * + * @param sql sql 语句 + * @param params 参数 + * @return list + */ + public final List query(String sql, Object... params) { + try { + return Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)).query(sql, params); + } catch (Exception e) { + throw warpException(e); + } + } + + public Number queryNumber(String sql, Object... params) { + try { + return Db.use(this.getDataSource()).queryNumber(sql, params); + } catch (Exception e) { + throw warpException(e); + } + } + + + /** + * sql 执行 + * + * @param sql sql 语句 + * @param params 参数 + * @return list + */ + public final int execute(String sql, Object... params) { + try { + return Db.use(this.getDataSource(), DialectUtil.getDialectByMode(dbMode)).execute(sql, params); + } catch (Exception e) { + throw warpException(e); + } + } + + /** + * 查询结果 填充 + * + * @param data 数据 + */ + protected void fillSelectResult(T data) { + } + + /** + * 包裹异常 + * + * @param e 异常 + */ + private JpomRuntimeException warpException(Exception e) { + return StorageServiceFactory.get().warpException(e); + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java new file mode 100644 index 0000000000..f5f3ab19df --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.sql.SqlLog; +import cn.hutool.setting.Setting; +import lombok.Data; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +/** + * 数据库配置 + * + * @author bwcx_jzy + * @since 2022/2/9 + */ +@Configuration +@ConfigurationProperties(prefix = "jpom.db") +@Data +public class DbExtConfig implements InitializingBean { + + /** + * 默认的账号或者密码 + */ + public static final String DEFAULT_USER_OR_AUTHORIZATION = "jpom"; + + /** + * SQL backup default directory name + * 数据库备份默认目录名称 + */ + public static final String BACKUP_DIRECTORY_NAME = "backup"; + /** + * 备份 SQL 文件 后缀 + */ + public static final String SQL_FILE_SUFFIX = ".sql"; + + /** + * 日志记录最大条数 + */ + private Integer logStorageCount = 10000; + /** + * 数据库默认 + */ + private Mode mode = Mode.H2; + /** + * 数据库 url + */ + private String url; + /** + * 数据库账号、默认为 jpom + */ + private String userName; + + /** + * 数据库密码、默认为 jpom + */ + private String userPwd; + + /** + * 缓存大小 + *

+ * http://www.h2database.com/html/features.html#cache_settings + */ + private DataSize cacheSize = DataSize.ofMegabytes(10); + + /** + * 自动全量备份数据库间隔天数 小于等于 0,不自动备份 + */ + private Integer autoBackupIntervalDay = 1; + + /** + * 自动备份保留天数 小于等于 0,不自动删除自动备份数据 + */ + private int autoBackupReserveDay = 5; + + private int maxActive = 100; + + private int initialSize = 10; + + private int maxWait = 10; + + private int minIdle = 1; + /** + * @see cn.hutool.db.sql.SqlLog#KEY_SHOW_SQL + */ + private Boolean showSql = false; + + public String userName() { + return StrUtil.emptyToDefault(this.userName, DbExtConfig.DEFAULT_USER_OR_AUTHORIZATION); + } + + public String userPwd() { + return StrUtil.emptyToDefault(this.userPwd, DbExtConfig.DEFAULT_USER_OR_AUTHORIZATION); + } + + public Setting toSetting() { + // + Setting setting = new Setting(); + setting.set("user", this.userName()); + setting.set("pass", this.userPwd()); + setting.set("url", this.getUrl()); + // 配置连接池大小 + setting.set("maxActive", this.getMaxActive() + ""); + setting.set("initialSize", this.getInitialSize() + ""); + setting.set("maxWait", this.getMaxWait() + ""); + setting.set("minIdle", this.getMinIdle() + ""); + // 调试模式显示sql 信息 + if (this.getShowSql()) { + + setting.set(SqlLog.KEY_SHOW_SQL, "true"); + /* + @author Hotstrip + sql log only show when it's needed, + if you want to check init sql, + set the [sqlLevel] from [DEBUG] to [INFO] + */ + setting.set(SqlLog.KEY_SQL_LEVEL, "DEBUG"); + setting.set(SqlLog.KEY_SHOW_PARAMS, "true"); + } + return setting; + } + + @Override + public void afterPropertiesSet() throws Exception { + StorageServiceFactory.setMode(this.getMode()); + } + + public enum Mode { + /** + * h2 + */ + H2, + /** + * mysql + */ + MYSQL, + /** + * postgresql + */ + POSTGRESQL, + /** + * Mariadb + */ + MARIADB + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IMode.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IMode.java new file mode 100644 index 0000000000..24f47530c6 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IMode.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +public interface IMode { + + /** + * 当前模式 + * + * @return 当前运行模式 + */ + DbExtConfig.Mode mode(); +} 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 new file mode 100644 index 0000000000..a94917d264 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageService.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import cn.hutool.db.ds.DSFactory; +import cn.hutool.setting.Setting; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.JpomRuntimeException; + +import java.io.File; +import java.sql.SQLException; +import java.util.List; + +/** + * 数据库实现 + * + * @author bwcx_jzy + * @since 2023/1/5 + */ +public interface IStorageService extends AutoCloseable, IMode { + + /** + * 初始化数据库 + * + * @param dbExtConfig 配置参数信息 + * @return 数据库连接工厂 + */ + DSFactory init(DbExtConfig dbExtConfig); + + /** + * 创建数据库连接工厂 + * + * @param dbExtConfig 数据库配置 + * @param url url + * @param user 用户名 + * @param pass 密码 + * @return 数据库连接工厂 + */ + DSFactory create(DbExtConfig dbExtConfig, String url, String user, String pass); + + /** + * 创建数据库配置参数 + * + * @param dbExtConfig 数据库配置 + * @param url url + * @param user 用户名 + * @param pass 密码 + * @return 配置 + */ + Setting createSetting(DbExtConfig dbExtConfig, String url, String user, String pass); + + /** + * 获取数据库连接工厂 + * + * @return DSFactory + */ + DSFactory getDsFactory(); + + /** + * 是否存在数据库文件 + * + * @return true 存在 + * @throws Exception 异常 + */ + default boolean hasDbData() throws Exception { + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 恢复数据库 + * + * @return 恢复后的 sql 路径 + * @throws Exception 异常 + */ + default File recoverDb() throws Exception { + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 删除数据库 + * + * @return 删除后的 sql 路径 + * @throws Exception 异常 + */ + default String deleteDbFiles() throws Exception { + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 转换 sql 文件内容,低版本兼容高版本 + * + * @param sqlFile sql 文件 + */ + default void transformSql(File sqlFile) { + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 恢复数据库 + * + * @param dsFactory 数据库连接 + * @param recoverSqlFile 要恢复的数据库文件 + * @throws Exception 异常 + */ + default void executeRecoverDbSql(DSFactory dsFactory, File recoverSqlFile) throws Exception { + if (recoverSqlFile == null) { + return; + } + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 修改账号 密码 + * + * @param oldUes 旧的账号 + * @param newUse 新的账号 + * @param newPwd 新密码 + * @throws SQLException sql 异常 + */ + default void alterUser(String oldUes, String newUse, String newPwd) throws SQLException { + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 备份数据库 + * + * @param url url + * @param user 账号 + * @param pass 密码 + * @param backupSqlPath sql 存放路径 + * @param tableName 备份的表名 + * @throws Exception 异常 + */ + default void backupSql(String url, String user, String pass, String backupSqlPath, List tableName) throws Exception { + throw new UnsupportedOperationException(I18nMessageUtil.get("i18n.no_implemented_feature.af80")); + } + + /** + * 数据库地址 + * + * @return url + */ + String dbUrl(); + + /** + * 游标查询批处理数量 + * + * @return mysql 和 h2 不一致 + */ + int getFetchSize(); + + /** + * 异常转换 + * + * @param e 异常 + * @return 转换后 + */ + JpomRuntimeException warpException(Exception e); +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageSqlBuilderService.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageSqlBuilderService.java new file mode 100644 index 0000000000..97840a2408 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageSqlBuilderService.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import cn.hutool.core.util.StrUtil; + +import java.util.List; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +public interface IStorageSqlBuilderService extends IMode { + + /** + * 生成表 sql + * + * @param name 表名称 + * @param desc 表描述 + * @param row 字段 + * @return sql + */ + String generateTableSql(String name, String desc, List row); + + /** + * 生成 修改表 sql + * + * @param row 需要修改的字段 + * @return sql + */ + String generateAlterTableSql(List row); + + /** + * 生成 修改表 sql + * + * @param row 需要修改的字段 + * @return sql + */ + String generateIndexSql(List row); + + /** + * 根据字段信息生成 sql + * + * @param tableViewRowData 字段信息 + * @return sql + */ + String generateColumnSql(TableViewRowData tableViewRowData); + + /** + * 根据表名和字段信息生成 sql + * + * @param tableName 表名 + * @param tableViewRowData 字段信息 + * @return sql + */ + default String generateColumnSql(String tableName,TableViewRowData tableViewRowData) { + return generateColumnSql(tableViewRowData); + } + + /** + * sql 分隔执行标记 + * + * @return 分隔标记 + */ + default String delimiter() { + return StrUtil.EMPTY; + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java new file mode 100644 index 0000000000..a2411331ea --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.date.SystemClock; +import cn.hutool.core.exceptions.CheckedUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.*; +import cn.hutool.db.*; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.sql.Wrapper; +import cn.hutool.setting.Setting; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.dialect.DialectUtil; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.util.Assert; + +import java.io.File; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 数据存储服务 + * + * @author bwcx_jzy + * @since 2023/1/5 + */ +@Slf4j +public class StorageServiceFactory { + + private static final String DB = "db"; + /** + * 当前运行模式 + */ + private static DbExtConfig.Mode mode; + + /** + * 配置当前数据库 模式 + * + * @param mode mode + */ + public static void setMode(DbExtConfig.Mode mode) { + StorageServiceFactory.mode = mode; + } + + public static DbExtConfig.Mode getMode() { + return mode; + } + + /** + * 将数据迁移到当前环境 + */ + public static void migrateH2ToNow(DbExtConfig dbExtConfig, String h2Url, String h2User, String h2Pass, DbExtConfig.Mode targetNode) { + log.info(I18nMessageUtil.get("i18n.start_migrating_h2_data_to.f478"), dbExtConfig.getMode()); + Assert.notNull(mode, I18nMessageUtil.get("i18n.target_database_info_not_specified.2ff6")); + try { + IStorageService h2StorageService = doCreateStorageService(DbExtConfig.Mode.H2); + boolean hasDbData = h2StorageService.hasDbData(); + if (!hasDbData) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.no_h2_data_info_for_migration.5799")); + } + long time = SystemClock.now(); + DSFactory h2DsFactory = h2StorageService.create(dbExtConfig, h2Url, h2User, h2Pass); + h2DsFactory.getDataSource(); + log.info(I18nMessageUtil.get("i18n.h2_connection_successful.11f3")); + // 设置默认备份 SQL 的文件地址 + String fileName = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), DatePattern.PURE_DATETIME_PATTERN); + File file = FileUtil.file(StorageServiceFactory.dbLocalPath(), DbExtConfig.BACKUP_DIRECTORY_NAME, fileName + DbExtConfig.SQL_FILE_SUFFIX); + String backupSqlPath = FileUtil.getAbsolutePath(file); + Setting setting = h2StorageService.createSetting(dbExtConfig, h2Url, h2User, h2Pass); + // 数据源参数 + String url = setting.get("url"); + String user = setting.get("user"); + String pass = setting.get("pass"); + h2StorageService.backupSql(url, user, pass, backupSqlPath, null); + log.info(I18nMessageUtil.get("i18n.h2_database_backup_success.a099"), backupSqlPath); + // + IStorageService nowStorageService = doCreateStorageService(dbExtConfig.getMode()); + DSFactory nowDsFactory = nowStorageService.create(dbExtConfig, null, null, null); + nowDsFactory.getDataSource(); + log.info(I18nMessageUtil.get("i18n.connection_successful.0515"), dbExtConfig.getMode(), dbExtConfig.getUrl()); + Set> classes = ClassUtil.scanPackageByAnnotation("org.dromara.jpom", TableName.class); + classes = classes.stream() + .filter(aClass -> { + TableName tableName = aClass.getAnnotation(TableName.class); + DbExtConfig.Mode[] modes = tableName.modes(); + if (ArrayUtil.isEmpty(modes)) { + return true; + } + return ArrayUtil.contains(modes, dbExtConfig.getMode()); + }) + .sorted((o1, o2) -> StrUtil.compare(o1.getSimpleName(), o2.getSimpleName(), false)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + log.info(I18nMessageUtil.get("i18n.prepare_to_migrate_data.f251")); + int total = 0; + for (Class aClass : classes) { + total += migrateH2ToNowItem(aClass, h2DsFactory, nowDsFactory, targetNode); + } + long endTime = SystemClock.now(); + log.info(I18nMessageUtil.get("i18n.migration_completed.7a30"), total, DateUtil.formatBetween(endTime - time)); + h2DsFactory.destroy(); + nowDsFactory.destroy(); + log.info(I18nMessageUtil.get("i18n.prepare_to_delete_current_database_file.1e6a")); + String dbFiles = h2StorageService.deleteDbFiles(); + log.info(I18nMessageUtil.get("i18n.auto_backup_h2_database.2ed0"), dbFiles); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + private static int migrateH2ToNowItem(Class aClass, DSFactory h2DsFactory, DSFactory targetDsFactory, DbExtConfig.Mode targetNode) throws SQLException { + TableName tableName = aClass.getAnnotation(TableName.class); + Wrapper targetModeWrapper = DialectUtil.getDialectByMode(targetNode).getWrapper(); + Set boolFieldSet = Arrays.stream(ReflectUtil.getFields(aClass, field -> Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType()))) + .map(Field::getName) + .collect(Collectors.toSet()); + + String tableDesc = I18nMessageUtil.get(tableName.nameKey()); + log.info(I18nMessageUtil.get("i18n.start_migrating.20d6"), tableDesc, tableName.value()); + int total = 0; + while (true) { + Entity where = Entity.create(tableName.value()); + PageResult pageResult; + Db db = Db.use(h2DsFactory.getDataSource(), DialectUtil.getH2Dialect()); + Page page = new Page(1, 200); + pageResult = db.page(where, page); + if (pageResult.isEmpty()) { + break; + } + // 过滤需要忽略迁移的数据 + List newResult = pageResult.stream() + .map(entity -> entity.toBeanIgnoreCase(aClass)) + .map(o -> { + // 兼容大小写 + Entity entity = Entity.create(targetModeWrapper.wrap(tableName.value())); + return entity.parseBean(o, false, true); + }).peek(entity -> { + if (DbExtConfig.Mode.POSTGRESQL.equals(targetNode)) { + // tinyint类型查出来是数字,需转为bool + boolFieldSet.forEach(fieldName -> { + Object field = entity.get(fieldName); + if (field instanceof Number) { + entity.set(fieldName, BooleanUtil.toBoolean(field.toString())); + } + }); + } + }).collect(Collectors.toList()); + if (newResult.isEmpty()) { + if (pageResult.isLast()) { + // 最后一页 + break; + } + // 继续 + continue; + } + total += newResult.size(); + // 插入信息数据 + Db db2 = Db.use(targetDsFactory.getDataSource(), DialectUtil.getDialectByMode(targetNode)); + + Connection connection = db2.getConnection(); + try { + SqlConnRunner runner = db2.getRunner(); + for (Entity entity : newResult) { + // hutool的批量insert方法有坑,可能导致部分参数被丢弃 + runner.insert(connection, entity); + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + db2.closeConnection(connection); + } + + // 删除数据 + Entity deleteWhere = Entity.create(tableName.value()); + deleteWhere.set("id", newResult.stream().map(entity -> entity.getStr("id")).collect(Collectors.toList())); + db.del(deleteWhere); + } + log.info(I18nMessageUtil.get("i18n.migration_success.b20d"), tableDesc, total); + return total; + } + + /** + * 加载 本地已经执行的记录 + * + * @return sha1 log + * @author bwcx_jzy + */ + public static Set loadExecuteSqlLog() { + File localPath = dbLocalPath(); + File file = FileUtil.file(localPath, "execute.init.sql.log"); + if (!FileUtil.isFile(file)) { + // 不存在或者是文件夹 + FileUtil.del(file); + return new LinkedHashSet<>(); + } + List strings = FileUtil.readLines(file, CharsetUtil.CHARSET_UTF_8); + return new LinkedHashSet<>(strings); + } + + /** + * 获取数据库保存路径 + * + * @return 默认本地数据目录下面的 db 目录 + * @author bwcx_jzy + */ + public static File dbLocalPath() { + return FileUtil.file(ExtConfigBean.getPath(), DB); + } + + /** + * 清除执行记录 + */ + public static void clearExecuteSqlLog() { + File localPath = dbLocalPath(); + File file = FileUtil.file(localPath, "execute.init.sql.log"); + FileUtil.del(file); + } + + /** + * 保存本地已经执行的记录 + * + * @author bwcx_jzy + */ + public static void saveExecuteSqlLog(Set logs) { + File localPath = dbLocalPath(); + File file = FileUtil.file(localPath, "execute.init.sql.log"); + FileUtil.writeUtf8Lines(logs, file); + } + + /** + * 获得单例的 IStorageService + * + * @return 单例的 IStorageService + */ + public static IStorageService get() { + Assert.notNull(mode, I18nMessageUtil.get("i18n.unknown_database_mode.f9e5")); + return Singleton.get(IStorageService.class.getName(), (CheckedUtil.Func0Rt) () -> doCreateStorageService(mode)); + } + + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code EngineFactory} + */ + private static IStorageService doCreateStorageService(DbExtConfig.Mode mode) { + final List storageServiceList = ServiceLoaderUtil.loadList(IStorageService.class); + if (storageServiceList != null) { + for (IStorageService storageService : storageServiceList) { + if (storageService.mode() == mode) { + return storageService; + } + } + } + throw new RuntimeException("No Jpom Storage " + mode + " jar found ! Please add one of it to your project !"); + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageTableFactory.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageTableFactory.java new file mode 100644 index 0000000000..c435d3a333 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageTableFactory.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.exceptions.CheckedUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.text.csv.CsvReadConfig; +import cn.hutool.core.text.csv.CsvReader; +import cn.hutool.core.text.csv.CsvUtil; +import cn.hutool.core.util.ServiceLoaderUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +@Slf4j +public class StorageTableFactory { + + /** + * 初始化表 sql + * + * @param resource 资源 + * @return sql + */ + public static String initTable(Resource resource) { + try (InputStream inputStream = resource.getInputStream()) { + CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.setHeaderLineNo(0); + CsvReader csvReader = CsvUtil.getReader(csvReadConfig); + BufferedReader bufferedReader = IoUtil.getUtf8Reader(inputStream); + List tableViewData = csvReader.read(bufferedReader, TableViewData.class); + + Map> map = CollStreamUtil.groupByKey(tableViewData, TableViewData::getTableName); + StringBuilder stringBuffer = new StringBuilder(); + + IStorageSqlBuilderService sqlBuilderService = StorageTableFactory.get(); + for (Map.Entry> entry : map.entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + String tableComment = value.stream() + .map(TableViewData::getTableComment) + .filter(StrUtil::isNotEmpty) + .findAny() + .orElse(null); + Assert.hasText(tableComment, key + I18nMessageUtil.get("i18n.no_description.c231")); + stringBuffer.append(sqlBuilderService.generateTableSql(key, tableComment, value)).append(StrUtil.LF); + stringBuffer.append(sqlBuilderService.delimiter()).append(StrUtil.LF); + } + return stringBuffer.toString(); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + public static String initAlter(Resource resource) { + try (InputStream inputStream = resource.getInputStream()) { + CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.setHeaderLineNo(0); + CsvReader csvReader = CsvUtil.getReader(csvReadConfig); + BufferedReader bufferedReader = IoUtil.getUtf8Reader(inputStream); + List tableViewData = csvReader.read(bufferedReader, TableViewAlterData.class); + IStorageSqlBuilderService sqlBuilderService = StorageTableFactory.get(); + return sqlBuilderService.generateAlterTableSql(tableViewData); + + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + public static String initIndex(Resource resource) { + try (InputStream inputStream = resource.getInputStream()) { + CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.setHeaderLineNo(0); + CsvReader csvReader = CsvUtil.getReader(csvReadConfig); + BufferedReader bufferedReader = IoUtil.getUtf8Reader(inputStream); + List tableViewData = csvReader.read(bufferedReader, TableViewIndexData.class); + IStorageSqlBuilderService sqlBuilderService = StorageTableFactory.get(); + return sqlBuilderService.generateIndexSql(tableViewData); + + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + /** + * 获得单例的 IStorageSqlBuilderService + * + * @return 单例的 IStorageSqlBuilderService + */ + public static IStorageSqlBuilderService get() { + Assert.notNull(StorageServiceFactory.getMode(), I18nMessageUtil.get("i18n.unknown_database_mode.f9e5")); + return Singleton.get(IStorageSqlBuilderService.class.getName(), (CheckedUtil.Func0Rt) () -> doCreateStorageService(StorageServiceFactory.getMode())); + } + + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
+ * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@code EngineFactory} + */ + private static IStorageSqlBuilderService doCreateStorageService(DbExtConfig.Mode mode) { + final List storageServiceList = ServiceLoaderUtil.loadList(IStorageSqlBuilderService.class); + if (storageServiceList != null) { + for (IStorageSqlBuilderService storageService : storageServiceList) { + if (storageService.mode() == mode) { + return storageService; + } + } + } + throw new RuntimeException("No Jpom Storage " + mode + " jar found ! Please add one of it to your project !"); + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableName.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableName.java new file mode 100644 index 0000000000..b1d9c0affe --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableName.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import java.lang.annotation.*; + +/** + * 数据库表名 + * + * @author bwcx_jzy + * @since 2021/8/13 + */ +@Documented +@Target({ElementType.TYPE}) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface TableName { + + /** + * 表名 + * + * @return tableName + */ + String value(); + + /** + * 表描述 + * + * @return 描述 + */ + String nameKey(); + + /** + * 数据库默认 + * + * @return 默认所有模式 + */ + DbExtConfig.Mode[] modes() default {}; + + /** + * 父级 + * + * @return class + */ + Class parents() default Void.class; + + /** + * 绑定关系 + *

+ * 1 严格模式,需要手动删除 + * 2 删除工作空间时自动删除 + * 3 父级数据为空时可以自动删除 + * + * @return 数据绑定关系 + */ + int workspaceBind() default 1; +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewAlterData.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewAlterData.java new file mode 100644 index 0000000000..5571a8ce07 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewAlterData.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2023/1/6 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class TableViewAlterData extends TableViewRowData { + + private String alterType; + + private String tableName; +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewData.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewData.java new file mode 100644 index 0000000000..a6a4890869 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewData.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author bwcx_jzy + * @since 2023/1/6 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class TableViewData extends TableViewRowData { + + private String tableName; + + private Boolean primaryKey; + + private String tableComment; +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewIndexData.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewIndexData.java new file mode 100644 index 0000000000..b5bc4f2c0d --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewIndexData.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import lombok.Data; + +/** + * @author bwcx_jzy + * @since 2023/1/6 + */ +@Data +public class TableViewIndexData { + + private String indexType; + + private String tableName; + + private String name; + + /** + * 多个,使用 + 分割 + */ + private String field; +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewRowData.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewRowData.java new file mode 100644 index 0000000000..453f98e999 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/TableViewRowData.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.db; + +import lombok.Data; + +/** + * @author bwcx_jzy + * @since 2023/1/6 + */ +@Data +public class TableViewRowData { + + private String name; + private String type; + private Integer len; + private String defaultValue; + private Boolean notNull; + private String comment; +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java new file mode 100644 index 0000000000..fa7336411e --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.dialect; + +import cn.hutool.db.dialect.Dialect; +import cn.hutool.db.dialect.impl.H2Dialect; +import cn.hutool.db.dialect.impl.MysqlDialect; +import cn.hutool.db.dialect.impl.PostgresqlDialect; +import cn.hutool.db.sql.Wrapper; +import cn.hutool.extra.spring.SpringUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.DbExtConfig; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 数据库方言工具类 + * + * @author whz + * @since 2024/3/10 + */ +public class DialectUtil { + + private final static ConcurrentHashMap DIALECT_CACHE = new ConcurrentHashMap<>(); + private static volatile Wrapper currentDbFieldWrapper; + /** + * 通用的包裹器 + */ + private static final Wrapper COMMON_WRAPPER = new Wrapper('`'); + + public static Dialect getH2Dialect() { + return DIALECT_CACHE.computeIfAbsent(DbExtConfig.Mode.H2, key -> { + H2Dialect h2Dialect = new H2Dialect(); + h2Dialect.setWrapper(COMMON_WRAPPER); + return h2Dialect; + }); + } + + public static Dialect getMySqlDialect() { + return DIALECT_CACHE.computeIfAbsent(DbExtConfig.Mode.MYSQL, key -> new MysqlDialect()); + } + + /** + * 获取自定义postgresql数据库的方言 + * + * @return 自定义postgresql数据库的方言 + */ + public static Dialect getPostgresqlDialect() { + return DIALECT_CACHE.computeIfAbsent(DbExtConfig.Mode.POSTGRESQL, key -> { + Set names = Stream.of("group", "desc", "user", "content") + .map(String::toLowerCase) + .collect(Collectors.toSet()); + Wrapper wrapper = new Wrapper('"') { + @Override + public String wrap(String field) { + String unWrap = COMMON_WRAPPER.unWrap(field); + // 代码中存在固定编码 warp + if (names.contains(unWrap)) { + return super.wrap(unWrap); + } + // 不属于names的直接返回 并且转小写 + return unWrap.toLowerCase(); + } + + @Override + public String unWrap(String field) { + String unWrap = COMMON_WRAPPER.unWrap(field); + return super.unWrap(unWrap); + } + }; + PostgresqlDialect dialect = new PostgresqlDialect(); + dialect.setWrapper(wrapper); + return dialect; + }); + } + + public static Dialect getDialectByMode(DbExtConfig.Mode mode) { + switch (mode) { + case H2: + return getH2Dialect(); + case MYSQL: + case MARIADB: + return getMySqlDialect(); + case POSTGRESQL: + return getPostgresqlDialect(); + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unknown_database_dialect_type.951b") + mode); + } + } + + /** + * 获取表名或列名在当前配置的数据库上的方言,例如 postgresql会使用 " ,mysql使用 ` + * + * @param field 表名或者列名 + * @return 处理后的表名或者列名 + */ + public static String wrapField(String field) { + initWrapFieldMap(); + return currentDbFieldWrapper.wrap(field); + } + + /** + * 初始化当前数据库 wrapper + */ + private static void initWrapFieldMap() { + if (currentDbFieldWrapper == null) { + synchronized (DialectUtil.class) { + if (currentDbFieldWrapper == null) { + DbExtConfig dbExtConfig = SpringUtil.getBean(DbExtConfig.class); + if (dbExtConfig == null || dbExtConfig.getMode() == null) { + throw new IllegalStateException(I18nMessageUtil.get("i18n.database_mode_config_missing.ae5d")); + } + Dialect dialectByMode = getDialectByMode(dbExtConfig.getMode()); + currentDbFieldWrapper = dialectByMode.getWrapper(); + } + } + } + } + + public static String unWrapField(String field) { + initWrapFieldMap(); + return currentDbFieldWrapper.unWrap(field); + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/model/BaseDbModel.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/model/BaseDbModel.java new file mode 100644 index 0000000000..448e1ff905 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/model/BaseDbModel.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.core.date.SystemClock; +import cn.keepbx.jpom.model.BaseIdModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据基础实体 + * + * @author jzy + * @since 2021-08-13 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class BaseDbModel extends BaseIdModel { + + /** + * 数据创建时间 + * + * @see SystemClock#now() + */ + private Long createTimeMillis; + + /** + * 数据修改时间 + */ + private Long modifyTimeMillis; + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/model/PageResultDto.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/model/PageResultDto.java new file mode 100644 index 0000000000..e4c0e34121 --- /dev/null +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/model/PageResultDto.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.model; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.PageUtil; +import cn.hutool.db.PageResult; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; +import java.util.function.Consumer; + +/** + * 分页查询结果对象 + * + * @author bwcx_jzy + * @since 2021/12/3 + */ +@Data +public class PageResultDto implements Serializable { + + /** + * 结果 + */ + private List result; + /** + * 页码 + */ + private Integer page; + /** + * 每页结果数 + */ + private Integer pageSize; + /** + * 总页数 + */ + private Integer totalPage; + /** + * 总数 + */ + private Integer total; + + public PageResultDto(PageResult pageResult) { + this.setPage(pageResult.getPage()); + this.setPageSize(pageResult.getPageSize()); + this.setTotalPage(pageResult.getTotalPage()); + this.setTotal(pageResult.getTotal()); + } + + public PageResultDto(int page, int pageSize, int total) { + this.setPage(page); + this.setPageSize(pageSize); + this.setTotalPage(PageUtil.totalPage(total, pageSize)); + this.setTotal(total); + } + + public void each(Consumer consumer) { + if (result == null) { + return; + } + result.forEach(consumer); + } + + public boolean isEmpty() { + return CollUtil.isEmpty(getResult()); + } + + public T get(int index) { + return CollUtil.get(getResult(), index); + } +} diff --git a/modules/storage-module/storage-module-h2/README.md b/modules/storage-module/storage-module-h2/README.md new file mode 100644 index 0000000000..520f318e13 --- /dev/null +++ b/modules/storage-module/storage-module-h2/README.md @@ -0,0 +1,8 @@ +# h2database + +### 用到 h2 特性函数 + +1. instr + 1. 容器标签 +2. ~~HASH~~ + 1. 构建触发器 \ No newline at end of file diff --git a/modules/storage-module/storage-module-h2/pom.xml b/modules/storage-module/storage-module-h2/pom.xml new file mode 100644 index 0000000000..ef0f74c3ba --- /dev/null +++ b/modules/storage-module/storage-module-h2/pom.xml @@ -0,0 +1,52 @@ + + + + 4.0.0 + + org.dromara.jpom.storage-module + jpom-storage-module-parent + 2.11.6.6 + ../pom.xml + + + storage-module-h2 + + + 8 + 8 + UTF-8 + + + + + org.dromara.jpom.storage-module + storage-module-common + ${project.version} + + + + com.h2database + h2 + + + + org.dromara.jpom + common + provided + ${project.version} + + + + diff --git a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/plugin/DefaultDbH2PluginImpl.java b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/plugin/DefaultDbH2PluginImpl.java new file mode 100644 index 0000000000..bd08c0103f --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/plugin/DefaultDbH2PluginImpl.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.plugins.PluginConfig; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.StorageServiceFactory; +import org.h2.store.FileLister; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Recover; +import org.h2.tools.RunScript; +import org.h2.tools.Shell; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import javax.sql.DataSource; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/1/18 + */ +@PluginConfig(name = "db-h2") +@Slf4j +public class DefaultDbH2PluginImpl implements IDefaultPlugin { + + @Override + public Object execute(Object main, Map parameter) throws Exception { + String method = StrUtil.toString(main); + if (StrUtil.equals("backupSql", method)) { + String url = (String) parameter.get("url"); + String user = (String) parameter.get("user"); + String password = (String) parameter.get("pass"); + String backupSqlPath = (String) parameter.get("backupSqlPath"); + List tableNameList = (List) parameter.get("tableNameList"); + this.backupSql(url, user, password, backupSqlPath, tableNameList); + } else if (StrUtil.equals("restoreBackupSql", method)) { + String backupSqlPath = (String) parameter.get("backupSqlPath"); + DataSource dataSource = (DataSource) parameter.get("dataSource"); + if (dataSource == null) { + // 加载数据源 + dataSource = StorageServiceFactory.get().getDsFactory().getDataSource(); + } + this.restoreBackupSql(backupSqlPath, dataSource); + } else if (StrUtil.equals("recoverToSql", method)) { + File dbPath = (File) parameter.get("dbPath"); + String dbName = (String) parameter.get("dbName"); + File recoverBackup = (File) parameter.get("recoverBackup"); + return this.recover(dbPath, dbName, recoverBackup); + } else if (StrUtil.equals("deleteDbFiles", method)) { + File dbPath = (File) parameter.get("dbPath"); + String dbName = (String) parameter.get("dbName"); + File backupPath = (File) parameter.get("backupPath"); + this.deleteDbFiles(dbPath, dbName, backupPath); + } else if (StrUtil.equals("hasDbFiles", method)) { + File dbPath = (File) parameter.get("dbPath"); + String dbName = (String) parameter.get("dbName"); + return this.hasDbFiles(dbPath, dbName); + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type.7495")); + } + return "done"; + } + + /** + * 恢复 + * + * @param dbPath 数据库路径 + * @param dbName 数据库名 + * @param recoverBackup 恢复到哪个路径 + * @return 返回恢复到 sql 文件 + * @throws SQLException sql + */ + private File recover(File dbPath, String dbName, File recoverBackup) throws SQLException { + String dbLocalPath = FileUtil.getAbsolutePath(dbPath); + ArrayList list = FileLister.getDatabaseFiles(dbLocalPath, dbName, true); + if (CollUtil.isEmpty(list)) { + return null; + } + FileUtil.mkdir(recoverBackup); + // 备份数据 + for (String s : list) { + FileUtil.move(FileUtil.file(s), recoverBackup, true); + } + String absolutePath = FileUtil.getAbsolutePath(recoverBackup); + log.info("h2 db recover backup path,{}", absolutePath); + // 恢复数据 + Recover recover = new Recover(); + recover.runTool("-dir", absolutePath, "-db", dbName); + return FileUtil.file(recoverBackup, dbName + ".h2.sql"); + } + + /** + * 是否存在数据库文件 + * + * @param dbPath 数据库路径 + * @param dbName 数据库名 + */ + private boolean hasDbFiles(File dbPath, String dbName) { + String dbLocalPath = FileUtil.getAbsolutePath(dbPath); + ArrayList list = FileLister.getDatabaseFiles(dbLocalPath, dbName, true); + return CollUtil.isNotEmpty(list); + } + + /** + * 删除数据 + * + * @param dbPath 数据库路径 + * @param dbName 数据库名 + * @throws SQLException sql + */ + private void deleteDbFiles(File dbPath, String dbName, File backupPath) throws SQLException { + String dbLocalPath = FileUtil.getAbsolutePath(dbPath); + ArrayList list = FileLister.getDatabaseFiles(dbLocalPath, dbName, true); + if (CollUtil.isEmpty(list)) { + return; + } + if (backupPath != null) { + FileUtil.mkdir(backupPath); + // 备份数据 + for (String s : list) { + FileUtil.move(FileUtil.file(s), backupPath, true); + } + } + // 删除数据 + DeleteDbFiles deleteDbFiles = new DeleteDbFiles(); + deleteDbFiles.runTool("-dir", dbLocalPath, "-db", dbName); + } + + /** + * 备份 SQL + * + * @param url jdbc url + * @param user user + * @param password password + * @param backupSqlPath backup SQL file path, absolute path + * @param tableNameList backup table name list, if need backup all table, use null + */ + private void backupSql(String url, String user, String password, + String backupSqlPath, List tableNameList) throws SQLException { + // 备份 SQL + String sql = StrUtil.format("SCRIPT DROP to '{}'", backupSqlPath); + // 判断是否部分部分表 + if (!CollectionUtils.isEmpty(tableNameList)) { + String tableNames = StrUtil.join(StrUtil.COMMA, tableNameList.toArray()); + sql = StrUtil.format("{} TABLE {}", sql, tableNames); + } + log.debug("backup SQL is: {}", sql); + // 执行 SQL 备份脚本 + Shell shell = new Shell(); + + /* + * url 表示 h2 数据库的 jdbc url + * user 表示登录的用户名 + * password 表示登录密码 + * driver 是 jdbc 驱动 + * sql 是备份的 sql 语句 + * - 案例:script drop to ${fileName1} table ${tableName1},${tableName2}... + * - script drop to 表示备份数据库,drop 表示建表之前会先删除表 + * - ${fileName1} 表示备份之后的文件名 + * - table 表示需要备份的表名称,后面跟多个表名,用英文逗号分割 + */ + String[] params = new String[]{ + "-url", url, + "-user", user, + "-password", password, + "-driver", "org.h2.Driver", + "-sql", sql + }; + try (FastByteArrayOutputStream arrayOutputStream = new FastByteArrayOutputStream()) { + try (PrintStream printStream = new PrintStream(arrayOutputStream)) { + shell.setOut(printStream); + shell.runTool(params); + } + } + } + + /** + * 还原备份 SQL + * + * @param backupSqlPath backup SQL file path, absolute path + * @throws SQLException SQLException + * @throws IOException FileNotFoundException + */ + private void restoreBackupSql(String backupSqlPath, DataSource dataSource) throws SQLException, IOException { + Assert.notNull(dataSource, "Restore Backup sql error...H2 DataSource not null"); + try (Connection connection = dataSource.getConnection()) { + // 读取数据库备份文件,执行还原 + File backupSqlFile = FileUtil.file(backupSqlPath); + try (FileReader fileReader = new FileReader(backupSqlFile)) { + RunScript.execute(connection, fileReader); + } + } + } +} 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 new file mode 100644 index 0000000000..704c7cc924 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2StorageServiceImpl.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Db; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.setting.Setting; +import cn.keepbx.jpom.plugins.IPlugin; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.common.JpomManifest; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.IStorageService; +import org.dromara.jpom.db.StorageServiceFactory; +import org.dromara.jpom.plugin.PluginFactory; +import org.dromara.jpom.system.JpomRuntimeException; +import org.h2.jdbc.JdbcSQLNonTransientConnectionException; +import org.h2.jdbc.JdbcSQLNonTransientException; +import org.h2.mvstore.MVStoreException; +import org.springframework.boot.autoconfigure.h2.H2ConsoleProperties; +import org.springframework.util.Assert; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +@Slf4j +public class H2StorageServiceImpl implements IStorageService { + + private String dbUrl; + private DSFactory dsFactory; + + @Override + public String dbUrl() { + Assert.hasText(this.dbUrl, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dbUrl; + } + + @Override + public int getFetchSize() { + return 100; + } + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.H2; + } + + @Override + public DSFactory create(DbExtConfig dbExtConfig, String url, String user, String pass) { + Setting setting = this.createSetting(dbExtConfig, url, user, pass); + return DSFactory.create(setting); + } + + @Override + public Setting createSetting(DbExtConfig dbExtConfig, String url, String user, String pass) { + String url2 = Opt.ofBlankAble(url).orElseGet(() -> this.getDefaultDbUrl(dbExtConfig)); + String user2 = Opt.ofBlankAble(user).orElse(DbExtConfig.DEFAULT_USER_OR_AUTHORIZATION); + String pass2 = Opt.ofBlankAble(pass).orElse(DbExtConfig.DEFAULT_USER_OR_AUTHORIZATION); + Setting setting = dbExtConfig.toSetting(); + setting.set("user", user2); + setting.set("pass", pass2); + setting.set("url", url2); + return setting; + } + + @Override + public DSFactory init(DbExtConfig dbExtConfig) { + Assert.isNull(this.dsFactory, I18nMessageUtil.get("i18n.do_not_reinitialize_database.9bb5")); + Setting setting = dbExtConfig.toSetting(); + // + String dbUrl = this.getDbUrl(dbExtConfig); + setting.set("url", dbUrl); + // 安全检查 + dbSecurityCheck(dbExtConfig); + this.dsFactory = DSFactory.create(setting); + this.dbUrl = dbUrl; + return this.dsFactory; + } + + public DSFactory getDsFactory() { + Assert.notNull(this.dsFactory, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dsFactory; + } + + /** + * 数据库是否开启 web 配置检查 + *

+ * http://${ip}:${port}/h2-console + * + * @param dbExtConfig 外部配置 + * @see H2ConsoleProperties#getEnabled() + */ + private void dbSecurityCheck(DbExtConfig dbExtConfig) { + + + String property = SpringUtil.getApplicationContext().getEnvironment().getProperty("spring.h2.console.enabled"); + boolean h2ConsoleEnabled = BooleanUtil.toBoolean(property); + if (!JpomManifest.getInstance().isDebug() && h2ConsoleEnabled + && StrUtil.equals(dbExtConfig.userName(), DbExtConfig.DEFAULT_USER_OR_AUTHORIZATION) + && StrUtil.equals(dbExtConfig.userPwd(), DbExtConfig.DEFAULT_USER_OR_AUTHORIZATION)) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.security_warning_h2_console.4669")); + } + } + + /** + * 获取数据库的jdbc 连接 + * + * @return jdbc + */ + public String getDbUrl(DbExtConfig dbExtConfig) { + String dbUrl = dbExtConfig.getUrl(); + if (StrUtil.isNotEmpty(dbUrl)) { + return dbUrl; + } + return this.getDefaultDbUrl(dbExtConfig); + } + + /** + * 获取数据库的jdbc 连接 + * + * @return jdbc + */ + public String getDefaultDbUrl(DbExtConfig dbExtConfig) { + File file = FileUtil.file(StorageServiceFactory.dbLocalPath(), this.getDbName()); + String path = FileUtil.getAbsolutePath(file); + return StrUtil.format("jdbc:h2:{};CACHE_SIZE={};MODE=MYSQL;LOCK_TIMEOUT=10000", path, dbExtConfig.getCacheSize().toKilobytes()); + } + + private String getDbName() { + return JpomApplication.getAppType().name(); + } + + @Override + public JpomRuntimeException warpException(Exception e) { + String message = e.getMessage(); + if (e instanceof MVStoreException || ExceptionUtil.isCausedBy(e, MVStoreException.class)) { + if (StrUtil.containsIgnoreCase(message, "The write format 1 is smaller than the supported format 2")) { + log.warn(message); + String tip = I18nMessageUtil.get("i18n.upgrade_database_process.e604") + StrUtil.LF; + tip += StrUtil.TAB + I18nMessageUtil.get("i18n.export_low_version_data.f1aa") + StrUtil.LF; + tip += StrUtil.TAB + I18nMessageUtil.get("i18n.import_low_version_data_to_new_version.247b"); + return new JpomRuntimeException(I18nMessageUtil.get("i18n.incompatible_database_version.8f7b") + StrUtil.LF + tip + StrUtil.LF, -1); + } + } + if (e instanceof JdbcSQLNonTransientException || ExceptionUtil.isCausedBy(e, JdbcSQLNonTransientException.class)) { + return new JpomRuntimeException(I18nMessageUtil.get("i18n.database_corrupted.944e") + message, e); + } + if (e instanceof JdbcSQLNonTransientConnectionException) { + return new JpomRuntimeException(I18nMessageUtil.get("i18n.database_exception_due_to_resources.dbf1") + message, e); + } + return new JpomRuntimeException(I18nMessageUtil.get("i18n.database_exception.4894"), e); + } + + + /** + * 恢复数据库 + */ + public File recoverDb() throws Exception { + File dbLocalPathFile = StorageServiceFactory.dbLocalPath(); + if (!FileUtil.exist(dbLocalPathFile)) { + return null; + } + String dbName = this.getDbName(); + File recoverBackup = FileUtil.file(dbLocalPathFile, "recover_backup", DateTime.now().toString(DatePattern.PURE_DATETIME_FORMAT)); + // + IPlugin plugin = PluginFactory.getPlugin("db-h2"); + Map map = new HashMap<>(10); + map.put("dbName", dbName); + map.put("dbPath", dbLocalPathFile); + map.put("recoverBackup", recoverBackup); + File backupSql = (File) plugin.execute("recoverToSql", map); + // 清空记录 + StorageServiceFactory.clearExecuteSqlLog(); + // 记录恢复的 sql + return backupSql; + } + + @Override + public boolean hasDbData() throws Exception { + File dbLocalPathFile = StorageServiceFactory.dbLocalPath(); + if (!FileUtil.exist(dbLocalPathFile)) { + return false; + } + IPlugin plugin = PluginFactory.getPlugin("db-h2"); + Map map = new HashMap<>(10); + map.put("dbName", this.getDbName()); + map.put("dbPath", dbLocalPathFile); + Object hasDbFiles = plugin.execute("hasDbFiles", map); + return BooleanUtil.toBoolean(StrUtil.toStringOrNull(hasDbFiles)); + } + + /** + * 恢复数据库 + */ + @Override + public String deleteDbFiles() throws Exception { + File dbLocalPathFile = StorageServiceFactory.dbLocalPath(); + if (!FileUtil.exist(dbLocalPathFile)) { + return null; + } + File deleteBackup = FileUtil.file(dbLocalPathFile, "recover_backup", DateTime.now().toString(DatePattern.PURE_DATETIME_FORMAT)); + // + IPlugin plugin = PluginFactory.getPlugin("db-h2"); + Map map = new HashMap<>(10); + map.put("dbName", this.getDbName()); + map.put("dbPath", dbLocalPathFile); + map.put("backupPath", deleteBackup); + plugin.execute("deleteDbFiles", map); + // 清空记录 + StorageServiceFactory.clearExecuteSqlLog(); + return FileUtil.getAbsolutePath(deleteBackup); + } + + /** + * 转换 sql 文件内容,低版本兼容高版本 + * + * @param sqlFile sql 文件 + */ + @Override + public void transformSql(File sqlFile) { + List list = FileUtil.readLines(sqlFile, StandardCharsets.UTF_8); + list = list.stream().map(s -> { + if (StrUtil.startWith(s, "CREATE PRIMARY KEY SYSTEM_LOB_STREAM_PRIMARY_KEY ON SYSTEM_LOB_STREAM(ID, PART);")) { + return "-- " + s; + } + return s; + }).collect(Collectors.toList()); + FileUtil.writeLines(list, sqlFile, StandardCharsets.UTF_8); + } + + + /** + * 恢复数据库 + * + * @param dsFactory 数据库连接 + */ + @Override + public void executeRecoverDbSql(DSFactory dsFactory, File recoverSqlFile) throws Exception { + if (!FileUtil.isFile(recoverSqlFile)) { + return; + } + // + IPlugin plugin = PluginFactory.getPlugin("db-h2"); + Map map = new HashMap<>(10); + map.put("backupSqlPath", FileUtil.getAbsolutePath(recoverSqlFile)); + map.put("dataSource", dsFactory.getDataSource()); + plugin.execute("restoreBackupSql", map); + } + + + @Override + public void alterUser(String oldUes, String newUse, String newPwd) throws SQLException { + String sql; + if (StrUtil.equals(oldUes, newUse)) { + sql = String.format("ALTER USER %s SET PASSWORD '%s' ", newUse, newPwd); + } else { + sql = String.format("create user %s password '%s';DROP USER %s", newUse, newPwd, oldUes); + } + Db.use(this.dsFactory.getDataSource()).execute(sql); + } + + @Override + public void backupSql(String url, String user, String pass, String backupSqlPath, List tableNameList) throws Exception { + Map map = new HashMap<>(10); + map.put("url", url); + map.put("user", user); + map.put("pass", pass); + map.put("backupSqlPath", backupSqlPath); + map.put("tableNameList", tableNameList); + IPlugin plugin = PluginFactory.getPlugin("db-h2"); + plugin.execute("backupSql", map); + } + + @Override + public void close() throws Exception { + log.info("h2 db destroy"); + if (this.dsFactory != null) { + dsFactory.destroy(); + this.dsFactory = null; + } + } +} 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 new file mode 100644 index 0000000000..225464b069 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.*; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +public class H2TableBuilderImpl implements IStorageSqlBuilderService { + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.H2; + } + + @Override + public String generateIndexSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + for (TableViewIndexData viewIndexData : row) { + String indexType = viewIndexData.getIndexType(); + switch (indexType) { + 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, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + 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, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + 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(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + indexType); + } + stringBuilder.append(";").append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + @Override + public String generateAlterTableSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + for (TableViewAlterData viewAlterData : row) { + String alterType = viewAlterData.getAlterType(); + switch (alterType) { + case "DROP": + // ALTER TABLE NODE_INFO DROP COLUMN IF EXISTS `cycle`; + stringBuilder.append("ALTER TABLE ").append(viewAlterData.getTableName()).append(" DROP COLUMN IF EXISTS `").append(viewAlterData.getName()).append("`"); + break; + case "ADD": + // ALTER TABLE PROJECT_INFO ADD IF NOT EXISTS triggerToken VARCHAR (100) comment '触发器token'; + stringBuilder.append("ALTER TABLE ").append(viewAlterData.getTableName()).append(" ADD IF NOT EXISTS "); + stringBuilder.append(this.generateColumnSql(viewAlterData)); + break; + case "ALTER": + // alter table table1 modify column column1 decimal(10,1) DEFAULT NULL COMMENT '注释'; + stringBuilder.append("ALTER TABLE ").append(viewAlterData.getTableName()).append(" modify column "); + stringBuilder.append(this.generateColumnSql(viewAlterData)); + break; + case "DROP-TABLE": + stringBuilder.append("drop table if exists ").append(viewAlterData.getTableName()); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + alterType); + } + stringBuilder.append(";").append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + /** + * CREATE TABLE IF NOT EXISTS PUBLIC.USEROPERATELOGV1 + * ( + * id VARCHAR(50) not null comment 'id', + * reqId VARCHAR(50) COMMENT '请求ID', + * CONSTRAINT USEROPERATELOGV1_PK PRIMARY KEY (id) + * ); + * COMMENT ON TABLE USEROPERATELOGV1 is '操作日志'; + * + * @param name 表名 + * @param desc 描述 + * @param row 字段信息 + * @return sql + */ + @Override + public String generateTableSql(String name, String desc, List row) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("CREATE TABLE IF NOT EXISTS PUBLIC.").append(name).append(StrUtil.LF); + stringBuilder.append("(").append(StrUtil.LF); + for (TableViewData tableViewData : row) { + stringBuilder.append(StrUtil.TAB).append(this.generateColumnSql(tableViewData)).append(StrUtil.COMMA).append(StrUtil.LF); + } + // 主键 + List primaryKeys = row.stream() + .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey()) + .map(TableViewRowData::getName) + .collect(Collectors.toList()); + Assert.notEmpty(primaryKeys, I18nMessageUtil.get("i18n.table_without_primary_key.7392")); + stringBuilder.append(StrUtil.TAB).append("CONSTRAINT ").append(name).append("_PK PRIMARY KEY (").append(CollUtil.join(primaryKeys, StrUtil.COMMA)).append(")").append(StrUtil.LF); + stringBuilder.append(");").append(StrUtil.LF); + // 表描述 + stringBuilder.append("COMMENT ON TABLE ").append(name).append(" is '").append(desc).append("';"); + return stringBuilder.toString(); + } + + @Override + public String generateColumnSql(TableViewRowData tableViewRowData) { + // id VARCHAR(50) not null default '' comment 'id' + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("`").append(tableViewRowData.getName()).append("`").append(StrUtil.SPACE); + String type = tableViewRowData.getType(); + Assert.hasText(type, I18nMessageUtil.get("i18n.data_type_not_configured_correctly.bf16")); + type = type.toUpperCase(); + switch (type) { + case "LONG": + stringBuilder.append("BIGINT").append(StrUtil.SPACE); + break; + case "STRING": + stringBuilder.append("VARCHAR(").append(ObjectUtil.defaultIfNull(tableViewRowData.getLen(), 255)).append(")").append(StrUtil.SPACE); + break; + case "TEXT": + stringBuilder.append("CLOB").append(StrUtil.SPACE); + break; + case "INTEGER": + stringBuilder.append("INTEGER").append(StrUtil.SPACE); + break; + case "TINYINT": + stringBuilder.append("TINYINT").append(StrUtil.SPACE); + break; + case "FLOAT": + stringBuilder.append("REAL").append(StrUtil.SPACE); + break; + case "DOUBLE": + stringBuilder.append("DOUBLE").append(StrUtil.SPACE); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.data_type_not_supported.fd03") + type); + } + // + Boolean notNull = tableViewRowData.getNotNull(); + if (notNull != null && notNull) { + stringBuilder.append("not null").append(StrUtil.SPACE); + } + // + String defaultValue = tableViewRowData.getDefaultValue(); + if (StrUtil.isNotEmpty(defaultValue)) { + stringBuilder.append("default '").append(defaultValue).append("'").append(StrUtil.SPACE); + } + stringBuilder.append("comment '").append(tableViewRowData.getComment()).append("'"); + return stringBuilder.toString(); + } +} diff --git a/modules/storage-module/storage-module-h2/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService b/modules/storage-module/storage-module-h2/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService new file mode 100644 index 0000000000..2b0891e167 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService @@ -0,0 +1 @@ +org.dromara.jpom.storage.H2StorageServiceImpl diff --git a/modules/storage-module/storage-module-h2/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService b/modules/storage-module/storage-module-h2/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService new file mode 100644 index 0000000000..e0d259e0c3 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService @@ -0,0 +1 @@ +org.dromara.jpom.storage.H2TableBuilderImpl diff --git a/modules/storage-module/storage-module-h2/src/test/java/H2ConnTest1.java b/modules/storage-module/storage-module-h2/src/test/java/H2ConnTest1.java new file mode 100644 index 0000000000..b9252c9953 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/H2ConnTest1.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; + +/** + *

ClassName: H2ConnTest1

+ *

Description: Java通过JDBC方式连接H2数据库

+ * + * @author xudp + * @version 1.0 V + * @createTime 2014-12-18 上午11:22:12 + */ +public class H2ConnTest1 { + //数据库连接URL,当前连接的是E:/H2目录下的gacl数据库 + private static final String JDBC_URL = "jdbc:h2:D:\\jpom\\server\\db\\Server"; + //连接数据库时使用的用户名 + private static final String USER = "jpom"; + //连接数据库时使用的密码 + private static final String PASSWORD = "jpom"; + //连接H2数据库时使用的驱动类,org.h2.Driver这个类是由H2数据库自己提供的,在H2数据库的jar包中可以找到 + private static final String DRIVER_CLASS = "org.h2.Driver"; + + public static void main(String[] args) throws Exception { + // 加载H2数据库驱动 + Class.forName(DRIVER_CLASS); + // 根据连接URL,用户名,密码获取数据库连接 + Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); + Statement stmt = conn.createStatement(); + + //查询 + ResultSet rs = stmt.executeQuery("SELECT * FROM UserOperateLogV1"); + //遍历结果集 + while (rs.next()) { + System.out.println(rs.getString("userId")); + } + //释放资源 + stmt.close(); + //关闭连接 + conn.close(); + } +} diff --git a/modules/storage-module/storage-module-h2/src/test/java/TestH2Recover.java b/modules/storage-module/storage-module-h2/src/test/java/TestH2Recover.java new file mode 100644 index 0000000000..b2d912f4f6 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/TestH2Recover.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import org.h2.tools.Recover; +import org.junit.Test; + +import java.sql.SQLException; + +/** + * @author bwcx_jzy + * @since 2022/1/18 + */ +public class TestH2Recover { + + @Test + public void test() throws SQLException { + String path = "/Users/user/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/e54b85b859057912ed9509c5ea6878fd/Message/MessageTemp/69852fb1bb9f685625ff2d92ea23c1af/File"; + Recover recover = new Recover(); + recover.runTool("-dir", path, "-db", "Server", "-trace", "-transactionLog"); + } +} diff --git a/modules/storage-module/storage-module-h2/src/test/java/TestH2Shell.java b/modules/storage-module/storage-module-h2/src/test/java/TestH2Shell.java new file mode 100644 index 0000000000..04ea471a22 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/TestH2Shell.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import org.h2.tools.Shell; +import org.junit.Test; + +import java.io.PrintStream; +import java.sql.SQLException; + +/** + * @author bwcx_jzy + * @since 2022/1/18 + */ +public class TestH2Shell { + + @Test + public void testShell() throws SQLException { +// + Shell shell = new Shell(); + String sql = StrUtil.format("SCRIPT DROP to '{}'", FileUtil.file(".", "t.sql").getAbsoluteFile()); + String[] params = new String[]{ + "-url", "jdbc:h2:/Users/user/jpom/server/db/Server", + "-user", "jpom", + "-password", "jpom", + "-driver", "org.h2.Driver", + "-sql", sql + }; + FastByteArrayOutputStream baos = new FastByteArrayOutputStream(); + shell.setOut(new PrintStream(baos)); + shell.runTool(params); + } +} diff --git a/modules/storage-module/storage-module-h2/src/test/java/TestH2Sql.java b/modules/storage-module/storage-module-h2/src/test/java/TestH2Sql.java new file mode 100644 index 0000000000..04307090cd --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/TestH2Sql.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Test; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2022/6/10 + */ +public class TestH2Sql { + + @Test + public void test() { + File sqlFile = FileUtil.file("/Users/user/jpom/server/db/backup/20220609131122.sql"); + List list = FileUtil.readLines(sqlFile, StandardCharsets.UTF_8); + list = list.stream().map(s -> { + if (StrUtil.startWith(s, "CREATE PRIMARY KEY SYSTEM_LOB_STREAM_PRIMARY_KEY ON SYSTEM_LOB_STREAM(ID, PART);")) { + return "-- " + s; + } + return s; + }).collect(Collectors.toList()); + FileUtil.writeLines(list, sqlFile, StandardCharsets.UTF_8); + } +} diff --git a/modules/storage-module/storage-module-h2/src/test/java/TestH2String.java b/modules/storage-module/storage-module-h2/src/test/java/TestH2String.java new file mode 100644 index 0000000000..6fb457eb53 --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/TestH2String.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.convert.Convert; +import org.h2.util.StringUtils; +import org.junit.Test; + +import java.math.BigInteger; + +/** + * @author bwcx_jzy + * @since 2022/1/19 + */ +public class TestH2String { + + @Test + public void testStr() { + String json = "356232323465346634343435323232633232353535303437353234313434343535663465346634343435356634633439353335343232326332323533343534313532343334383566353035323466346134353433353432323263323235333533343832323263323235333533343835663436343934633435323232633232353335333438356635343435353234643439346534313463323232633232353335333438356635343435353234643439346534313463356634633466343732323263323234663535353434373439353634393465343732323263323234663535353434373439353634393465343735663463346634373232326332323466353535343437343935363439346534373566343334663465343634393437356635373438343935343435346334393533353432323263323234643466346534393534346635323232326332323464346634653439353434663532356634633466343732323263323234663530353435663464346634653439353434663532323232633232343235353439346334343232326332323432353534393463343435663463346634373232326332323432353534393463343435663532343535303466353334393534346635323539323232633232353535333435353232323263323235353533343535323566346334663437323232633232353335393533353434353464356634353464343134393463323232633232353335393533353434353464356634333431343334383435323232633232353335393533353434353464356634633466343732323263323235333539353335343435346435663535353034373532343134343435323232633232353335393533353434353464356634333466346534363439343732323263323235333539353335343435346435663433346634653436343934373566343935303232326332323533353935333534343534643566343234313433346235353530323232633232353335393533353434353464356635373466353234623533353034313433343532323263323235303532346634613435343335343232326332323530353234663461343534333534356634363439346334353232326332323530353234663461343534333534356634633466343732323263323235303532346634613435343335343566343334663465353334663463343532323263323234613434346235663463343935333534323232633232353334333532343935303534323232633232353434663464343334313534323232633232353434663464343334313534356634363439346334353232326332323534346634643433343135343566346334663437323232633232346534373439346535383232326332323533353334633232326332323465346634343435356634333466346534363439343735663537343834393534343534633439353335343232326332323465346634343435356634333466346534363439343732323263323234653466343434353566343334313433343834353232326332323465346634343435356634633466343732323263323234653466343434353566353535303437353234313434343532323564"; + BigInteger bigInteger = new BigInteger(json); + byte[] bytes = StringUtils.convertHexToBytes(json); + System.out.println(new String(bytes)); + String s1 = Convert.toStr(bigInteger); + System.out.println(s1); + String s = bigInteger.toString(2); + System.out.println(s); + System.out.println(bigInteger); + } +} diff --git a/modules/storage-module/storage-module-h2/src/test/java/TestSqlStr.java b/modules/storage-module/storage-module-h2/src/test/java/TestSqlStr.java new file mode 100644 index 0000000000..fa7ac18ecf --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/TestSqlStr.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import org.junit.Test; + +import java.io.InputStream; + +/** + * @author bwcx_jzy + * @since 2023/1/6 + */ +public class TestSqlStr { + + @Test + public void test() { + InputStream stream = ResourceUtil.getStream("sql/h2-db-v1.0.sql"); + String sql = IoUtil.readUtf8(stream); + System.out.println(sql); + } +} diff --git a/modules/storage-module/storage-module-h2/src/test/java/jdbc/conn/h2/test/TestBackup.java b/modules/storage-module/storage-module-h2/src/test/java/jdbc/conn/h2/test/TestBackup.java new file mode 100644 index 0000000000..2cf1d0274f --- /dev/null +++ b/modules/storage-module/storage-module-h2/src/test/java/jdbc/conn/h2/test/TestBackup.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package jdbc.conn.h2.test; + +import org.h2.tools.Backup; +import org.h2.tools.ConvertTraceFile; +import org.junit.Test; + +import java.sql.SQLException; + +/** + * @author bwcx_jzy + * @since 2022/1/18 + */ +public class TestBackup { + + @Test + public void testBackup() throws SQLException { + Backup backup = new Backup(); + backup.runTool( + "-dir", "/Users/user/jpom/server/db-back/", + "-db", "Server"); + } + + @Test + public void convertTrace() throws SQLException { + ConvertTraceFile convertTraceFile = new ConvertTraceFile(); + convertTraceFile.runTool("-traceFile", "/Users/user/jpom/server/db/Server.trace.db"); + } + + @Test + public void convertTrace2() throws SQLException { + ConvertTraceFile convertTraceFile = new ConvertTraceFile(); + convertTraceFile.runTool("-traceFile", "/Users/user/fsdownload/Server.trace.db"); + } + + +} diff --git a/modules/storage-module/storage-module-mariadb/README.md b/modules/storage-module/storage-module-mariadb/README.md new file mode 100644 index 0000000000..0afcb27d12 --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/README.md @@ -0,0 +1,3 @@ +```shell +docker run -d --name jpom-mariadb -e MYSQL_ROOT_PASSWORD=jpom123456 -p 3309:3306 mariadb +``` \ No newline at end of file diff --git a/modules/storage-module/storage-module-mariadb/pom.xml b/modules/storage-module/storage-module-mariadb/pom.xml new file mode 100644 index 0000000000..d9f22c5600 --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + org.dromara.jpom.storage-module + jpom-storage-module-parent + 2.11.6.6 + ../pom.xml + + + storage-module-mariadb + + + 8 + 8 + UTF-8 + + + + + org.dromara.jpom.storage-module + storage-module-common + ${project.version} + + + + org.mariadb.jdbc + mariadb-java-client + + + jna + net.java.dev.jna + + + jna-platform + net.java.dev.jna + + + checker-qual + org.checkerframework + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + diff --git a/modules/storage-module/storage-module-mariadb/src/main/java/org/dromara/jpom/storage/MariadbStorageServiceImpl.java b/modules/storage-module/storage-module-mariadb/src/main/java/org/dromara/jpom/storage/MariadbStorageServiceImpl.java new file mode 100644 index 0000000000..e2d17572a8 --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/src/main/java/org/dromara/jpom/storage/MariadbStorageServiceImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.lang.Opt; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.setting.Setting; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.IStorageService; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @since 2024/4/1 + */ +@Slf4j +public class MariadbStorageServiceImpl implements IStorageService { + + private String dbUrl; + private DSFactory dsFactory; + + @Override + public String dbUrl() { + Assert.hasText(this.dbUrl, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dbUrl; + } + + @Override + public int getFetchSize() { + return Integer.MIN_VALUE; + } + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.MARIADB; + } + + @Override + public DSFactory init(DbExtConfig dbExtConfig) { + Assert.isNull(this.dsFactory, I18nMessageUtil.get("i18n.do_not_reinitialize_database.9bb5")); + Assert.hasText(dbExtConfig.getUrl(), I18nMessageUtil.get("i18n.database_connection_not_configured.c80e")); + Setting setting = dbExtConfig.toSetting(); + this.dsFactory = DSFactory.create(setting); + this.dbUrl = dbExtConfig.getUrl(); + return this.dsFactory; + } + + @Override + public DSFactory create(DbExtConfig dbExtConfig, String url, String user, String pass) { + Setting setting = this.createSetting(dbExtConfig, url, user, pass); + return DSFactory.create(setting); + } + + @Override + public Setting createSetting(DbExtConfig dbExtConfig, String url, String user, String pass) { + String url2 = Opt.ofBlankAble(url).orElse(dbExtConfig.getUrl()); + String user2 = Opt.ofBlankAble(user).orElse(dbExtConfig.getUserName()); + String pass2 = Opt.ofBlankAble(pass).orElse(dbExtConfig.getUserPwd()); + Setting setting = dbExtConfig.toSetting(); + setting.set("user", user2); + setting.set("pass", pass2); + setting.set("url", url2); + return setting; + } + + public DSFactory getDsFactory() { + Assert.notNull(this.dsFactory, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dsFactory; + } + + + @Override + public JpomRuntimeException warpException(Exception e) { + return new JpomRuntimeException(I18nMessageUtil.get("i18n.database_exception.4894"), e); + } + + + @Override + public void close() throws Exception { + log.info("mysql db destroy"); + if (this.dsFactory != null) { + dsFactory.destroy(); + this.dsFactory = null; + } + } +} diff --git a/modules/storage-module/storage-module-mariadb/src/main/java/org/dromara/jpom/storage/MariadbTableBuilderImpl.java b/modules/storage-module/storage-module-mariadb/src/main/java/org/dromara/jpom/storage/MariadbTableBuilderImpl.java new file mode 100644 index 0000000000..d518520ac7 --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/src/main/java/org/dromara/jpom/storage/MariadbTableBuilderImpl.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.*; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2024/4/1 + */ +public class MariadbTableBuilderImpl implements IStorageSqlBuilderService { + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.MARIADB; + } + + @Override + public String generateIndexSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + for (TableViewIndexData viewIndexData : row) { + String indexType = viewIndexData.getIndexType(); + switch (indexType) { + case "ADD-UNIQUE": { + // 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, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + 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 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, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + 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(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + indexType); + } + stringBuilder.append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + @Override + public String generateAlterTableSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + for (TableViewAlterData viewAlterData : row) { + String alterType = viewAlterData.getAlterType(); + switch (alterType) { + case "DROP": + // ALTER TABLE NODE_INFO DROP COLUMN IF EXISTS `cycle`; + stringBuilder.append("CALL drop_column_if_exists('").append(viewAlterData.getTableName()).append("', '").append(viewAlterData.getName()).append("')"); + break; + case "ADD": + // ALTER TABLE PROJECT_INFO ADD IF NOT EXISTS triggerToken VARCHAR (100) comment '触发器token'; + String columnSql = this.generateColumnSql(viewAlterData, true); + stringBuilder.append("CALL add_column_if_not_exists('").append(viewAlterData.getTableName()).append("','").append(viewAlterData.getName()).append("','").append(columnSql).append("')"); + break; + case "ALTER": + // alter table table1 modify column column1 decimal(10,1) DEFAULT NULL COMMENT '注释'; + stringBuilder.append("ALTER TABLE ").append(viewAlterData.getTableName()).append(" modify column "); + stringBuilder.append(this.generateColumnSql(viewAlterData)); + break; + case "DROP-TABLE": + stringBuilder.append("drop table if exists ").append(viewAlterData.getTableName()); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + alterType); + } + stringBuilder.append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + /** + * CREATE TABLE IF NOT EXISTS USEROPERATELOGV1 + * ( + * id VARCHAR(50) not null comment 'id', + * reqId VARCHAR(50) COMMENT '请求ID', + * CONSTRAINT USEROPERATELOGV1_PK PRIMARY KEY (id) + * ); + * COMMENT ON TABLE USEROPERATELOGV1 is '操作日志'; + * + * @param name 表名 + * @param desc 描述 + * @param row 字段信息 + * @return sql + */ + @Override + public String generateTableSql(String name, String desc, List row) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("CREATE TABLE IF NOT EXISTS ").append(name).append(StrUtil.LF); + stringBuilder.append("(").append(StrUtil.LF); + for (TableViewData tableViewData : row) { + stringBuilder.append(StrUtil.TAB).append(this.generateColumnSql(tableViewData)).append(StrUtil.COMMA).append(StrUtil.LF); + } + // 主键 + List primaryKeys = row.stream() + .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey()) + .map(TableViewRowData::getName) + .collect(Collectors.toList()); + Assert.notEmpty(primaryKeys, I18nMessageUtil.get("i18n.table_without_primary_key.7392")); + 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("';"); + return stringBuilder.toString(); + } + + @Override + public String generateColumnSql(TableViewRowData tableViewRowData) { + return generateColumnSql(tableViewRowData, false); + } + + private String generateColumnSql(TableViewRowData tableViewRowData, boolean encode) { + // id VARCHAR(50) not null default '' comment 'id' + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("`").append(tableViewRowData.getName()).append("`").append(StrUtil.SPACE); + String type = tableViewRowData.getType(); + Assert.hasText(type, I18nMessageUtil.get("i18n.data_type_not_configured_correctly.bf16")); + type = type.toUpperCase(); + switch (type) { + case "LONG": + stringBuilder.append("BIGINT").append(StrUtil.SPACE); + break; + case "STRING": + stringBuilder.append("VARCHAR(").append(ObjectUtil.defaultIfNull(tableViewRowData.getLen(), 255)).append(")").append(StrUtil.SPACE); + break; + case "TEXT": + stringBuilder.append("TEXT").append(StrUtil.SPACE); + break; + case "INTEGER": + stringBuilder.append("int").append(StrUtil.SPACE); + break; + case "TINYINT": + stringBuilder.append("TINYINT").append(StrUtil.SPACE); + break; + case "FLOAT": + stringBuilder.append("float").append(StrUtil.SPACE); + break; + case "DOUBLE": + stringBuilder.append("double").append(StrUtil.SPACE); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.data_type_not_supported.fd03") + type); + } + // + Boolean notNull = tableViewRowData.getNotNull(); + if (notNull != null && notNull) { + stringBuilder.append("not null").append(StrUtil.SPACE); + } + // + String defaultValue = tableViewRowData.getDefaultValue(); + if (StrUtil.isNotEmpty(defaultValue)) { + stringBuilder.append("default '").append(defaultValue).append("'").append(StrUtil.SPACE); + } + stringBuilder.append("comment '").append(tableViewRowData.getComment()).append("'"); + // + String columnSql = stringBuilder.toString(); + if (encode) { + columnSql = StrUtil.replace(columnSql, "'", "\\'"); + } + int length = StrUtil.length(columnSql); + Assert.state(length <= 180, I18nMessageUtil.get("i18n.sql_statement_too_long.38d6")); + return columnSql; + } + + @Override + public String delimiter() { + return "-- mariadb delimiter"; + } +} diff --git a/modules/storage-module/storage-module-mariadb/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService b/modules/storage-module/storage-module-mariadb/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService new file mode 100644 index 0000000000..0b26db7e3c --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService @@ -0,0 +1 @@ +org.dromara.jpom.storage.MariadbStorageServiceImpl diff --git a/modules/storage-module/storage-module-mariadb/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService b/modules/storage-module/storage-module-mariadb/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService new file mode 100644 index 0000000000..260d0be7b2 --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService @@ -0,0 +1 @@ +org.dromara.jpom.storage.MariadbTableBuilderImpl diff --git a/modules/storage-module/storage-module-mariadb/src/main/resources/sql-view/execute.mariadb.v1.0.sql b/modules/storage-module/storage-module-mariadb/src/main/resources/sql-view/execute.mariadb.v1.0.sql new file mode 100644 index 0000000000..3544396e0c --- /dev/null +++ b/modules/storage-module/storage-module-mariadb/src/main/resources/sql-view/execute.mariadb.v1.0.sql @@ -0,0 +1,88 @@ +-- +-- Copyright (c) 2019 Of Him Code Technology Studio +-- Jpom is licensed under Mulan PSL v2. +-- You can use this software according to the terms and conditions of the Mulan PSL v2. +-- You may obtain a copy of Mulan PSL v2 at: +-- http://license.coscl.org.cn/MulanPSL2 +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +-- See the Mulan PSL v2 for more details. +-- + +DROP FUNCTION IF EXISTS column_exists1; + +-- mariadb delimiter + +CREATE FUNCTION column_exists1( + tname VARCHAR(64), + cname VARCHAR(64) +) + RETURNS BOOLEAN + READS SQL DATA +BEGIN + RETURN 0 < (SELECT COUNT(*) + FROM `INFORMATION_SCHEMA`.`COLUMNS` + WHERE `TABLE_SCHEMA` = SCHEMA() + AND `TABLE_NAME` = tname + AND `COLUMN_NAME` = cname); +END + +-- mariadb delimiter + +DROP PROCEDURE IF EXISTS drop_column_if_exists; + +-- mariadb delimiter + +CREATE PROCEDURE drop_column_if_exists( + tname VARCHAR(64), + cname VARCHAR(64) +) +BEGIN + IF column_exists1(tname, cname) + THEN + SET @drop_column_if_exists = CONCAT('ALTER TABLE `', tname, '` DROP COLUMN `', cname, '`'); + PREPARE drop_query FROM @drop_column_if_exists; + EXECUTE drop_query; + END IF; +END + +-- mariadb delimiter + +DROP PROCEDURE IF EXISTS add_column_if_not_exists; + +-- mariadb delimiter + +CREATE PROCEDURE add_column_if_not_exists( + tname VARCHAR(64), + cname VARCHAR(64), + columninfo VARCHAR(200) +) +BEGIN + IF column_exists1(tname, cname) + THEN + SET @add_column_sql = ''; + else + SET @add_column_sql = CONCAT('ALTER TABLE `', tname, '` ADD COLUMN ', columninfo); + PREPARE execute_query FROM @add_column_sql; + EXECUTE execute_query; + END IF; +END + +-- mariadb delimiter + +DROP PROCEDURE IF EXISTS drop_index_if_exists; + +-- mariadb delimiter + +create procedure drop_index_if_exists( + p_tablename varchar(200), + p_idxname VARCHAR(200) +) +begin + select count(*) into @cnt from information_schema.statistics where `TABLE_SCHEMA` = SCHEMA() and table_name = p_tablename and index_name = p_idxname; + if @cnt > 0 then + set @str = concat('drop index ', p_idxname, ' on ', p_tablename); + PREPARE execute_query FROM @str; + EXECUTE execute_query; + end if; + +end; diff --git a/modules/storage-module/storage-module-mysql/README.md b/modules/storage-module/storage-module-mysql/README.md new file mode 100644 index 0000000000..13f87d0845 --- /dev/null +++ b/modules/storage-module/storage-module-mysql/README.md @@ -0,0 +1,14 @@ +# mysql + +```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 new file mode 100644 index 0000000000..6314e64388 --- /dev/null +++ b/modules/storage-module/storage-module-mysql/pom.xml @@ -0,0 +1,52 @@ + + + + 4.0.0 + + org.dromara.jpom.storage-module + jpom-storage-module-parent + 2.11.6.6 + ../pom.xml + + + storage-module-mysql + + + 8 + 8 + UTF-8 + + + + + org.dromara.jpom.storage-module + storage-module-common + ${project.version} + + + + com.mysql + mysql-connector-j + + + + org.dromara.jpom + common + provided + ${project.version} + + + + 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 new file mode 100644 index 0000000000..77028aa223 --- /dev/null +++ b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlStorageServiceImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.lang.Opt; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.setting.Setting; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.IStorageService; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.util.Assert; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +@Slf4j +public class MysqlStorageServiceImpl implements IStorageService { + + private String dbUrl; + private DSFactory dsFactory; + + @Override + public String dbUrl() { + Assert.hasText(this.dbUrl, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dbUrl; + } + + @Override + public int getFetchSize() { + return Integer.MIN_VALUE; + } + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.MYSQL; + } + + @Override + public DSFactory init(DbExtConfig dbExtConfig) { + Assert.isNull(this.dsFactory, I18nMessageUtil.get("i18n.do_not_reinitialize_database.9bb5")); + Assert.hasText(dbExtConfig.getUrl(), I18nMessageUtil.get("i18n.database_connection_not_configured.c80e")); + Setting setting = dbExtConfig.toSetting(); + this.dsFactory = DSFactory.create(setting); + this.dbUrl = dbExtConfig.getUrl(); + return this.dsFactory; + } + + @Override + public DSFactory create(DbExtConfig dbExtConfig, String url, String user, String pass) { + Setting setting = this.createSetting(dbExtConfig, url, user, pass); + return DSFactory.create(setting); + } + + @Override + public Setting createSetting(DbExtConfig dbExtConfig, String url, String user, String pass) { + String url2 = Opt.ofBlankAble(url).orElse(dbExtConfig.getUrl()); + String user2 = Opt.ofBlankAble(user).orElse(dbExtConfig.getUserName()); + String pass2 = Opt.ofBlankAble(pass).orElse(dbExtConfig.getUserPwd()); + Setting setting = dbExtConfig.toSetting(); + setting.set("user", user2); + setting.set("pass", pass2); + setting.set("url", url2); + return setting; + } + + public DSFactory getDsFactory() { + Assert.notNull(this.dsFactory, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dsFactory; + } + + + @Override + public JpomRuntimeException warpException(Exception e) { + return new JpomRuntimeException(I18nMessageUtil.get("i18n.database_exception.4894"), e); + } + + + @Override + public void close() throws Exception { + log.info("mysql db destroy"); + if (this.dsFactory != null) { + dsFactory.destroy(); + this.dsFactory = null; + } + } +} 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 new file mode 100644 index 0000000000..8419452cd8 --- /dev/null +++ b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.*; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/1/5 + */ +public class MysqlTableBuilderImpl implements IStorageSqlBuilderService { + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.MYSQL; + } + + @Override + public String generateIndexSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + for (TableViewIndexData viewIndexData : row) { + String indexType = viewIndexData.getIndexType(); + switch (indexType) { + case "ADD-UNIQUE": { + // 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, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + 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 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, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + 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(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + indexType); + } + stringBuilder.append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + @Override + public String generateAlterTableSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + for (TableViewAlterData viewAlterData : row) { + String alterType = viewAlterData.getAlterType(); + switch (alterType) { + case "DROP": + // ALTER TABLE NODE_INFO DROP COLUMN IF EXISTS `cycle`; + stringBuilder.append("CALL drop_column_if_exists('").append(viewAlterData.getTableName()).append("', '").append(viewAlterData.getName()).append("')"); + break; + case "ADD": + // ALTER TABLE PROJECT_INFO ADD IF NOT EXISTS triggerToken VARCHAR (100) comment '触发器token'; + String columnSql = this.generateColumnSql(viewAlterData, true); + stringBuilder.append("CALL add_column_if_not_exists('").append(viewAlterData.getTableName()).append("','").append(viewAlterData.getName()).append("','").append(columnSql).append("')"); + break; + case "ALTER": + // alter table table1 modify column column1 decimal(10,1) DEFAULT NULL COMMENT '注释'; + stringBuilder.append("ALTER TABLE ").append(viewAlterData.getTableName()).append(" modify column "); + stringBuilder.append(this.generateColumnSql(viewAlterData)); + break; + case "DROP-TABLE": + stringBuilder.append("drop table if exists ").append(viewAlterData.getTableName()); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + alterType); + } + stringBuilder.append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + /** + * CREATE TABLE IF NOT EXISTS USEROPERATELOGV1 + * ( + * id VARCHAR(50) not null comment 'id', + * reqId VARCHAR(50) COMMENT '请求ID', + * CONSTRAINT USEROPERATELOGV1_PK PRIMARY KEY (id) + * ); + * COMMENT ON TABLE USEROPERATELOGV1 is '操作日志'; + * + * @param name 表名 + * @param desc 描述 + * @param row 字段信息 + * @return sql + */ + @Override + public String generateTableSql(String name, String desc, List row) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("CREATE TABLE IF NOT EXISTS ").append(name).append(StrUtil.LF); + stringBuilder.append("(").append(StrUtil.LF); + for (TableViewData tableViewData : row) { + stringBuilder.append(StrUtil.TAB).append(this.generateColumnSql(tableViewData)).append(StrUtil.COMMA).append(StrUtil.LF); + } + // 主键 + List primaryKeys = row.stream() + .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey()) + .map(TableViewRowData::getName) + .collect(Collectors.toList()); + Assert.notEmpty(primaryKeys, I18nMessageUtil.get("i18n.table_without_primary_key.7392")); + 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("';"); + return stringBuilder.toString(); + } + + @Override + public String generateColumnSql(TableViewRowData tableViewRowData) { + return generateColumnSql(tableViewRowData, false); + } + + private String generateColumnSql(TableViewRowData tableViewRowData, boolean encode) { + // id VARCHAR(50) not null default '' comment 'id' + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("`").append(tableViewRowData.getName()).append("`").append(StrUtil.SPACE); + String type = tableViewRowData.getType(); + Assert.hasText(type, I18nMessageUtil.get("i18n.data_type_not_configured_correctly.bf16")); + type = type.toUpperCase(); + switch (type) { + case "LONG": + stringBuilder.append("BIGINT").append(StrUtil.SPACE); + break; + case "STRING": + stringBuilder.append("VARCHAR(").append(ObjectUtil.defaultIfNull(tableViewRowData.getLen(), 255)).append(")").append(StrUtil.SPACE); + break; + case "TEXT": + stringBuilder.append("TEXT").append(StrUtil.SPACE); + break; + case "INTEGER": + stringBuilder.append("int").append(StrUtil.SPACE); + break; + case "TINYINT": + stringBuilder.append("TINYINT").append(StrUtil.SPACE); + break; + case "FLOAT": + stringBuilder.append("float").append(StrUtil.SPACE); + break; + case "DOUBLE": + stringBuilder.append("double").append(StrUtil.SPACE); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.data_type_not_supported.fd03") + type); + } + // + Boolean notNull = tableViewRowData.getNotNull(); + if (notNull != null && notNull) { + stringBuilder.append("not null").append(StrUtil.SPACE); + } + // + String defaultValue = tableViewRowData.getDefaultValue(); + if (StrUtil.isNotEmpty(defaultValue)) { + stringBuilder.append("default '").append(defaultValue).append("'").append(StrUtil.SPACE); + } + stringBuilder.append("comment '").append(tableViewRowData.getComment()).append("'"); + // + String columnSql = stringBuilder.toString(); + if (encode) { + columnSql = StrUtil.replace(columnSql, "'", "\\'"); + } + int length = StrUtil.length(columnSql); + Assert.state(length <= 180, I18nMessageUtil.get("i18n.sql_statement_too_long.38d6")); + return columnSql; + } + + @Override + public String delimiter() { + return "-- mysql delimiter"; + } +} diff --git a/modules/storage-module/storage-module-mysql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService b/modules/storage-module/storage-module-mysql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService new file mode 100644 index 0000000000..991b3f7eca --- /dev/null +++ b/modules/storage-module/storage-module-mysql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService @@ -0,0 +1 @@ +org.dromara.jpom.storage.MysqlStorageServiceImpl diff --git a/modules/storage-module/storage-module-mysql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService b/modules/storage-module/storage-module-mysql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService new file mode 100644 index 0000000000..fb235e2376 --- /dev/null +++ b/modules/storage-module/storage-module-mysql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService @@ -0,0 +1 @@ +org.dromara.jpom.storage.MysqlTableBuilderImpl diff --git a/modules/storage-module/storage-module-mysql/src/main/resources/sql-view/execute.mysql.v1.0.sql b/modules/storage-module/storage-module-mysql/src/main/resources/sql-view/execute.mysql.v1.0.sql new file mode 100644 index 0000000000..aa64c29702 --- /dev/null +++ b/modules/storage-module/storage-module-mysql/src/main/resources/sql-view/execute.mysql.v1.0.sql @@ -0,0 +1,88 @@ +-- +-- Copyright (c) 2019 Of Him Code Technology Studio +-- Jpom is licensed under Mulan PSL v2. +-- You can use this software according to the terms and conditions of the Mulan PSL v2. +-- You may obtain a copy of Mulan PSL v2 at: +-- http://license.coscl.org.cn/MulanPSL2 +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +-- See the Mulan PSL v2 for more details. +-- + +DROP FUNCTION IF EXISTS column_exists; + +-- mysql delimiter + +CREATE FUNCTION column_exists( + tname VARCHAR(64), + cname VARCHAR(64) +) + RETURNS BOOLEAN + READS SQL DATA +BEGIN + RETURN 0 < (SELECT COUNT(*) + FROM `INFORMATION_SCHEMA`.`COLUMNS` + WHERE `TABLE_SCHEMA` = SCHEMA() + AND `TABLE_NAME` = tname + AND `COLUMN_NAME` = cname); +END + +-- mysql delimiter + +DROP PROCEDURE IF EXISTS drop_column_if_exists; + +-- mysql delimiter + +CREATE PROCEDURE drop_column_if_exists( + tname VARCHAR(64), + cname VARCHAR(64) +) +BEGIN + IF column_exists(tname, cname) + THEN + SET @drop_column_if_exists = CONCAT('ALTER TABLE `', tname, '` DROP COLUMN `', cname, '`'); + PREPARE drop_query FROM @drop_column_if_exists; + EXECUTE drop_query; + END IF; +END + +-- mysql delimiter + +DROP PROCEDURE IF EXISTS add_column_if_not_exists; + +-- mysql delimiter + +CREATE PROCEDURE add_column_if_not_exists( + tname VARCHAR(64), + cname VARCHAR(64), + columninfo VARCHAR(200) +) +BEGIN + IF column_exists(tname, cname) + THEN + SET @add_column_sql = ''; + else + SET @add_column_sql = CONCAT('ALTER TABLE `', tname, '` ADD COLUMN ', columninfo); + PREPARE execute_query FROM @add_column_sql; + EXECUTE execute_query; + END IF; +END + +-- mysql delimiter + +DROP PROCEDURE IF EXISTS drop_index_if_exists; + +-- mysql delimiter + +create procedure drop_index_if_exists( + p_tablename varchar(200), + p_idxname VARCHAR(200) +) +begin + select count(*) into @cnt from information_schema.statistics where `TABLE_SCHEMA` = SCHEMA() and table_name = p_tablename and index_name = p_idxname; + if @cnt > 0 then + set @str = concat('drop index ', p_idxname, ' on ', p_tablename); + PREPARE execute_query FROM @str; + EXECUTE execute_query; + end if; + +end; diff --git a/modules/storage-module/storage-module-postgresql/README.md b/modules/storage-module/storage-module-postgresql/README.md new file mode 100644 index 0000000000..adc758f2e4 --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/README.md @@ -0,0 +1,7 @@ +```shell +docker run --name jpom-postgres \ +-e POSTGRES_USER=jpom \ +-e POSTGRES_PASSWORD=jpom123456 \ +-e POSTGRES_DB=jpom \ +-p 5432:5432 -d postgres +``` \ No newline at end of file diff --git a/modules/storage-module/storage-module-postgresql/pom.xml b/modules/storage-module/storage-module-postgresql/pom.xml new file mode 100644 index 0000000000..58175efa17 --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/pom.xml @@ -0,0 +1,50 @@ + + + + + jpom-storage-module-parent + org.dromara.jpom.storage-module + 2.11.6.6 + + 4.0.0 + + storage-module-postgresql + + + 8 + 8 + UTF-8 + + + + + org.dromara.jpom.storage-module + storage-module-common + ${project.version} + + + + org.postgresql + postgresql + + + + org.dromara.jpom + common + provided + ${project.version} + + + diff --git a/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java new file mode 100644 index 0000000000..3d16a69e65 --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.lang.Opt; +import cn.hutool.db.ds.DSFactory; +import cn.hutool.setting.Setting; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.DbExtConfig; +import org.dromara.jpom.db.IStorageService; +import org.dromara.jpom.system.JpomRuntimeException; +import org.springframework.util.Assert; + +/** + * @author whz + * @since 2024/03/06 + */ +@Slf4j +public class PostgresqlStorageServiceImpl implements IStorageService { + + private String dbUrl; + private DSFactory dsFactory; + + @Override + public String dbUrl() { + Assert.hasText(this.dbUrl, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dbUrl; + } + + @Override + public int getFetchSize() { + return Integer.MIN_VALUE; + } + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.POSTGRESQL; + } + + @Override + public DSFactory init(DbExtConfig dbExtConfig) { + Assert.isNull(this.dsFactory, I18nMessageUtil.get("i18n.do_not_reinitialize_database.9bb5")); + Assert.hasText(dbExtConfig.getUrl(), I18nMessageUtil.get("i18n.database_connection_not_configured.c80e")); + Setting setting = dbExtConfig.toSetting(); + this.dsFactory = DSFactory.create(setting); + this.dbUrl = dbExtConfig.getUrl(); + return this.dsFactory; + } + + @Override + public DSFactory create(DbExtConfig dbExtConfig, String url, String user, String pass) { + Setting setting = this.createSetting(dbExtConfig, url, user, pass); + return DSFactory.create(setting); + } + + @Override + public Setting createSetting(DbExtConfig dbExtConfig, String url, String user, String pass) { + String url2 = Opt.ofBlankAble(url).orElse(dbExtConfig.getUrl()); + String user2 = Opt.ofBlankAble(user).orElse(dbExtConfig.getUserName()); + String pass2 = Opt.ofBlankAble(pass).orElse(dbExtConfig.getUserPwd()); + Setting setting = dbExtConfig.toSetting(); + setting.set("user", user2); + setting.set("pass", pass2); + setting.set("url", url2); + return setting; + } + + public DSFactory getDsFactory() { + Assert.notNull(this.dsFactory, I18nMessageUtil.get("i18n.database_not_initialized.e5e7")); + return dsFactory; + } + + + @Override + public JpomRuntimeException warpException(Exception e) { + return new JpomRuntimeException(I18nMessageUtil.get("i18n.database_exception.4894"), e); + } + + + @Override + public void close() { + log.info("postgresql db destroy"); + if (this.dsFactory != null) { + dsFactory.destroy(); + this.dsFactory = null; + } + } +} diff --git a/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java new file mode 100644 index 0000000000..bee086ea8d --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.storage; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.*; +import cn.hutool.db.sql.Wrapper; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.db.*; +import org.dromara.jpom.dialect.DialectUtil; +import org.springframework.util.Assert; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * postgresql的sql语句构建类 + * 目前不管列名还是表名,索引名,都会转化为小写的形式,而特殊的关键字会通过Dialect的Wrapper处理 + * + * @author whz + * @since 2024/3/10 + */ +public class PostgresqlTableBuilderImpl implements IStorageSqlBuilderService { + + /** + * 记录当前所有Model中含有的bool字段的属性名,表名和属性名都会被小写处理 + */ + private final Map> tableName2ModelBoolFieldNameSetMap; + + public PostgresqlTableBuilderImpl() { + Set> modelClassSet = ClassUtil.scanPackageByAnnotation("org.dromara.jpom", TableName.class); + this.tableName2ModelBoolFieldNameSetMap = new HashMap<>(); + modelClassSet.forEach(modelClass -> { + TableName annotation = modelClass.getAnnotation(TableName.class); + // 统一处理成小写,model也应该不会出现转为小写后重名的field + String tableName = annotation.value().toLowerCase(); + Field[] boolFieldArr = ReflectUtil.getFields(modelClass, field -> Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())); + Set nameSet = Arrays.stream(boolFieldArr) + .map(field -> field.getName().toLowerCase()) + .collect(Collectors.toSet()); + tableName2ModelBoolFieldNameSetMap.put(tableName, nameSet); + }); + } + + @Override + public DbExtConfig.Mode mode() { + return DbExtConfig.Mode.POSTGRESQL; + } + + @Override + public String generateIndexSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper(); + for (TableViewIndexData viewIndexData : row) { + String indexType = viewIndexData.getIndexType(); + // 存储过程对大小写敏感,因此传入的 表名,索引名,列名都转需要为小写 + String name = viewIndexData.getName().toLowerCase(); + String field = viewIndexData.getField(); + String tableName = viewIndexData.getTableName().toLowerCase(); + + List fields = StrUtil.splitTrim(field, "+").stream() + .map(fieldWrapper::wrap).collect(Collectors.toList()); + /** + * CALL drop_index_if_exists('表名','索引名'); + * CREATE UNIQUE INDEX 索引名 ON 表名 (field1,field2,...); + * CREATE INDEX 索引名 ON 表名 (field1,field2,...); + * field需要通过wrapper处理 + */ + switch (indexType) { + case "ADD-UNIQUE": { + Assert.notEmpty(fields, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + stringBuilder.append("CALL drop_index_if_exists('").append(tableName).append("','").append(name).append("')").append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + stringBuilder.append("CREATE UNIQUE INDEX ").append(name) + .append(" ON ").append(tableName).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")") + .append(";"); + break; + } + case "ADD": { + Assert.notEmpty(fields, I18nMessageUtil.get("i18n.index_field_not_configured.96d9")); + stringBuilder.append("CALL drop_index_if_exists('").append(tableName).append("','").append(name).append("')").append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + stringBuilder.append("CREATE INDEX ").append(name) + .append(" ON ").append(tableName).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")") + .append(";"); + break; + } + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + indexType); + } + stringBuilder.append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + } + return stringBuilder.toString(); + } + + @Override + public String generateAlterTableSql(List row) { + StringBuilder stringBuilder = new StringBuilder(); + Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper(); + for (TableViewAlterData viewAlterData : row) { + String alterType = viewAlterData.getAlterType(); + String tableName = fieldWrapper.wrap(viewAlterData.getTableName()); + //不使用wrapper,存储过程调用时,column不需要包裹 + String columnName = viewAlterData.getName().toLowerCase(); + switch (alterType) { + case "DROP": + stringBuilder.append("CALL drop_column_if_exists('").append(tableName).append("', '").append(columnName).append("')"); + break; + case "ADD": + stringBuilder.append("CALL add_column_if_not_exists('").append(tableName).append("','") + .append(columnName).append("','"); + stringBuilder.append(generateColumnSql(viewAlterData.getTableName(), viewAlterData, true)); + stringBuilder.append("');"); + /** + * 添加列时,因为不能同时指定注释内容,单独加一个语句设置注释 + * COMMENT ON COLUMN 表名.field IS '注释内容' + * field需要通过wrapper处理 + */ + if (StrUtil.isNotBlank(viewAlterData.getComment())) { + stringBuilder.append(delimiter()).append(StrUtil.LF); + stringBuilder.append("COMMENT ON COLUMN ").append(tableName) + .append(StrUtil.DOT).append(fieldWrapper.wrap(viewAlterData.getName())).append(" IS ") + .append("'").append(viewAlterData.getComment().trim()).append("';"); + } + break; + case "ALTER": + stringBuilder.append(generateAlterSql(viewAlterData)); + break; + case "DROP-TABLE": + stringBuilder.append("drop table if exists ").append(viewAlterData.getTableName()); + break; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + alterType); + } + stringBuilder.append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + + } + return stringBuilder.toString(); + } + + /** + * @param name 表名 + * @param desc 描述 + * @param row 字段信息 + * @return sql + */ + @Override + public String generateTableSql(String name, String desc, List row) { + StringBuilder stringBuilder = new StringBuilder(); + Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper(); + String tableName = fieldWrapper.wrap(name); + + stringBuilder.append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(StrUtil.LF); + stringBuilder.append("(").append(StrUtil.LF); + for (TableViewData tableViewData : row) { + stringBuilder.append(StrUtil.TAB).append(this.generateColumnSql(tableName, tableViewData)).append(StrUtil.COMMA).append(StrUtil.LF); + } + // 主键 + List primaryKeys = row.stream() + .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey()) + .map(viewData -> fieldWrapper.wrap(viewData.getName())) + .collect(Collectors.toList()); + Assert.notEmpty(primaryKeys, I18nMessageUtil.get("i18n.table_without_primary_key.7392")); + stringBuilder.append(StrUtil.TAB).append("PRIMARY KEY (").append(CollUtil.join(primaryKeys, StrUtil.COMMA)).append(")").append(StrUtil.LF); + stringBuilder.append(");").append(StrUtil.LF); + // 表注释 + stringBuilder.append("COMMENT ON TABLE ").append(fieldWrapper.wrap(name)).append(" IS '").append(desc).append("';"); + + + // 建表语句的列注释需要通过单独的sql语句设置 + for (TableViewData tableViewData : row) { + if (StrUtil.isNotBlank(tableViewData.getComment())) { + stringBuilder.append(delimiter()).append(StrUtil.LF); + stringBuilder.append("CALL exec_if_column_exists('") + .append(tableName).append("','") + .append(tableViewData.getName()).append("','") + .append("COMMENT ON COLUMN ").append(tableName) + .append(StrUtil.DOT).append(fieldWrapper.wrap(tableViewData.getName())).append(" IS ") + .append("''").append(tableViewData.getComment().trim()).append("'' ');"); + } + } + return stringBuilder.toString(); + } + + @Override + public String generateColumnSql(TableViewRowData tableViewRowData) { + return StrUtil.EMPTY; + } + + @Override + public String generateColumnSql(String tableName, TableViewRowData tableViewRowData) { + return generateColumnSql(tableName, tableViewRowData, false); + } + + /** + * 生成 alter add 或 create table 时的列定义 + * + * @param tableName + * @param tableViewRowData + * @param encode + * @return + */ + private String generateColumnSql(String tableName, TableViewRowData tableViewRowData, boolean encode) { + + StringBuilder strBuilder = new StringBuilder(); + Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper(); + String type = getColumnTypeStr(tableName, tableViewRowData.getName(), + tableViewRowData.getType(), tableViewRowData.getLen()); + strBuilder.append(StrUtil.SPACE).append(fieldWrapper.wrap(tableViewRowData.getName())) + .append(StrUtil.SPACE).append(type); + + String defaultValue = tableViewRowData.getDefaultValue(); + if (StrUtil.isNotEmpty(defaultValue)) { + if ("BOOLEAN".equals(type)) { + defaultValue = Boolean.toString(BooleanUtil.toBoolean(defaultValue.trim())); + } + strBuilder.append(" DEFAULT '").append(defaultValue).append("'"); + } + + Boolean notNull = tableViewRowData.getNotNull(); + if (notNull != null && notNull) { + strBuilder.append(" NOT NULL "); + } + String columnSql = strBuilder.toString(); + columnSql = encode ? StrUtil.replace(columnSql, "'", "''") : columnSql; + int length = StrUtil.length(columnSql); + Assert.state(length <= 180, I18nMessageUtil.get("i18n.sql_statement_too_long.38d6")); + return columnSql; + } + + /** + * 生成postgresql的alter语句 + * postgresql不像 h2或mysql可以一个alter同时设置 数据类型,默认值,非空,注释,因此需生成多条sql语句才能实现功能 + * + * @param viewAlterData + * @return + */ + private String generateAlterSql(TableViewAlterData viewAlterData) { + + Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper(); + StringBuilder strBuilder = new StringBuilder(); + String tableName = fieldWrapper.wrap(viewAlterData.getTableName()); + String name = fieldWrapper.wrap(viewAlterData.getName()); + + // 先改类型 + String type = getColumnTypeStr(viewAlterData.getTableName(), viewAlterData.getName() + , viewAlterData.getType(), viewAlterData.getLen()); + strBuilder.append("ALTER TABLE ").append(tableName) + .append(" ALTER COLUMN ").append(name) + .append(" TYPE ").append(type).append(";"); + + // 再设置默认值 + strBuilder.append(delimiter()).append(StrUtil.LF); + String defaultValue = viewAlterData.getDefaultValue(); + strBuilder.append("ALTER TABLE ").append(tableName) + .append(" ALTER COLUMN ").append(name) + .append(" SET DEFAULT '"); + if (StrUtil.isNotEmpty(defaultValue)) { + if ("BOOLEAN".equals(type)) { + defaultValue = Boolean.toString(BooleanUtil.toBoolean(defaultValue.trim())); + } + strBuilder.append(defaultValue).append("';"); + } else { + strBuilder.append("NULL").append("';"); + } + + // 设置非空 + strBuilder.append(delimiter()).append(StrUtil.LF); + strBuilder.append("ALTER TABLE ").append(tableName) + .append(" ALTER COLUMN ").append(name); + Boolean notNull = viewAlterData.getNotNull(); + if (notNull != null && notNull) { + strBuilder.append(" SET NOT NULL ").append(";"); + } else { + strBuilder.append(" DROP NOT NULL ").append(";"); + } + + // 注释 + strBuilder.append(delimiter()).append(StrUtil.LF); + String comment = viewAlterData.getComment(); + comment = StrUtil.isEmpty(comment) ? StrUtil.EMPTY : comment.trim(); + strBuilder.append("COMMENT ON COLUMN ").append(tableName) + .append(StrUtil.DOT).append(name).append(" IS ") + .append("'").append(comment).append("';"); + + Assert.state(strBuilder.length() <= 1000, I18nMessageUtil.get("i18n.sql_statement_too_long.38d6")); + return strBuilder.toString(); + } + + + @Override + public String delimiter() { + return "-- postgresql $delimiter$"; + } + + + private String getColumnTypeStr(String tableName, String columnName, String type, Integer dataLen) { + Assert.hasText(type, I18nMessageUtil.get("i18n.data_type_not_configured_correctly.bf16")); + type = type.toUpperCase(); + switch (type) { + case "LONG": + return "BIGINT"; + case "STRING": + return "VARCHAR(" + ObjectUtil.defaultIfNull(dataLen, 255) + ")"; + case "TEXT": + return "TEXT"; + case "INTEGER": + return "INTEGER"; + case "TINYINT": { + columnName = columnName.toLowerCase(); + tableName = tableName.toLowerCase(); + Set nameSet = tableName2ModelBoolFieldNameSetMap.get(tableName); + return nameSet != null && nameSet.contains(columnName) ? "BOOLEAN" : "SMALLINT"; + } + case "FLOAT": + return "REAL"; + case "DOUBLE": + return "DOUBLE PRECISION"; + default: + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.data_type_not_supported.fd03") + type); + } + } +} diff --git a/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService new file mode 100644 index 0000000000..735b67e3a6 --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService @@ -0,0 +1 @@ +org.dromara.jpom.storage.PostgresqlStorageServiceImpl diff --git a/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService new file mode 100644 index 0000000000..6173f7ff35 --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService @@ -0,0 +1 @@ +org.dromara.jpom.storage.PostgresqlTableBuilderImpl diff --git a/modules/storage-module/storage-module-postgresql/src/main/resources/sql-view/execute.postgresql.v1.0.sql b/modules/storage-module/storage-module-postgresql/src/main/resources/sql-view/execute.postgresql.v1.0.sql new file mode 100644 index 0000000000..9818c0615d --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/src/main/resources/sql-view/execute.postgresql.v1.0.sql @@ -0,0 +1,89 @@ +-- +-- Copyright (c) 2019 Of Him Code Technology Studio +-- Jpom is licensed under Mulan PSL v2. +-- You can use this software according to the terms and conditions of the Mulan PSL v2. +-- You may obtain a copy of Mulan PSL v2 at: +-- http://license.coscl.org.cn/MulanPSL2 +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +-- See the Mulan PSL v2 for more details. +-- + +DROP PROCEDURE IF EXISTS drop_column_if_exists; +CREATE PROCEDURE drop_column_if_exists( + tname varchar, + cname varchar +) +LANGUAGE plpgsql +AS $$ +DECLARE + drop_query varchar; +BEGIN + -- 检查列是否存在 + IF (select column_exists(tname,cname)) THEN + -- 构造ALTER TABLE语句 + drop_query := format('ALTER TABLE %s DROP COLUMN %s', tname, cname); + -- 执行ALTER TABLE语句 + EXECUTE drop_query; + END IF; +END; +$$; + +-- postgresql $delimiter$ + +DROP PROCEDURE IF EXISTS add_column_if_not_exists; +CREATE PROCEDURE add_column_if_not_exists( + tname varchar, + cname varchar, + columninfo varchar +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF NOT ( + select column_exists(tname,cname) + ) THEN + -- 构造并执行ALTER TABLE语句来添加新列 + EXECUTE format('ALTER TABLE %s ADD COLUMN %s ', tname, columninfo); + END IF; +END; +$$; + +-- postgresql $delimiter$ + +DROP PROCEDURE IF EXISTS drop_index_if_exists; +CREATE PROCEDURE drop_index_if_exists( + p_tablename varchar, + p_idxname varchar +) +LANGUAGE plpgsql +AS $$ +DECLARE + idx_exists boolean; + drop_idx_sql text; +BEGIN + -- 检查索引是否存在 + SELECT EXISTS ( + SELECT 1 + FROM pg_indexes + WHERE tablename = p_tablename + AND indexname = p_idxname + ) INTO idx_exists; + + -- 如果索引存在,则构建DROP INDEX语句并执行 + IF idx_exists THEN + drop_idx_sql := format('DROP INDEX IF EXISTS %s', p_idxname); + EXECUTE drop_idx_sql; + END IF; +END; +$$; + +-- postgresql $delimiter$ + +-- 实现 instr函数,这个是postgresql上没有的 +DROP FUNCTION IF EXISTS instr; +CREATE FUNCTION instr(str1 text, str2 text) +RETURNS boolean AS +$$ + SELECT POSITION(str2 IN str1) > 0; +$$ +LANGUAGE sql; diff --git a/modules/storage-module/storage-module-postgresql/src/main/resources/sql-view/init.postgresql.v1.0.sql b/modules/storage-module/storage-module-postgresql/src/main/resources/sql-view/init.postgresql.v1.0.sql new file mode 100644 index 0000000000..91188c58a5 --- /dev/null +++ b/modules/storage-module/storage-module-postgresql/src/main/resources/sql-view/init.postgresql.v1.0.sql @@ -0,0 +1,46 @@ +-- +-- Copyright (c) 2019 Of Him Code Technology Studio +-- Jpom is licensed under Mulan PSL v2. +-- You can use this software according to the terms and conditions of the Mulan PSL v2. +-- You may obtain a copy of Mulan PSL v2 at: +-- http://license.coscl.org.cn/MulanPSL2 +-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +-- See the Mulan PSL v2 for more details. +-- + +DROP FUNCTION IF EXISTS column_exists; +CREATE FUNCTION column_exists(tname varchar, cname varchar) +RETURNS boolean +AS +$$ +BEGIN +RETURN EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = tname + AND column_name = cname + ); +END; +$$ +LANGUAGE plpgsql; + +-- postgresql $delimiter$ + +DROP PROCEDURE IF EXISTS exec_if_column_exists; +CREATE PROCEDURE exec_if_column_exists( + tname varchar, + cname varchar, + statemetStr varchar +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF ( + select column_exists(tname,cname) + ) THEN + EXECUTE statemetStr; +END IF; +END; +$$; + +-- postgresql $delimiter$ diff --git a/modules/sub-plugin/README.md b/modules/sub-plugin/README.md new file mode 100644 index 0000000000..a624b3bdc5 --- /dev/null +++ b/modules/sub-plugin/README.md @@ -0,0 +1 @@ +# Jpom 插件 \ No newline at end of file diff --git a/modules/sub-plugin/docker-cli/README.md b/modules/sub-plugin/docker-cli/README.md new file mode 100644 index 0000000000..d462301e66 --- /dev/null +++ b/modules/sub-plugin/docker-cli/README.md @@ -0,0 +1,37 @@ + +## mac + +> docker run -it -d --name=socat -p 2375:2375 -v /var/run/docker.sock:/var/run/docker.sock bobrik/socat TCP4-LISTEN:2375,fork,reuseaddr UNIX-CONNECT:/var/run/docker.sock +> +> lsof -i:2375 + + +https://www.cnblogs.com/xiaoqi/p/docker-java.html + +https://www.cnblogs.com/boshen-hzb/p/10714414.html + +https://blog.csdn.net/qq_40321119/article/details/107951712 + +https://blog.csdn.net/u012946310/article/details/82315302 + +vim /usr/lib/systemd/system/docker.service + +systemctl daemon-reload && systemctl restart docker + +systemctl status docker.service + +``` +--tlsverify \ +--tlscacert=/home/docker-ca/ca.pem \ +--tlscert=/home/docker-ca/server-cert.pem \ +--tlskey=/home/docker-ca/server-key.pem \ +-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 +``` + +``` +--tlsverify --tlscacert=/home/docker-ca/ca.pem --tlscert=/home/docker-ca/server-cert.pem --tlskey=/home/docker-ca/server-key.pem -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 +``` + +``` +ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --tlsverify --tlscacert=/home/docker/tls-ca/ca.pem --tlscert=/home/docker/tls-ca/server-cert.pem --tlskey=/home/docker/tls-ca/server-key.pem -H tcp://0.0.0.0:2375 +``` \ No newline at end of file diff --git a/modules/sub-plugin/docker-cli/pom.xml b/modules/sub-plugin/docker-cli/pom.xml new file mode 100644 index 0000000000..aef5978b9f --- /dev/null +++ b/modules/sub-plugin/docker-cli/pom.xml @@ -0,0 +1,144 @@ + + + + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + 4.0.0 + docker-cli + plugin-docker-cli + + 8 + 8 + + 3.3.5 + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + com.github.docker-java + docker-java-core + ${docker-java-version} + + + org.bouncycastle + bcpkix-jdk15on + + + org.apache.commons + commons-compress + + + commons-io + commons-io + + + + + org.bouncycastle + bcpkix-jdk15on + 1.70 + + + com.github.docker-java + docker-java-transport-httpclient5 + ${docker-java-version} + + + jna + net.java.dev.jna + + + + + net.java.dev.jna + jna + ${jna-version} + + + + org.dromara.jpom.plugins + ssh-jsch + ${project.version} + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + false + + script/release.xml + + + + + + + + make-assembly + package + + single + + + + + + + + diff --git a/modules/sub-plugin/docker-cli/script/release.xml b/modules/sub-plugin/docker-cli/script/release.xml new file mode 100644 index 0000000000..49d2741f26 --- /dev/null +++ b/modules/sub-plugin/docker-cli/script/release.xml @@ -0,0 +1,44 @@ + + + + release + false + + jar + + + + + / + false + true + runtime + + + com.github.docker-java:docker-java + com.github.docker-java:docker-java-transport-httpclient5 + + + + + + + + ${project.build.directory}/classes + / + + + + diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerCheckPluginImpl.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerCheckPluginImpl.java new file mode 100644 index 0000000000..f4598d1f9e --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerCheckPluginImpl.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.plugins.PluginConfig; +import com.alibaba.fastjson2.JSONObject; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.PingCmd; +import com.github.dockerjava.api.exception.UnauthorizedException; +import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.api.model.AuthResponse; +import com.github.dockerjava.api.model.Info; +import com.github.dockerjava.api.model.Version; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.core.RemoteApiVersion; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.plugin.IDefaultPlugin; +import org.springframework.util.Assert; + +import javax.net.ssl.SSLHandshakeException; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * docker 验证 实现 + * + * @author bwcx_jzy + * @since 2022/1/26 + */ +@PluginConfig(name = "docker-cli:check") +@Slf4j +public class DefaultDockerCheckPluginImpl implements IDefaultPlugin { + + + @Override + public Object execute(Object main, Map parameter) throws Exception { + String type = main.toString(); + switch (type) { + case "certPath": + return this.checkCertPath(parameter); + case "apiVersions": + return getApiVersions(); + case "host": + return this.checkUrl(parameter); + case "ping": + return this.checkPing(parameter); + case "info": + return this.infoCmd(parameter); + case "testLocal": + return this.testLocal(); + case "testAuth": + return this.auth(parameter); + case "hasDependPlugin": + return DockerBuild.hasDependPlugin(parameter); + default: + break; + } + return null; + } + +// private void deleteRuns(Map parameter) { +// String dataPath = DockerUtil.FILE_PATHS[0]; +// String name = (String) parameter.get("name"); +// File dockerfile = FileUtil.file(dataPath, DockerUtil.RUNS_FOLDER, name); +// if (!FileUtil.exist(dockerfile)) { +//// return JsonMessage.getString(400, "文件不存在"); +// return; +// } +// FileUtil.del(dockerfile); +// } +// +// private void updateRuns(Map parameter) { +// String name = (String) parameter.get("name"); +// String content = (String) parameter.get("content"); +// String dataPath = DockerUtil.FILE_PATHS[0]; +// File dockerfile = FileUtil.file(dataPath, DockerUtil.RUNS_FOLDER, name, DockerUtil.DOCKER_FILE); +// FileUtil.writeString(content, dockerfile, StandardCharsets.UTF_8); +// } +// +// private Map listRuns() throws URISyntaxException { +// // // runs/%s/Dockerfile +// Map runs = new HashMap<>(5); +// for (String filePath : DockerUtil.FILE_PATHS) { +// // +// File dockerFileDir = FileUtil.file(filePath, DockerUtil.RUNS_FOLDER); +// if (!FileUtil.exist(dockerFileDir) || FileUtil.isFile(dockerFileDir)) { +// continue; +// } +// File[] files = dockerFileDir.listFiles(); +// if (files == null) { +// continue; +// } +// for (File file : files) { +// File dockerFile = FileUtil.file(file, DockerUtil.DOCKER_FILE); +// if (FileUtil.isFile(dockerFile)) { +// // 不存在才添加 +// runs.putIfAbsent(file.getName(), dockerFile.toURI()); +// } +// } +// } +// if (Objects.isNull(runs.get(DockerUtil.DEFAULT_RUNS))) { +// URL resourceObj = ResourceUtil.getResource(DockerUtil.RUNS_FOLDER + "/" + DockerUtil.DEFAULT_RUNS + "/" + DockerUtil.DOCKER_FILE); +// runs.put(DockerUtil.DEFAULT_RUNS, resourceObj.toURI()); +// } +// return runs; +// } + + private JSONObject auth(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + AuthConfig authConfig = dockerClient.authConfig(); + AuthResponse exec = dockerClient.authCmd().withAuthConfig(authConfig).exec(); + return DockerUtil.toJSON(exec); + } + + private String testLocal() throws IOException { + DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build(); + URI dockerHost = config.getDockerHost(); + String host = dockerHost.toString(); + try (DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(dockerHost).connectionTimeout(Duration.ofSeconds(3)).build()) { + try (PingCmd pingCmd = DockerClientImpl.getInstance(config, httpClient).pingCmd()) { + pingCmd.exec(); + return host; + } + } + } + + /** + * 获取 docker info 信息 + * + * @param parameter 参数 + * @return info + */ + private JSONObject infoCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + Info exec = dockerClient.infoCmd().exec(); + return DockerUtil.toJSON(exec); + } + + /** + * 获取版本信息 + * + * @param parameter 参数 + * @return true 可以通讯 + */ + private Version pullVersion(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + return dockerClient.versionCmd().exec(); + + } + + /** + * 检查 ping docker 超时时间 5 秒 + * + * @param parameter 参数 + * @return true 可以通讯 + */ + private String checkPing(Map parameter) { + try { + DockerClient dockerClient = DockerUtil.get(parameter); + dockerClient.pingCmd().exec(); + return null; + } catch (UnauthorizedException unauthorizedException) { + log.warn(I18nMessageUtil.get("i18n.docker_authorization_failed.8ede"), unauthorizedException.getMessage()); + return I18nMessageUtil.get("i18n.auth_failed.2765") + unauthorizedException.getMessage(); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.check_docker_url_exception.4302"), e.getMessage()); + if (ExceptionUtil.isCausedBy(e, SSLHandshakeException.class)) { + return I18nMessageUtil.get("i18n.ssl_connection_failed.e26c") + e.getMessage(); + } + log.warn(I18nMessageUtil.get("i18n.check_docker_url_exception.4302"), e.getMessage()); + return StrUtil.emptyToDefault(e.getMessage(), I18nMessageUtil.get("i18n.check_docker_exception.a6d1")); + } + } + + /** + * 检查 docker url 是否可用 + * + * @param parameter 参数 + * @return true 可用 + */ + private boolean checkUrl(Map parameter) { + String url = (String) parameter.get("host"); + URI dockerHost; + try { + dockerHost = URI.create(url); + } catch (Exception e) { + return false; + } + if (dockerHost == null) { + return false; + } + switch (dockerHost.getScheme()) { + case "tcp": + case "unix": + case "npipe": + case "ssh": + return true; + default: + return false; + } + } + + /** + * 获取支持到所有 api 版本 + * + * @return list + */ + private List getApiVersions() { + Field[] fields = ReflectUtil.getFields(RemoteApiVersion.class); + return Arrays.stream(fields) + .map(field -> { + boolean aFinal = Modifier.isFinal(field.getModifiers()); + boolean aStatic = Modifier.isStatic(field.getModifiers()); + boolean aPublic = Modifier.isPublic(field.getModifiers()); + if (!aFinal || !aStatic || !aPublic) { + return null; + } + Object fieldValue = ReflectUtil.getFieldValue(null, field); + if (fieldValue instanceof RemoteApiVersion) { + return (RemoteApiVersion) fieldValue; + } + return null; + }) + .filter(apiVersion -> apiVersion != null && apiVersion != RemoteApiVersion.UNKNOWN_VERSION) + .sorted((o1, o2) -> StrUtil.compareVersion(o2.getVersion(), o1.getVersion())).map(apiVersion -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("webVersion", apiVersion.asWebPathPart()); + jsonObject.put("version", apiVersion.getVersion()); + return jsonObject; + }) + .collect(Collectors.toList()); + } + + /** + * 验证 证书是否满足条件 + * + * @param parameter 参数 + * @return 都是文件满足条件 + */ + private boolean checkCertPath(Map parameter) { + String certPath = (String) parameter.get("certPath"); + Assert.hasText(certPath, "certPath is empty"); + File caPemPath = FileUtil.file(certPath, "ca.pem"); + File keyPemPath = FileUtil.file(certPath, "key.pem"); + File certPemPath = FileUtil.file(certPath, "cert.pem"); + return FileUtil.isFile(caPemPath) && FileUtil.isFile(keyPemPath) && FileUtil.isFile(certPemPath); + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerPluginImpl.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerPluginImpl.java new file mode 100644 index 0000000000..79e4bddb2b --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerPluginImpl.java @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.exceptions.InvocationTargetRuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.unit.DataSizeUtil; +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.util.*; +import cn.keepbx.jpom.plugins.PluginConfig; +import com.alibaba.fastjson2.JSONObject; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.InvocationBuilder; +import com.github.dockerjava.core.NameParser; +import lombok.Lombok; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * docker 插件 + * + * @author bwcx_jzy + * @since 2022/1/26 + */ +@PluginConfig(name = "docker-cli") +@Slf4j +public class DefaultDockerPluginImpl implements IDockerConfigPlugin { + + + @Override + public Object execute(Object main, Map parameter) throws Exception { + String type = main.toString(); + if ("build".equals(type)) { + try (DockerBuild dockerBuild = new DockerBuild(parameter, this)) { + return dockerBuild.build(); + } + } + Method method = ReflectUtil.getMethodByName(this.getClass(), type + "Cmd"); + Assert.notNull(method, I18nMessageUtil.get("i18n.unsupported_type_with_colon.1050") + type); + try { + return ReflectUtil.invoke(this, method, parameter); + } catch (InvocationTargetRuntimeException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof InvocationTargetException) { + InvocationTargetException invocationTargetException = (InvocationTargetException) cause; + throw Lombok.sneakyThrow(invocationTargetException.getTargetException()); + } + throw Lombok.sneakyThrow(cause); + } + } + + /** + * 裁剪 + * https://blog.csdn.net/zhanremo3062/article/details/120860327 + * + * @param parameter 参数 + * @return 回收空间 + */ + private Long pruneCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String pruneTypeStr = (String) parameter.get("pruneType"); + + PruneType pruneType = EnumUtil.fromString(PruneType.class, pruneTypeStr, null); + Assert.notNull(pruneType, I18nMessageUtil.get("i18n.unknown_prune_type.0931")); + String until = (String) parameter.get("until"); + String labels = (String) parameter.get("labels"); + String dangling = (String) parameter.get("dangling"); + PruneCmd pruneCmd = dockerClient.pruneCmd(pruneType); + Opt.ofBlankAble(dangling).map(s -> Convert.toBool(s, true)).ifPresent(pruneCmd::withDangling); + Opt.ofBlankAble(until).ifPresent(pruneCmd::withUntilFilter); + Opt.ofBlankAble(labels).map(s -> StrUtil.splitToArray(s, StrUtil.COMMA)).ifPresent(pruneCmd::withLabelFilter); + PruneResponse pruneResponse = pruneCmd.exec(); + return pruneResponse.getSpaceReclaimed(); + } + + private Map statsCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String containerId = (String) parameter.get("containerId"); + List split = StrUtil.split(containerId, StrUtil.COMMA); + return split.stream().map(s -> { + Statistics statistics = dockerClient.statsCmd(s).exec(new InvocationBuilder.AsyncResultCallback() { + @SneakyThrows + @Override + public void onNext(Statistics object) { + super.onNext(object); + super.close(); + } + }).awaitResult(); + return new Tuple(s, DockerUtil.toJSON(statistics)); + }).collect(Collectors.toMap(tuple -> tuple.get(0), tuple -> tuple.get(1))); + } + + private JSONObject updateContainerCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String containerId = (String) parameter.get("containerId"); + UpdateContainerCmd updateContainerCmd = dockerClient.updateContainerCmd(containerId); + // + Optional.ofNullable(parameter.get("cpusetCpus")) + .map(StrUtil::toStringOrNull) + .ifPresent(updateContainerCmd::withCpusetCpus); + + Optional.ofNullable(parameter.get("cpusetMems")) + .map(StrUtil::toStringOrNull) + .ifPresent(updateContainerCmd::withCpusetMems); + + Optional.ofNullable(parameter.get("cpuPeriod")) + .map(Convert::toInt) + .ifPresent(updateContainerCmd::withCpuPeriod); + + Optional.ofNullable(parameter.get("cpuQuota")) + .map(Convert::toInt) + .ifPresent(updateContainerCmd::withCpuQuota); + + Optional.ofNullable(parameter.get("cpuShares")) + .map(Convert::toInt) + .ifPresent(updateContainerCmd::withCpuShares); + + Optional.ofNullable(parameter.get("blkioWeight")) + .map(Convert::toInt) + .ifPresent(updateContainerCmd::withBlkioWeight); + + Optional.ofNullable(parameter.get("memoryReservation")) + .map(StrUtil::toStringOrNull) + .map(s -> { + if (StrUtil.isEmpty(s)) { + return null; + } + return DataSizeUtil.parse(s); + }) + .ifPresent(updateContainerCmd::withMemoryReservation); + + Optional.ofNullable(parameter.get("memory")) + .map(StrUtil::toStringOrNull) + .map(s -> { + if (StrUtil.isEmpty(s)) { + return null; + } + return DataSizeUtil.parse(s); + }) + .ifPresent(updateContainerCmd::withMemory); + + // updateContainerCmd.withKernelMemory(DataSizeUtil.parse("10M")); + + Optional.ofNullable(parameter.get("memorySwap")) + .map(StrUtil::toStringOrNull) + .map(s -> { + if (StrUtil.isEmpty(s)) { + return null; + } + return DataSizeUtil.parse(s); + }) + .ifPresent(updateContainerCmd::withMemorySwap); + + UpdateContainerResponse updateContainerResponse = updateContainerCmd.exec(); + return DockerUtil.toJSON(updateContainerResponse); + } + + private JSONObject inspectContainerCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String containerId = (String) parameter.get("containerId"); + InspectContainerResponse containerResponse = dockerClient.inspectContainerCmd(containerId).withSize(true).exec(); + return DockerUtil.toJSON(containerResponse); + } + + private List listNetworksCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListNetworksCmd listNetworksCmd = dockerClient.listNetworksCmd(); + + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(name)) { + listNetworksCmd.withNameFilter(name); + } + String id = (String) parameter.get("id"); + if (StrUtil.isNotEmpty(id)) { + listNetworksCmd.withIdFilter(id); + } + List networks = listNetworksCmd.exec(); + networks = ObjectUtil.defaultIfNull(networks, new ArrayList<>()); + return networks.stream().map(DockerUtil::toJSON).collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + public void pullImageCmd(Map parameter) throws InterruptedException { + DockerClient dockerClient = DockerUtil.get(parameter); + + Consumer logConsumer = (Consumer) parameter.get("logConsumer"); + String repositoryStr = (String) parameter.get("repository"); + Assert.hasText(repositoryStr, I18nMessageUtil.get("i18n.image_name_required.ab44")); + NameParser.ReposTag reposTag = NameParser.parseRepositoryTag(repositoryStr); + // 解析 tag + String tag = reposTag.tag; + tag = StrUtil.emptyToDefault(tag, "latest"); + logConsumer.accept(StrUtil.format("start pull {}:{}", reposTag.repos, tag)); + PullImageCmd pullImageCmd = dockerClient.pullImageCmd(reposTag.repos) + .withTag(tag) + .withAuthConfig(dockerClient.authConfig()); + pullImageCmd.exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(PullResponseItem object) { + String responseItem = DockerUtil.parseResponseItem(object); + logConsumer.accept(responseItem); + } + + }).awaitCompletion(); + } + + + @SuppressWarnings(value = {"unchecked"}) + private void createContainerCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String imageId = (String) parameter.get("imageId"); + String name = (String) parameter.get("name"); + String exposedPorts = (String) parameter.get("exposedPorts"); + String volumes = (String) parameter.get("volumes"); + String networkMode = (String) parameter.get("networkMode"); + Object autorunStr = parameter.get("autorun"); + Object privileged = parameter.get("privileged"); + String restartPolicy = (String) parameter.get("restartPolicy"); + Map env = (Map) parameter.get("env"); + Map storageOpt = (Map) parameter.get("storageOpt"); + String labels = (String) parameter.get("labels"); + String runtime = (String) parameter.get("runtime"); + List extraHosts = (List) parameter.get("extraHosts"); + + // + CreateContainerCmd containerCmd = dockerClient.createContainerCmd(imageId); + containerCmd.withName(name); + Opt.ofBlankAble(labels) + .map(s -> UrlQuery.of(s, CharsetUtil.CHARSET_UTF_8)) + .map(UrlQuery::getQueryMap) + .map((Function, Map>) map -> { + HashMap labelMap = MapUtil.newHashMap(); + for (Map.Entry entry : map.entrySet()) { + labelMap.put(StrUtil.toString(entry.getKey()), StrUtil.toString(entry.getValue())); + } + return labelMap; + }) + .ifPresent(containerCmd::withLabels); + String hostname = (String) parameter.get("hostname"); + Opt.ofBlankAble(hostname).ifPresent(containerCmd::withHostName); + HostConfig hostConfig = HostConfig.newHostConfig(); + Opt.ofBlankAble(runtime).ifPresent(hostConfig::withRuntime); + // + Opt.ofBlankAble(extraHosts).ifPresent(list -> { + String[] array = list.stream().filter(StrUtil::isNotEmpty).toArray(String[]::new); + hostConfig.withExtraHosts(array); + }); + List exposedPortList = new ArrayList<>(); + if (StrUtil.isNotEmpty(exposedPorts)) { + List portBindings = StrUtil.splitTrim(exposedPorts, StrUtil.COMMA) + .stream() + .map(PortBinding::parse) + .peek(portBinding -> exposedPortList.add(portBinding.getExposedPort())) + .collect(Collectors.toList()); + hostConfig.withPortBindings(portBindings); + } + if (StrUtil.isNotEmpty(volumes)) { + List binds = StrUtil.splitTrim(volumes, StrUtil.COMMA) + .stream() + .map(Bind::parse) + .collect(Collectors.toList()); + hostConfig.withBinds(binds); + } + Opt.ofBlankAble(networkMode).ifPresent(hostConfig::withNetworkMode); + Optional.ofNullable(privileged).map(o -> Convert.toBool(o, false)).ifPresent(hostConfig::withPrivileged); + Opt.ofBlankAble(restartPolicy).map(RestartPolicy::parse).ifPresent(hostConfig::withRestartPolicy); + // 环境变量 + if (env != null) { + List envList = env.entrySet() + .stream() + .map(entry -> StrUtil.format("{}={}", entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + containerCmd.withEnv(envList); + } + Optional.ofNullable(storageOpt).map(map -> { + if (MapUtil.isEmpty(map)) { + // 空参数不能传入,避免低版本不支持 + return null; + } + return map; + }).ifPresent(hostConfig::withStorageOpt); + + // 命令 + List commands = (List) parameter.get("commands"); + Optional.ofNullable(commands).ifPresent(strings -> { + List list = strings.stream() + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toList()); + if (CollUtil.isNotEmpty(list)) { + containerCmd.withCmd(list); + } + }); + + containerCmd.withHostConfig(hostConfig).withExposedPorts(exposedPortList); + CreateContainerResponse containerResponse = containerCmd.exec(); + // + boolean autorun = Convert.toBool(autorunStr, false); + if (autorun) { + // + dockerClient.startContainerCmd(containerResponse.getId()).exec(); + } + + } + + private JSONObject inspectImageCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String imageId = (String) parameter.get("imageId"); + InspectImageCmd inspectImageCmd = dockerClient.inspectImageCmd(imageId); + InspectImageResponse inspectImageResponse = inspectImageCmd.exec(); + return DockerUtil.toJSON(inspectImageResponse); + } + + @SuppressWarnings("unchecked") + private void pushImageCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + Consumer logConsumer = (Consumer) parameter.get("logConsumer"); + String repository = (String) parameter.get("repository"); + try { + dockerClient.pushImageCmd(repository).exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(PushResponseItem object) { + String responseItem = DockerUtil.parseResponseItem(object); + logConsumer.accept(responseItem); + } + }).awaitCompletion(); + } catch (InterruptedException e) { + logConsumer.accept(I18nMessageUtil.get("i18n.push_image_interrupted.6377") + e); + } + } + + /** + * http://edu.jb51.net/docker/docker-command-manual-build.html + * 构建镜像 + * + * @param parameter 参数 + * @return 构建是否成功 + */ + @SuppressWarnings("unchecked") + private boolean buildImageCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + Consumer logConsumer = (Consumer) parameter.get("logConsumer"); + File dockerfile = (File) parameter.get("Dockerfile"); + File baseDirectory = (File) parameter.get("baseDirectory"); + String tags = (String) parameter.get("tags"); + String buildArgs = (String) parameter.get("buildArgs"); + Object pull = parameter.get("pull"); + Object noCache = parameter.get("noCache"); + String labels = (String) parameter.get("labels"); + Map env = (Map) parameter.get("env"); + InvocationBuilder.AsyncResultCallback callback = null; + try { + AuthConfigurations authConfigurations = new AuthConfigurations(); + authConfigurations.addConfig(dockerClient.authConfig()); + + BuildImageCmd buildImageCmd = dockerClient.buildImageCmd(); + buildImageCmd + .withBaseDirectory(baseDirectory) + .withDockerfile(dockerfile) + .withBuildAuthConfigs(authConfigurations) + .withTags(CollUtil.newHashSet(StrUtil.splitTrim(tags, StrUtil.COMMA))); + // 添加构建参数 + UrlQuery query = UrlQuery.of(buildArgs, CharsetUtil.CHARSET_UTF_8); + query.getQueryMap() + .forEach((key, value) -> { + String valueStr = StrUtil.toString(value); + valueStr = StringUtil.formatStrByMap(valueStr, env); + buildImageCmd.withBuildArg(StrUtil.toString(key), valueStr); + }); + // 标签 + UrlQuery labelsQuery = UrlQuery.of(labels, CharsetUtil.CHARSET_UTF_8); + HashMap labelMap = MapUtil.newHashMap(); + labelsQuery.getQueryMap().forEach((key, value) -> { + String valueStr = StrUtil.toString(value); + valueStr = StringUtil.formatStrByMap(valueStr, env); + labelMap.put(StrUtil.toString(key), valueStr); + }); + buildImageCmd.withLabels(labelMap); + // + Optional.ofNullable(pull).map(Convert::toBool).ifPresent(buildImageCmd::withPull); + Optional.ofNullable(noCache).map(Convert::toBool).ifPresent(buildImageCmd::withNoCache); + // + final boolean[] hasError = {false}; + callback = buildImageCmd.exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(BuildResponseItem object) { + String responseItem = DockerUtil.parseResponseItem(object); + logConsumer.accept(responseItem); + hasError[0] = hasError[0] || object.isErrorIndicated(); + } + }).awaitCompletion(); + return !hasError[0]; + } catch (InterruptedException e) { + logConsumer.accept(I18nMessageUtil.get("i18n.container_build_interrupted.a17b") + e); + return false; + } finally { + IoUtil.close(callback); + } + } + + @SuppressWarnings("unchecked") + private void execCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + Consumer logConsumer = (Consumer) parameter.get("logConsumer"); + Consumer errorConsumer = (Consumer) parameter.get("errorConsumer"); + InvocationBuilder.AsyncResultCallback callback = null; + try { + String containerId = (String) parameter.get("containerId"); + Charset charset = (Charset) parameter.get("charset"); + InputStream stdin1 = (InputStream) parameter.get("stdin"); + // + ExecCreateCmd execCreateCmd = dockerClient.execCreateCmd(containerId); + execCreateCmd.withAttachStdout(true) + .withAttachStdin(true) + .withAttachStderr(true) + .withTty(true) + .withCmd("/bin/bash"); + ExecCreateCmdResponse exec = execCreateCmd.exec(); + // + String execId = exec.getId(); + ExecStartCmd execStartCmd = dockerClient.execStartCmd(execId); + execStartCmd.withDetach(false).withTty(true).withStdIn(stdin1); + logConsumer.accept(StrUtil.format("CALLBACK_EXECID:{}", execId)); + callback = execStartCmd.exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame frame) { + String s = new String(frame.getPayload(), charset); + logConsumer.accept(s); + } + }).awaitCompletion(); + } catch (InterruptedException e) { + errorConsumer.accept(I18nMessageUtil.get("i18n.container_cli_interrupted.b67f") + e); + } finally { + errorConsumer.accept("exit"); + IoUtil.close(callback); + } + } + + /** + * 检查 终端 + * + * @param parameter 参数 + */ + private void inspectExecCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String execId = (String) parameter.get("execId"); + // + InspectExecCmd inspectExecCmd = dockerClient.inspectExecCmd(execId); + inspectExecCmd.exec().isRunning(); + } + + /** + * 中断 终端 + * + * @param parameter 参数 + */ + private void resizeExecCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String execId = (String) parameter.get("execId"); + // + ResizeExecCmd resizeExecCmd = dockerClient.resizeExecCmd(execId); + Integer sizeHeight = (Integer) parameter.get("sizeHeight"); + Integer sizeWidth = (Integer) parameter.get("sizeWidth"); + resizeExecCmd.withSize(sizeHeight, sizeWidth).exec(); + } + + @SuppressWarnings("unchecked") + private void logContainerCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String uuid = (String) parameter.get("uuid"); + Consumer consumer = (Consumer) parameter.get("consumer"); + try { + String containerId = (String) parameter.get("containerId"); + Charset charset = (Charset) parameter.get("charset"); + Integer tail = (Integer) parameter.get("tail"); + Boolean timestamps = Convert.toBool(parameter.get("timestamps")); + DockerClientUtil.pullLog(dockerClient, containerId, timestamps, tail, charset, consumer, autoCloseable -> DockerUtil.putClose(uuid, autoCloseable)); + } catch (InterruptedException e) { + consumer.accept(I18nMessageUtil.get("i18n.get_container_log_interrupted_message.83a5") + e); + } finally { + DockerUtil.close(uuid); + } + } + + private List listVolumesCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListVolumesCmd listVolumesCmd = dockerClient.listVolumesCmd(); + Boolean dangling = Convert.toBool(parameter.get("dangling"), false); + if (dangling) { + listVolumesCmd.withDanglingFilter(true); + } + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(name)) { + listVolumesCmd.withFilter("name", CollUtil.newArrayList(name)); + } + + ListVolumesResponse exec = listVolumesCmd.exec(); + List volumes = exec.getVolumes(); + volumes = ObjectUtil.defaultIfNull(volumes, new ArrayList<>()); + return volumes.stream().map((Function) inspectVolumeResponse -> { + InspectVolumeCmd inspectVolumeCmd = dockerClient.inspectVolumeCmd(inspectVolumeResponse.getName()); + return inspectVolumeCmd.exec(); + }).map(DockerUtil::toJSON).collect(Collectors.toList()); + + } + + private void removeVolumeCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String volumeName = (String) parameter.get("volumeName"); + dockerClient.removeVolumeCmd(volumeName).exec(); + + } + + + private List listImagesCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListImagesCmd listImagesCmd = dockerClient.listImagesCmd(); + listImagesCmd.withShowAll(Convert.toBool(parameter.get("showAll"), true)); + listImagesCmd.withDanglingFilter(Convert.toBool(parameter.get("dangling"), false)); + + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(name)) { + listImagesCmd.withImageNameFilter(name); + } + List exec = listImagesCmd.exec(); + exec = ObjectUtil.defaultIfNull(exec, new ArrayList<>()); + return exec.stream().map(DockerUtil::toJSON).collect(Collectors.toList()); + } + + private void removeImageCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String imageId = (String) parameter.get("imageId"); + dockerClient.removeImageCmd(imageId).withForce(true).exec(); + } + + private void batchRemoveCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String[] imagesIds = (String[]) parameter.get("imagesIds"); + int successCount = 0, failCount = 0; + // 已经使用的镜像禁止删除 + for (String imageId : imagesIds) { + try { + dockerClient.removeImageCmd(imageId).withForce(false).exec(); + successCount++; + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.delete_container_exception.9ad8"), e); + } + } + failCount = imagesIds.length - successCount; + } + + /** + * 不包含 docker compose + * + * @param parameter 参数 + * @return list + */ + private List listContainerCmd(Map parameter) { + List list = this.listContainerByLabelCmd(parameter, null); + String composeLabel = "com.docker.compose.project"; + return list.stream() + .filter(jsonObject -> { + JSONObject labels = jsonObject.getJSONObject("labels"); + String project = MapUtil.get(labels, composeLabel, String.class); + return project == null; + }) + .collect(Collectors.toList()); + } + + private List listContainerByLabelCmd(Map parameter, String label) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListContainersCmd listContainersCmd = dockerClient.listContainersCmd(); + listContainersCmd.withShowAll(Convert.toBool(parameter.get("showAll"), true)); + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(name)) { + listContainersCmd.withNameFilter(CollUtil.newArrayList(name)); + } + String containerId = (String) parameter.get("containerId"); + if (StrUtil.isNotEmpty(containerId)) { + listContainersCmd.withIdFilter(CollUtil.newArrayList(containerId)); + } + + Opt.ofBlankAble(label).ifPresent(s -> { + // 只筛选 docker compose + listContainersCmd.withLabelFilter(CollUtil.newArrayList(s)); + }); + String imageId = (String) parameter.get("imageId"); + List exec = listContainersCmd.exec(); + exec = ObjectUtil.defaultIfNull(exec, new ArrayList<>()); + return exec.stream() + .map(DockerUtil::toJSON) + .filter(jsonObject -> { + if (StrUtil.isEmpty(imageId)) { + return true; + } + String imageId1 = jsonObject.getString("imageId"); + return StrUtil.contains(imageId1, imageId); + }) + .collect(Collectors.toList()); + } + + /** + * 不包含 docker compose + * + * @param parameter 参数 + * @return list + */ + private List listComposeContainerCmd(Map parameter) { + String composeLabel = "com.docker.compose.project"; + List list = this.listContainerByLabelCmd(parameter, composeLabel); + // + Map> map = CollStreamUtil.groupKeyValue(list, jsonObject -> { + JSONObject labels = jsonObject.getJSONObject("labels"); + return Optional.ofNullable(labels) + .map(jsonObject1 -> jsonObject1.getString(composeLabel)) + .orElse("null"); + }, jsonObject -> jsonObject); + // + return map.entrySet().stream() + .map(stringListEntry -> { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", stringListEntry.getKey()); + jsonObject.put("child", stringListEntry.getValue()); + return jsonObject; + }) + .collect(Collectors.toList()); + } + + private void restartContainerCmd(Map parameter) { + String containerId = (String) parameter.get("containerId"); + DockerClient dockerClient = DockerUtil.get(parameter); + + dockerClient.restartContainerCmd(containerId).exec(); + } + + private void startContainerCmd(Map parameter) { + String containerId = (String) parameter.get("containerId"); + DockerClient dockerClient = DockerUtil.get(parameter); + + dockerClient.startContainerCmd(containerId).exec(); + } + + private void stopContainerCmd(Map parameter) { + String containerId = (String) parameter.get("containerId"); + DockerClient dockerClient = DockerUtil.get(parameter); + + dockerClient.stopContainerCmd(containerId).exec(); + } + + /** + * 删除容器 + * + * @param parameter 参数 + */ + private void removeContainerCmd(Map parameter) { + String containerId = (String) parameter.get("containerId"); + DockerClient dockerClient = DockerUtil.get(parameter); + + DockerClientUtil.removeContainerCmd(dockerClient, containerId); + + } + + /** + * 关闭异步资源 + * + * @param parameter 参数 + */ + private void closeAsyncResourceCmd(Map parameter) { + String uuid = (String) parameter.get("uuid"); + DockerUtil.close(uuid); + } + + /** + * 导出镜像 + * + * @param parameter 参数 + * @return 镜像流 + */ + private Tuple saveImageCmd(Map parameter) { + try { + String imageId = (String) parameter.get("imageId"); + DockerClient dockerClient = DockerUtil.get(parameter); + // + InspectImageResponse imageResponse = dockerClient.inspectImageCmd(imageId).exec(); + List repoTags = imageResponse.getRepoTags(); + String arch = imageResponse.getArch(); + String nameTag = CollUtil.getFirst(repoTags); + // xxx/xxx 只保留最后的名称 + String name = CollUtil.getLast(StrUtil.splitTrim(nameTag, StrUtil.SLASH)); + // els-app:1.0.106 冒号替换 + name = StrUtil.replace(name, StrUtil.COLON, StrUtil.DASHED); + InputStream inputStream = dockerClient.saveImageCmd(nameTag).exec(); + return new Tuple(inputStream, name + "-" + arch + ".tar"); + } catch (com.github.dockerjava.api.exception.NotFoundException e) { + log.debug("{}", e.getMessage()); + return null; + } + } + + /** + * 导入镜像 + * + * @param parameter 参数 + */ + private void loadImageCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + InputStream inputStream = (InputStream) parameter.get("stream"); + // + dockerClient.loadImageCmd(inputStream).exec(); + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerSwarmPluginImpl.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerSwarmPluginImpl.java new file mode 100644 index 0000000000..47cb56da59 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DefaultDockerSwarmPluginImpl.java @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.unit.DataSize; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.plugins.PluginConfig; +import com.alibaba.fastjson2.JSONObject; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.command.RemoveSwarmNodeCmdImpl; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.plugin.IDefaultPlugin; +import org.springframework.util.Assert; + +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * docker swarm + * + * @author bwcx_jzy + * @since 2022/2/13 + */ +@PluginConfig(name = "docker-cli:swarm") +@Slf4j +public class DefaultDockerSwarmPluginImpl implements IDefaultPlugin { + + @Override + public Object execute(Object main, Map parameter) { + String type = main.toString(); + switch (type) { + case "inSpectSwarm": + return this.inSpectSwarmCmd(parameter); + case "tryInitializeSwarm": + return this.tryInitializeSwarmCmd(parameter); + case "joinSwarm": + this.joinSwarmCmd(parameter); + return null; + case "listSwarmNodes": + return this.listSwarmNodesCmd(parameter); + case "leaveSwarm": + this.leaveSwarmCmd(parameter); + return null; + case "updateSwarmNode": + this.updateSwarmNodeCmd(parameter); + return null; + case "removeSwarmNode": + this.removeSwarmNodeCmd(parameter); + return null; + case "listServices": + return this.listServicesCmd(parameter); + case "listTasks": + return this.listTasksCmd(parameter); + case "removeService": + this.removeServiceCmd(parameter); + return null; + case "updateService": + this.updateServiceCmd(parameter); + return null; + case "updateServiceImage": + this.updateServiceImage(parameter); + return null; + case "logService": + this.logServiceCmd(parameter); + return null; + case "logTask": + this.logTaskCmd(parameter); + return null; + default: + break; + } + return null; + } + + private void logTaskCmd(Map parameter) { + this.logServiceCmd(parameter, (String) parameter.get("taskId"), "task"); + } + + private void logServiceCmd(Map parameter) { + this.logServiceCmd(parameter, (String) parameter.get("serviceId"), "service"); + } + + private void logServiceCmd(Map parameter, String id, String type) { + DockerClient dockerClient = DockerUtil.get(parameter); + Consumer consumer = (Consumer) parameter.get("consumer"); + String uuid = (String) parameter.get("uuid"); + try { + + LogSwarmObjectCmd logSwarmObjectCmd = StrUtil.equalsIgnoreCase(type, "Service") ? dockerClient.logServiceCmd(id) : dockerClient.logTaskCmd(id); + + Charset charset = (Charset) parameter.get("charset"); + Integer tail = (Integer) parameter.get("tail"); + // 获取日志 + if (tail != null && tail > 0) { + logSwarmObjectCmd.withTail(tail); + } + //String since = (String) parameter.get("since"); + // Opt.ofBlankAble(since).ifPresent(s -> logSwarmObjectCmd.withSince(s)); + Boolean timestamps = Convert.toBool(parameter.get("timestamps")); + logSwarmObjectCmd.withTimestamps(timestamps); + ResultCallback.Adapter exec = logSwarmObjectCmd + .withDetails(true) + .withStderr(true) + .withFollow(true) + .withStdout(true) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame object) { + byte[] payload = object.getPayload(); + if (payload == null) { + return; + } + String s = new String(payload, charset); + consumer.accept(s); + } + }); + // 添加到缓存中 + DockerUtil.putClose(uuid, exec); + exec.awaitCompletion(); + } catch (InterruptedException e) { + consumer.accept(I18nMessageUtil.get("i18n.get_container_log_interrupted_message.83a5") + e); + } finally { + DockerUtil.close(uuid); + } + } + + private ServiceSpec intServiceSpec(DockerClient dockerClient, String serviceId) { + if (StrUtil.isEmpty(serviceId)) { + return new ServiceSpec(); + } + // 读取之前的信息-保留之前的信息-否则会全部替换 + InspectServiceCmd inspectServiceCmd = dockerClient.inspectServiceCmd(serviceId); + Service service = inspectServiceCmd.exec(); + ServiceSpec spec = service.getSpec(); + return ObjectUtil.defaultIfNull(spec, new ServiceSpec()); + } + + public void updateServiceImage(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String serviceId = (String) parameter.get("serviceId"); + String image = (String) parameter.get("image"); + // + InspectServiceCmd inspectServiceCmd = dockerClient.inspectServiceCmd(serviceId); + Service service = inspectServiceCmd.exec(); + ServiceSpec spec = service.getSpec(); + Assert.notNull(spec, I18nMessageUtil.get("i18n.service_info_incomplete.968d")); + TaskSpec taskTemplate = spec.getTaskTemplate(); + Assert.notNull(taskTemplate, I18nMessageUtil.get("i18n.service_info_incomplete_with_code1.30f4")); + ContainerSpec templateContainerSpec = taskTemplate.getContainerSpec(); + Assert.notNull(templateContainerSpec, I18nMessageUtil.get("i18n.service_info_incomplete_with_code2.e9ca")); + templateContainerSpec.withImage(image); + // + UpdateServiceCmd updateServiceCmd = dockerClient.updateServiceCmd(serviceId, spec); + ResourceVersion version = service.getVersion(); + Assert.notNull(version, I18nMessageUtil.get("i18n.service_info_incomplete_with_code3.8612")); + updateServiceCmd.withVersion(version.getIndex()); + updateServiceCmd.exec(); + } + + /** + * 更新 服务,如果不存在 id 则创建。需要传人版本号 + * + * @param parameter 测试 + */ + public void updateServiceCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String serviceId = (String) parameter.get("serviceId"); + ServiceSpec serviceSpec = this.intServiceSpec(dockerClient, serviceId); + String name = (String) parameter.get("name"); + serviceSpec.withName(name); + { + String mode = (String) parameter.get("mode"); + ServiceMode serviceMode = EnumUtil.fromString(ServiceMode.class, mode); + ServiceModeConfig serviceModeConfig = new ServiceModeConfig(); + if (serviceMode == ServiceMode.GLOBAL) { + serviceModeConfig.withGlobal(new ServiceGlobalModeOptions()); + } else if (serviceMode == ServiceMode.REPLICATED) { + Object replicas = parameter.get("replicas"); + ServiceReplicatedModeOptions serviceReplicatedModeOptions = new ServiceReplicatedModeOptions(); + serviceReplicatedModeOptions.withReplicas(Convert.toInt(replicas, 1)); + serviceModeConfig.withReplicated(serviceReplicatedModeOptions); + } + serviceSpec.withMode(serviceModeConfig); + } + { + TaskSpec taskSpec = ObjectUtil.defaultIfNull(serviceSpec.getTaskTemplate(), new TaskSpec()); + // + ContainerSpec containerSpec = this.buildContainerSpec(parameter, taskSpec.getContainerSpec()); + taskSpec.withContainerSpec(containerSpec); + // + Map> resources = (Map>) parameter.get("resources"); + + if (MapUtil.isNotEmpty(resources)) { + ResourceRequirements resourceRequirements = new ResourceRequirements(); + ResourceSpecs limitsResourceSpecs = this.buildResourceSpecs(resources.get("limits")); + if (limitsResourceSpecs != null) { + resourceRequirements.withLimits(limitsResourceSpecs); + } + ResourceSpecs reservationsResourceSpecs = this.buildResourceSpecs(resources.get("reservations")); + if (reservationsResourceSpecs != null) { + resourceRequirements.withReservations(reservationsResourceSpecs); + } + if (ObjectUtil.isAllEmpty(resourceRequirements.getLimits(), resourceRequirements.getReservations())) { + taskSpec.withResources(null); + } else { + taskSpec.withResources(resourceRequirements); + } + } + serviceSpec.withTaskTemplate(taskSpec); + } + { + EndpointSpec endpointSpec = this.buildEndpointSpec(parameter); + serviceSpec.withEndpointSpec(endpointSpec); + } + { + Map update = (Map) parameter.get("update"); + UpdateConfig updateConfig = this.buildUpdateConfig(update); + serviceSpec.withUpdateConfig(updateConfig); + Map rollback = (Map) parameter.get("rollback"); + UpdateConfig rollbackConfig = this.buildUpdateConfig(rollback); + serviceSpec.withRollbackConfig(rollbackConfig); + } + + if (StrUtil.isNotEmpty(serviceId)) { + Object version = parameter.get("version"); + UpdateServiceCmd updateServiceCmd = dockerClient.updateServiceCmd(serviceId, serviceSpec); + updateServiceCmd.withVersion(Convert.toLong(version, 0L)); + updateServiceCmd.exec(); + } else { + CreateServiceCmd createServiceCmd = dockerClient.createServiceCmd(serviceSpec).withAuthConfig(dockerClient.authConfig()); + createServiceCmd.exec(); + } + } + + private ResourceSpecs buildResourceSpecs(Map map) { + if (MapUtil.isNotEmpty(map)) { + ResourceSpecs resourceSpecs = new ResourceSpecs(); + Object nanoCpus = map.get("nanoCPUs"); + if (nanoCpus != null) { + String text = nanoCpus.toString(); + if (StrUtil.isNotEmpty(text)) { + resourceSpecs.withNanoCPUs(Convert.toLong(nanoCpus, 1L)); + } + } + Object memoryBytes = map.get("memoryBytes"); + if (memoryBytes != null) { + String text = memoryBytes.toString(); + if (StrUtil.isNotEmpty(text)) { + DataSize dataSize = DataSize.parse(text); + resourceSpecs.withMemoryBytes(dataSize.toBytes()); + } + } + if (ObjectUtil.isAllEmpty(resourceSpecs.getNanoCPUs(), resourceSpecs.getMemoryBytes())) { + return null; + } + return resourceSpecs; + } + return null; + } + + private EndpointSpec buildEndpointSpec(Map parameter) { + String endpointResolutionModeStr = (String) parameter.get("endpointResolutionMode"); + EndpointResolutionMode endpointResolutionMode = EnumUtil.fromString(EndpointResolutionMode.class, endpointResolutionModeStr); + EndpointSpec endpointSpec = new EndpointSpec(); + endpointSpec.withMode(endpointResolutionMode); + Collection> exposedPorts = (Collection) parameter.get("exposedPorts"); + if (CollUtil.isNotEmpty(exposedPorts)) { + List portConfigs = exposedPorts.stream() + .map(stringStringMap -> { + Object port = stringStringMap.get("targetPort"); + Object publicPort = stringStringMap.get("publishedPort"); + if (ObjectUtil.hasEmpty(port, publicPort)) { + return null; + } + PortConfig portConfig = new PortConfig(); + String mode = (String) stringStringMap.get("publishMode"); + PortConfig.PublishMode publishMode = EnumUtil.fromString(PortConfig.PublishMode.class, mode); + portConfig.withPublishMode(publishMode); + String scheme = (String) stringStringMap.get("protocol"); + PortConfigProtocol protocol = EnumUtil.fromString(PortConfigProtocol.class, scheme); + portConfig.withProtocol(protocol); + portConfig.withTargetPort(Convert.toInt(port, 0)); + portConfig.withPublishedPort(Convert.toInt(publicPort, 0)); + return portConfig; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + endpointSpec.withPorts(portConfigs); + } + return endpointSpec; + } + + private ContainerSpec buildContainerSpec(Map parameter, ContainerSpec oldContainerSpec) { + String image = (String) parameter.get("image"); + ContainerSpec containerSpec = ObjectUtil.defaultIfNull(oldContainerSpec, new ContainerSpec()); + String hostname = (String) parameter.get("hostname"); + Opt.ofBlankAble(hostname).ifPresent(containerSpec::withHostname); + containerSpec.withImage(image); + // + Collection> args = (Collection) parameter.get("args"); + if (CollUtil.isNotEmpty(args)) { + List value = args.stream() + .map(stringStringMap -> stringStringMap.get("value")) + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toList()); + containerSpec.withArgs(value); + } + Collection> envs = (Collection) parameter.get("envs"); + if (CollUtil.isNotEmpty(envs)) { + List value = envs.stream() + .map(stringStringMap -> { + String name1 = stringStringMap.get("name"); + String value1 = stringStringMap.get("value"); + if (StrUtil.isEmpty(name1)) { + return null; + } + return StrUtil.format("{}={}", name1, value1); + }) + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toList()); + containerSpec.withEnv(value); + } + Collection> commands = (Collection) parameter.get("commands"); + if (CollUtil.isNotEmpty(commands)) { + List value = commands.stream() + .map(stringStringMap -> stringStringMap.get("value")) + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toList()); + containerSpec.withCommand(value); + } + // + Collection> volumes = (Collection>) parameter.get("volumes"); + if (CollUtil.isNotEmpty(volumes)) { + List value = volumes.stream() + .map(stringStringMap -> { + String source = stringStringMap.get("source"); + String target = stringStringMap.get("target"); + if (StrUtil.hasBlank(source, target)) { + return null; + } + String type = stringStringMap.get("type"); + MountType mountType = EnumUtil.fromString(MountType.class, type); + Mount mount = new Mount(); + mount.withSource(source); + mount.withTarget(target); + mount.withType(mountType); + return mount; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + containerSpec.withMounts(value); + } + return containerSpec; + } + + private UpdateConfig buildUpdateConfig(Map update) { + if (MapUtil.isNotEmpty(update)) { + UpdateConfig updateConfig = new UpdateConfig(); + String failureAction = (String) update.get("failureAction"); + if (StrUtil.isNotEmpty(failureAction)) { + UpdateFailureAction updateFailureAction = EnumUtil.fromString(UpdateFailureAction.class, failureAction); + updateConfig.withFailureAction(updateFailureAction); + } + String order = (String) update.get("order"); + if (StrUtil.isNotEmpty(order)) { + UpdateOrder updateOrder = EnumUtil.fromString(UpdateOrder.class, order); + updateConfig.withOrder(updateOrder); + } + Object parallelism = update.get("parallelism"); + if (parallelism != null) { + updateConfig.withParallelism(Convert.toLong(parallelism)); + } + Object delay = update.get("delay"); + if (delay != null) { + updateConfig.withDelay(Convert.toLong(delay)); + } + Object maxFailureRatio = update.get("maxFailureRatio"); + if (maxFailureRatio != null) { + updateConfig.withMaxFailureRatio(Convert.toFloat(maxFailureRatio)); + } + Object monitor = update.get("monitor"); + if (monitor != null) { + updateConfig.withMonitor(Convert.toLong(monitor)); + } + return updateConfig; + } + return null; + } + + + public void removeServiceCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String serviceId = (String) parameter.get("serviceId"); + RemoveServiceCmd removeServiceCmd = dockerClient.removeServiceCmd(serviceId); + removeServiceCmd.exec(); + } + + private List listTasksCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListTasksCmd listTasksCmd = dockerClient.listTasksCmd(); + String serviceId = (String) parameter.get("serviceId"); + String id = (String) parameter.get("id"); + if (StrUtil.isNotEmpty(serviceId)) { + listTasksCmd.withServiceFilter(serviceId); + } + if (StrUtil.isNotEmpty(id)) { + listTasksCmd.withIdFilter(id); + } + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(name)) { + listTasksCmd.withNameFilter(name); + } + String node = (String) parameter.get("node"); + if (StrUtil.isNotEmpty(node)) { + listTasksCmd.withNodeFilter(node); + } + String state = (String) parameter.get("state"); + if (StrUtil.isNotEmpty(state)) { + TaskState taskState = EnumUtil.fromString(TaskState.class, state); + listTasksCmd.withStateFilter(taskState); + } + List exec = listTasksCmd.exec(); + return exec.stream().map(DockerUtil::toJSON).collect(Collectors.toList()); + } + + public List listServicesCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListServicesCmd listServicesCmd = dockerClient.listServicesCmd(); + String id = (String) parameter.get("id"); + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(id)) { + listServicesCmd.withIdFilter(CollUtil.newArrayList(id)); + } + if (StrUtil.isNotEmpty(name)) { + listServicesCmd.withNameFilter(CollUtil.newArrayList(name)); + } + List exec = listServicesCmd.exec(); + return exec.stream().map(DockerUtil::toJSON).collect(Collectors.toList()); + } + + private void removeSwarmNodeCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + DockerCmdExecFactory dockerCmdExecFactory = (DockerCmdExecFactory) ReflectUtil.getFieldValue(dockerClient, "dockerCmdExecFactory"); + Assert.notNull(dockerCmdExecFactory, I18nMessageUtil.get("i18n.method_not_supported.90c4")); + String nodeId = (String) parameter.get("nodeId"); + RemoveSwarmNodeCmdImpl removeSwarmNodeCmd = new RemoveSwarmNodeCmdImpl( + dockerCmdExecFactory.removeSwarmNodeCmdExec(), nodeId); + removeSwarmNodeCmd.withForce(true); + removeSwarmNodeCmd.exec(); + } + + private void updateSwarmNodeCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String nodeId = (String) parameter.get("nodeId"); + List nodes = dockerClient.listSwarmNodesCmd() + .withIdFilter(CollUtil.newArrayList(nodeId)).exec(); + SwarmNode swarmNode = CollUtil.getFirst(nodes); + Assert.notNull(swarmNode, I18nMessageUtil.get("i18n.no_node.2e83")); + ObjectVersion version = swarmNode.getVersion(); + Assert.notNull(version, I18nMessageUtil.get("i18n.node_info_incomplete.3b69")); + // + String availabilityStr = (String) parameter.get("availability"); + String roleStr = (String) parameter.get("role"); + // + SwarmNodeAvailability availability = EnumUtil.fromString(SwarmNodeAvailability.class, availabilityStr); + SwarmNodeRole role = EnumUtil.fromString(SwarmNodeRole.class, roleStr); + UpdateSwarmNodeCmd swarmNodeCmd = dockerClient.updateSwarmNodeCmd(); + swarmNodeCmd.withSwarmNodeId(nodeId); + SwarmNodeSpec swarmNodeSpec = new SwarmNodeSpec(); + swarmNodeSpec.withAvailability(availability); + swarmNodeSpec.withRole(role); + swarmNodeCmd.withSwarmNodeSpec(swarmNodeSpec); + swarmNodeCmd.withVersion(version.getIndex()); + swarmNodeCmd.exec(); + } + + + private void leaveSwarmCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + Object forceStr = parameter.get("force"); + boolean force = Convert.toBool(forceStr, false); + LeaveSwarmCmd leaveSwarmCmd = dockerClient.leaveSwarmCmd(); + if (force) { + leaveSwarmCmd.withForceEnabled(true); + } + leaveSwarmCmd.exec(); + + } + + +// private List listSwarmNodesCmd(Map parameter) { +// DockerClient dockerClient = DockerUtil.build(parameter); +// try { +// LeaveSwarmCmd leaveSwarmCmd = dockerClient.leaveSwarmCmd(); +// leaveSwarmCmd.withForceEnabled(true) +// } finally { +// +// } +// } + + private List listSwarmNodesCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + ListSwarmNodesCmd listSwarmNodesCmd = dockerClient.listSwarmNodesCmd(); + String id = (String) parameter.get("id"); + if (StrUtil.isNotEmpty(id)) { + listSwarmNodesCmd.withIdFilter(StrUtil.splitTrim(id, StrUtil.COMMA)); + } + String role = (String) parameter.get("role"); + if (StrUtil.isNotEmpty(role)) { + listSwarmNodesCmd.withRoleFilter(StrUtil.splitTrim(role, StrUtil.COMMA)); + } + String name = (String) parameter.get("name"); + if (StrUtil.isNotEmpty(name)) { + listSwarmNodesCmd.withNameFilter(StrUtil.splitTrim(name, StrUtil.COMMA)); + } + List exec = listSwarmNodesCmd.exec(); + return exec.stream().map(swarmNode -> { + JSONObject jsonObject = DockerUtil.toJSON(swarmNode); + jsonObject.remove("rawValues"); + return jsonObject; + }).collect(Collectors.toList()); + } + + + private void joinSwarmCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + String token = (String) parameter.get("token"); + String remoteAddrs = (String) parameter.get("remoteAddrs"); + JoinSwarmCmd joinSwarmCmd = dockerClient.joinSwarmCmd() + .withRemoteAddrs(StrUtil.splitTrim(remoteAddrs, StrUtil.COMMA)) + .withJoinToken(token); + joinSwarmCmd.exec(); + + } + + private JSONObject tryInitializeSwarmCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + // 先尝试获取 + try { + Swarm exec = dockerClient.inspectSwarmCmd().exec(); + JSONObject jsonObject = DockerUtil.toJSON(exec); + if (jsonObject != null) { + return jsonObject; + } + } catch (Exception ignored) { + // + } + // 尝试初始化 + SwarmSpec swarmSpec = new SwarmSpec(); + swarmSpec.withName("default"); + dockerClient.initializeSwarmCmd(swarmSpec).exec(); + // 获取信息 + Swarm exec = dockerClient.inspectSwarmCmd().exec(); + return DockerUtil.toJSON(exec); + } + + private JSONObject inSpectSwarmCmd(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + + Swarm exec = dockerClient.inspectSwarmCmd().exec(); + return DockerUtil.toJSON(exec); + } +} 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 new file mode 100644 index 0000000000..e1e7a8ee14 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerBuild.java @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.*; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.system.SystemUtil; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.CreateContainerCmd; +import com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.*; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.LogRecorder; +import org.dromara.jpom.util.StringUtil; +import org.springframework.util.Assert; + +import java.io.File; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 容器构建 + * + * @author bwcx_jzy + * @since 2022/2/7 + */ +@Slf4j +public class DockerBuild implements AutoCloseable { + + private final Map parameter; + private final DockerClient dockerClient; + private final Map env; + private final IDockerConfigPlugin plugin; + + public DockerBuild(Map parameter, IDockerConfigPlugin plugin) { + this.parameter = parameter; + this.dockerClient = DockerUtil.get(parameter); + this.env = (Map) parameter.get("env"); + this.plugin = plugin; + } + + public int build() { + + LogRecorder logRecorder = (LogRecorder) parameter.get("logRecorder"); + File tempDir = (File) parameter.get("tempDir"); + // 生成临时目录 + tempDir = FileUtil.file(tempDir, "docker-temp", IdUtil.fastSimpleUUID()); + List copy = (List) parameter.get("copy"); + String resultFile = (String) parameter.get("resultFile"); + String resultFileOut = (String) parameter.get("resultFileOut"); + List> steps = (List>) parameter.get("steps"); + + String buildId = env.get("JPOM_BUILD_ID"); + String runsOn = (String) parameter.get("runsOn"); + + String image = String.format("jpomdocker/runs_%s", runsOn); + parameter.put("image", image); + String containerId = null; + try { + this.buildRunOn(dockerClient, runsOn, image, tempDir, logRecorder); + List mounts = new ArrayList<>(); + // 缓存目录 + List cacheMount = this.cachePluginCheck(dockerClient, steps, buildId); + mounts.addAll(cacheMount); + // 添加插件到 mount + mounts.addAll(this.checkDependPlugin(dockerClient, image, steps, buildId, tempDir, logRecorder)); + containerId = this.buildNewContainer(dockerClient, parameter, mounts); + + String buildShell = this.generateBuildShell(steps, buildId); + File tempFile = DockerUtil.createTemp("build.sh", tempDir); + FileUtil.writeUtf8String(buildShell, tempFile); + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(tempFile.getAbsolutePath()) + .withRemotePath("/tmp/") + .exec(); + // + copy = this.replaceEnv(copy); + this.copyArchiveToContainerCmd(dockerClient, containerId, copy, logRecorder); + // 启动容器 + try { + dockerClient.startContainerCmd(containerId).exec(); + } catch (RuntimeException e) { + logRecorder.error(I18nMessageUtil.get("i18n.container_startup_failure.532e"), e); + return -101; + } + // 获取日志 + this.pullLog(dockerClient, containerId, logRecorder); + // 等待容器执行结果 + int statusCode = this.waitContainerCmd(dockerClient, containerId, logRecorder); + // 获取容器执行结果文件 + DockerClientUtil.copyArchiveFromContainerCmd(dockerClient, containerId, logRecorder, resultFile, resultFileOut); + return statusCode; + } finally { + DockerClientUtil.removeContainerCmd(dockerClient, containerId); + // 删除临时目录 + FileUtil.del(tempDir); + } + } + + private List replaceEnv(List list) { + if (env == null) { + return list; + } + // 处理变量 + return list.stream().map(this::replaceEnv).collect(Collectors.toList()); + } + + private String replaceEnv(String value) { + if (env == null) { + return value; + } + // 处理变量 + for (Map.Entry envEntry : env.entrySet()) { + String envValue = StrUtil.utf8Str(envEntry.getValue()); + if (null == envValue) { + continue; + } + value = StrUtil.replace(value, "${" + envEntry.getKey() + "}", envValue); + } + return value; + } + + + /** + * 构建一个执行 镜像 + * + * @param dockerClient docker 客户端连接 + * @param parameter 参数 + * @param mounts 挂载目录 + * @return 容器ID + */ + private String buildNewContainer(DockerClient dockerClient, Map parameter, List mounts) { + String image = (String) parameter.get("image"); + String workingDir = (String) parameter.get("workingDir"); + String dockerName = (String) parameter.get("dockerName"); + List binds = (List) parameter.get("binds"); + Map hostConfigMap = (Map) parameter.get("hostConfig"); + Map env = (Map) parameter.get("env"); + CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image); + containerCmd.withName(dockerName).withWorkingDir(workingDir); + + // + List bindList = new ArrayList<>(); + if (CollUtil.isNotEmpty(binds)) { + bindList = this.replaceEnv(binds).stream() + .map(Bind::parse) + .collect(Collectors.toList()); + } + + HostConfig hostConfig = HostConfig.newHostConfig() + .withMounts(mounts) + .withBinds(bindList); + if (hostConfigMap != null) { + Set> entrySet = hostConfigMap.entrySet(); + for (Map.Entry entry : entrySet) { + String key = entry.getKey(); + String value = entry.getValue(); + Field[] fields = ReflectUtil.getFields(HostConfig.class); + Field field = ArrayUtil.firstMatch(field1 -> { + boolean equals = key.equals(ReflectUtil.getFieldName(field1)); + if (equals) { + return true; + } + JsonProperty jsonProperty = field1.getAnnotation(JsonProperty.class); + if (jsonProperty != null) { + return jsonProperty.value().equals(key); + } + return false; + }, fields); + if (field != null) { + Class type = field.getType(); + Object typeValue = Convert.convert(type, value); + if (typeValue != null) { + ReflectUtil.setFieldValue(hostConfig, field, typeValue); + } else { + log.warn(I18nMessageUtil.get("i18n.container_build_host_config_conversion_failure.27aa"), key, value); + } + } else { + log.warn(I18nMessageUtil.get("i18n.container_build_host_config_field_not_exist.6f61"), key); + } + } + } + // 一定不能使用 auto remove 否则无法下载容器构建产物 + // hostConfig.withAutoRemove(true); + containerCmd.withHostConfig(hostConfig); + // 环境变量 + if (env != null) { + List envList = env.entrySet() + .stream() + .map(entry -> StrUtil.format("{}={}", entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + containerCmd.withEnv(envList); + } + // 如果容器已经存在则先删除 + try { + InspectContainerResponse exec = dockerClient.inspectContainerCmd(dockerName).exec(); + DockerClientUtil.removeContainerCmd(dockerClient, exec.getId()); + } catch (NotFoundException ignored) { + } + // 创建容器 + CreateContainerResponse containerResponse; + containerResponse = containerCmd.exec(); + return containerResponse.getId(); + } + + /** + * 将本地 文件 上传到 容器 + * + * @param dockerClient docker 连接 + * @param containerId 容器ID + * @param copy 需要 上传到文件信息 + */ + private void copyArchiveToContainerCmd(DockerClient dockerClient, String containerId, List copy, LogRecorder logRecorder) { + if (copy == null || dockerClient == null) { + return; + } + for (String s : copy) { + // C:\Users\bwcx_\jpom\server\data\build\5c631117d4834dd4833c04dc1e6e635c\source:/home/jpom/:true + String resource; + String remotePath; + boolean dirChildrenOnly; + List split = StrUtil.split(s, StrUtil.COLON); + if (SystemUtil.getOsInfo().isWindows() && StrUtil.length(split.get(0)) == 1) { + // 第一位是盘符 + resource = split.get(0) + StrUtil.COLON + split.get(1); + remotePath = split.get(2); + dirChildrenOnly = Convert.toBool(CollUtil.get(split, 3), true); + } else { + resource = split.get(0); + remotePath = split.get(1); + dirChildrenOnly = Convert.toBool(CollUtil.get(split, 2), true); + } + logRecorder.system("send file from : {} to : {}", resource, remotePath); + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(resource) + .withRemotePath(remotePath) + .withDirChildrenOnly(dirChildrenOnly) + .exec(); + } + } + + /** + * 生成执行构建的命令 + * + * @param steps 执行步骤 + * @param buildId 构建ID + * @return sh + */ + private String generateBuildShell(List> steps, String buildId) { + StringBuilder stepsScript = new StringBuilder("#!/bin/bash\n"); + stepsScript.append("echo \"\n<<<<<<< Build Start >>>>>>>\"\n"); + // + List afterScriptList = new ArrayList<>(); + for (Map step : steps) { + if (step.containsKey("env")) { + stepsScript.append("# env\n"); + Map env = (Map) step.get("env"); + for (String key : env.keySet()) { + stepsScript.append(String.format("echo \"export %s=%s\" >> /etc/profile\n", key, env.get(key))); + } + } + stepsScript.append("source /etc/profile \n"); + if (step.containsKey("uses")) { + String uses = (String) step.get("uses"); + if ("node".equals(uses)) { + stepsScript.append(nodeScript(step)); + } else if ("java".equals(uses)) { + stepsScript.append(javaScript(step)); + } else if ("maven".equals(uses)) { + stepsScript.append(mavenScript(step)); + } else if ("cache".equals(uses)) { + // 缓存插件 + String[] cacheScript = cacheScript(step, buildId); + stepsScript.append(cacheScript[0]); + afterScriptList.add(cacheScript[1]); + } else if ("go".equals(uses)) { + stepsScript.append(goScript(step)); + } else if ("gradle".equals(uses)) { + stepsScript.append(gradleScript(step)); + } else if ("python3".equals(uses)) { + stepsScript.append(python3Script(step)); + } else { + // 其他自定义插件 + stepsScript.append(otherScript(step, uses)); + } + } + if (step.containsKey("run")) { + stepsScript.append("# run\n"); + String run = (String) step.get("run"); + stepsScript.append(run).append(" \n"); + } + } + // copy + afterScriptList.forEach(stepsScript::append); + stepsScript.append("echo \"<<<<<<< Build End >>>>>>>\"\n"); + return stepsScript.toString(); + } + + /** + * 格式化其他插件端 脚本 + * + * @param step 步骤 + * @param name 插件名 + * @return 结果 + */ + private String otherScript(Map step, String name) { + String path = String.format("/opt/jpom_%s", name); + StringBuilder script = new StringBuilder("# " + name + "Script\n"); + Map map = new HashMap<>(); + map.put("JPOM_PLUGIN_PATH", path); + for (Map.Entry entry : step.entrySet()) { + String key = entry.getKey(); + String value = ObjectUtil.toString(entry.getValue()); + value = StringUtil.formatStrByMap(value, map); + script.append(String.format("echo \"export %s=%s\" >> /etc/profile\n", key, value)); + } + script.append(String.format("echo \"export PATH=%s/bin:$PATH\" >> /etc/profile\n", path)); + script.append("source /etc/profile \n"); + return script.toString(); + } + + + private String mavenScript(Map step) { + String version = String.valueOf(step.get("version")); + String path = String.format("/opt/jpom_maven_%s", version); + String script = "# mavenScript\n"; + script += String.format("echo \"export MAVEN_HOME=%s\" >> /etc/profile\n", path); + script += String.format("echo \"export PATH=%s/bin:$PATH\" >> /etc/profile\n", path); + script += "source /etc/profile \n"; + return script; + } + + private String goScript(Map step) { + String version = String.valueOf(step.get("version")); + String path = String.format("/opt/jpom_go_%s", version); + String script = "# goScript\n"; + script += String.format("echo \"export GOROOT=%s\" >> /etc/profile\n", path); + script += String.format("echo \"export PATH=$PATH:%s/bin\" >> /etc/profile\n", path); + script += "source /etc/profile \n"; + return script; + } + + + private String gradleScript(Map step) { + String version = String.valueOf(step.get("version")); + String path = String.format("/opt/jpom_gradle_%s", version); + String script = "# gradleScript\n"; + script += String.format("echo \"export GRADLE_HOME=%s\" >> /etc/profile\n", path); + script += String.format("echo \"export PATH=$PATH:%s/bin\" >> /etc/profile\n", path); + script += "source /etc/profile \n"; + return script; + } + + private String javaScript(Map step) { + String version = String.valueOf(step.get("version")); + String path = String.format("/opt/jpom_java_%s", version); + String script = "# javaScript\n"; + script += String.format("echo \"export JAVA_HOME=%s\" >> /etc/profile\n", path); + script += String.format("echo \"export PATH=%s/bin:$PATH\" >> /etc/profile\n", path); + script += "source /etc/profile \n"; + return script; + } + + private String python3Script(Map step) { + String version = String.valueOf(step.get("version")); + String path = String.format("/opt/jpom_python3_%s", version); + String script = "# python3Script\n"; + script += String.format("echo \"export PYTHONPATH=%s\" >> /etc/profile\n", path); + script += String.format("echo \"export PATH=%s/bin:$PATH\" >> /etc/profile\n", path); + // pip3 `bad interpreter: no such file or directory:` + script += String.format("sed -i \"1c#!%s/bin/python3\" %s/bin/pip3\n", path, path); + script += "source /etc/profile \n"; + return script; + } + + private String nodeScript(Map step) { + String version = String.valueOf(step.get("version")); + String path = String.format("/opt/jpom_node_%s", version); + String script = "# nodeScript\n"; + script += String.format("echo \"export NODE_HOME=%s\" >> /etc/profile\n", path); + script += String.format("echo \"export PATH=%s/bin:$PATH\" >> /etc/profile\n", path); + script += "source /etc/profile \n"; + return script; + } + + /** + * 插件 runsOn 镜像 + * + * @param dockerClient docker 连接 + * @param runsOn 基于容器镜像 + * @param image 镜像 + * @param tempDir 临时路径 + * @param logRecorder 日志记录 + */ + private void buildRunOn(DockerClient dockerClient, String runsOn, String image, File tempDir, LogRecorder logRecorder) { + try { + dockerClient.inspectImageCmd(image).exec(); + } catch (NotFoundException e) { + HashSet tags = new HashSet<>(); + tags.add(image); + try { + File file = plugin.getResourceToFile(String.format("runs/%s/" + DockerUtil.DOCKER_FILE, runsOn), tempDir); + Assert.notNull(file, I18nMessageUtil.get("i18n.unsupported_prefix.4f8c") + runsOn); + dockerClient.buildImageCmd(file) + .withTags(tags) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(BuildResponseItem object) { + String responseItem = DockerUtil.parseResponseItem(object); + logRecorder.append(responseItem); + } + }).awaitCompletion(); + } catch (InterruptedException ex) { + logRecorder.error(I18nMessageUtil.get("i18n.build_runs_on_image_interrupted.00fd"), ex); + } + } + } + + private String[] cacheScript(Map step, String buildId) { + String mode = String.valueOf(step.get("mode")); + String path = String.format("/opt/%s", this.buildCacheName(step, buildId)); + String beforeScript = "# cacheScript\n"; + String afterScript = ""; + String cachePath = String.valueOf(step.get("path")); + // 可能存在变量,替换为完整的值 + cachePath = this.replaceEnv(cachePath); + if (StrUtil.equalsIgnoreCase(mode, "copy")) { + // npm WARN reify Removing non-directory + // https://github.com/npm/cli/issues/3669 + beforeScript += String.format("echo \"upload cache %s\"\n", cachePath); + beforeScript += String.format("mkdir -p %s\n", cachePath); + beforeScript += String.format("cp -rf %s/* %s \n", path, cachePath); + // 执行构建完成后的命令,将缓存目录 copy 到卷中 + afterScript += "# cacheScript after\n"; + afterScript += String.format("echo \"download cache %s\"\n", cachePath); + afterScript += String.format("cp -rf %s/* %s \n", cachePath, path); + } else { + beforeScript += String.format("ln -s %s %s \n", path, cachePath); + } + return new String[]{beforeScript, afterScript}; + } + + /** + * 构建缓存插件名称 + * + * @param step 缓存配置信息 + * @param buildId 构建id + * @return 名称 + */ + private String buildCacheName(Map step, String buildId) { + String path = (String) step.get("path"); + String type = StrUtil.toString(step.get("type")); + // 全局模式 + boolean global = StrUtil.equalsIgnoreCase(type, "global"); + String md5 = SecureUtil.md5(path); + return global ? String.format("jpom_cache_%s", md5) : String.format("jpom_cache_%s_%s", buildId, md5); + } + + private List cachePluginCheck(DockerClient dockerClient, List> steps, String buildId) { + return steps.stream() + .filter(map -> { + String uses = (String) map.get("uses"); + return StrUtil.equals("cache", uses); + }) + .map(objMap -> { + String name = this.buildCacheName(objMap, buildId); + try { + dockerClient.inspectVolumeCmd(name).exec(); + } catch (NotFoundException e) { + HashMap labels = MapUtil.of("jpom_build_" + buildId, buildId); + String path = (String) objMap.get("path"); + String type = StrUtil.toString(objMap.get("type")); + labels.put("jpom_build_path", path); + labels.put("jpom_build_cache_type", type); + dockerClient.createVolumeCmd() + .withName(name) + .withLabels(labels) + .exec(); + } + // 最终使用 ls 来绑定 + Mount mount = new Mount(); + mount.withType(MountType.VOLUME).withSource(name).withTarget("/opt/" + name); + return mount; + }).collect(Collectors.toList()); + } + + /** + * 依赖插件检查 + * + * @param dockerClient docker 客户端连接 + * @param image 执行镜像名称 + * @param steps 相关参数 + * @param tempDir 临时文件目录 + * @param logRecorder 日志记录器 + * @param buildId 构建ID + * @return list mount + */ + private List checkDependPlugin(DockerClient dockerClient, String image, List> steps, String buildId, File tempDir, LogRecorder logRecorder) { + return steps.stream() + .filter(map -> { + String uses = (String) map.get("uses"); + return StrUtil.isNotEmpty(uses); + }) + .filter(map -> { + // 缓存插件不检查挂载 + String uses = (String) map.get("uses"); + return !StrUtil.equals("cache", uses); + }) + .map(map -> this.dependPluginCheck(dockerClient, image, map, buildId, tempDir, logRecorder)) + .collect(Collectors.toList()); + } + + /** + * 依赖插件检查,判断是否存在对应的 依赖插件 + * + * @param parameter 参数 + * @return mount + */ + public static boolean hasDependPlugin(Map parameter) { + DockerClient dockerClient = DockerUtil.get(parameter); + String pluginName = (String) parameter.get("pluginName"); + String version = Optional.ofNullable(parameter.get("version")) + .map(StrUtil::toStringOrNull) + .map(s -> "_" + s) + .orElse(StrUtil.EMPTY); + String name = String.format("jpom_%s%s", pluginName, version); + return hasDependPlugin(dockerClient, name); + } + + /** + * 依赖插件检查,判断是否存在对应的 依赖插件 + * + * @param dockerClient docker 客户端连接 + * @param pluginName 插件端名称 + * @return mount + */ + public static boolean hasDependPlugin(DockerClient dockerClient, String pluginName) { + try { + dockerClient.inspectVolumeCmd(pluginName).exec(); + return true; + } catch (NotFoundException e) { + return false; + } + } + + /** + * 依赖插件检查 + * + * @param dockerClient docker 客户端连接 + * @param image 执行镜像名称 + * @param usesMap 插件相关参数 + * @param tempDir 临时文件目录 + * @param logRecorder 日志记录器 + * @param buildId 构建ID + * @return mount + */ + private Mount dependPluginCheck(DockerClient dockerClient, String image, Map usesMap, String buildId, File tempDir, LogRecorder logRecorder) { + String pluginName = (String) usesMap.get("uses"); + // 兼容没有版本号 + String version = Optional.ofNullable(usesMap.get("version")) + .map(StrUtil::toStringOrNull) + .orElse(StrUtil.EMPTY); + // 拼接 _ + String version2 = Opt.ofBlankAble(version).map(s -> "_" + s).get(); + String name = String.format("jpom_%s%s", pluginName, version2); + if (!DockerBuild.hasDependPlugin(dockerClient, name)) { + HashMap labels = MapUtil.of("jpom_build_" + buildId, buildId); + labels.put("jpom_build_cache", "true"); + // + dockerClient.createVolumeCmd() + .withName(name) + .withLabels(labels) + .exec(); + + Mount mount = new Mount().withType(MountType.VOLUME) + .withSource(name) + .withTarget("/opt/" + pluginName); + + HostConfig hostConfig = new HostConfig() + .withAutoRemove(true).withMounts(CollUtil.newArrayList(mount)); + CreateContainerResponse createContainerResponse = dockerClient.createContainerCmd(image) + .withHostConfig(hostConfig) + .withName(name) + .withEnv(pluginName.toUpperCase() + "_VERSION=" + version) + .withLabels(labels) + .withEntrypoint("/bin/bash", "/tmp/install.sh") + .exec(); + String containerId = createContainerResponse.getId(); + // 将脚本 复制到容器 + File pluginInstallResource = plugin.getResourceToFile("uses/" + pluginName + "/install.sh", tempDir); + Assert.notNull(pluginInstallResource, I18nMessageUtil.get("i18n.current_not_supported.78b7") + pluginName); + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(pluginInstallResource.getAbsolutePath()) + .withRemotePath("/tmp/") + .exec(); + // + dockerClient.startContainerCmd(containerId).exec(); + // 获取日志 + this.pullLog(dockerClient, containerId, logRecorder); + } + return new Mount().withType(MountType.VOLUME).withSource(name).withTarget("/opt/" + name); + } + + + private void pullLog(DockerClient dockerClient, String containerId, LogRecorder logRecorder) { + // 获取日志 + try { + DockerClientUtil.pullLog(dockerClient, containerId, false, null, StandardCharsets.UTF_8, logRecorder::append, null); + } catch (InterruptedException e) { + logRecorder.error(I18nMessageUtil.get("i18n.get_container_log_interrupted.041d"), e); + } catch (RuntimeException e) { + logRecorder.error(I18nMessageUtil.get("i18n.get_container_log_failure.915d"), e); + } + } + + + private int waitContainerCmd(DockerClient dockerClient, String containerId, LogRecorder logRecorder) { + final Integer[] statusCode = {-100}; + try { + dockerClient.waitContainerCmd(containerId).exec(new ResultCallback.Adapter() { + @Override + public void onNext(WaitResponse object) { + statusCode[0] = object.getStatusCode(); + logRecorder.system("dockerTask status code is: {}", statusCode[0]); + } + }).awaitCompletion(); + + } catch (InterruptedException e) { + logRecorder.error(I18nMessageUtil.get("i18n.get_container_execution_result_interrupted.4a48"), e); + } catch (RuntimeException e) { + logRecorder.error(I18nMessageUtil.get("i18n.get_container_execution_result_failure.1828"), e); + } + return statusCode[0]; + } + + @Override + public void close() throws Exception { + + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerClientUtil.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerClientUtil.java new file mode 100644 index 0000000000..88270c34e3 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerClientUtil.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.LogContainerCmd; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.Frame; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.LogRecorder; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +public class DockerClientUtil { + + /** + * 拉取容器日志 + * + * @param dockerClient 连接 + * @param containerId 容器ID + * @param charset 字符编码 + * @param consumer 回调 + * @throws InterruptedException 打断异常 + */ + public static void pullLog(DockerClient dockerClient, + String containerId, + Boolean timestamps, + Integer tail, + Charset charset, + Consumer consumer, + Consumer consumerAdapter) throws InterruptedException { + Assert.state(tail == null || tail > 0, "tail > 0"); + // 获取日志 + LogContainerCmd logContainerCmd = dockerClient.logContainerCmd(containerId); + if (tail == null) { + logContainerCmd.withTailAll(); + } else { + logContainerCmd.withTail(tail); + } + ResultCallback.Adapter exec = logContainerCmd + .withTimestamps(timestamps) + .withStdOut(true) + .withStdErr(true) + .withFollowStream(true) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame object) { + byte[] payload = object.getPayload(); + if (payload == null) { + return; + } + String s = new String(payload, charset); + consumer.accept(s); + } + }); + Optional.ofNullable(consumerAdapter).ifPresent(consumer1 -> consumer1.accept(exec)); + // + exec.awaitCompletion(); + } + + /** + * 将容器文件下载到本地 + * + * @param dockerClient 容器连接 + * @param containerId 容器ID + * @param logRecorder 日志记录 + * @param resultFile 结果文件 + * @param resultFileOut 保存目录 + */ + public static void copyArchiveFromContainerCmd(DockerClient dockerClient, String containerId, LogRecorder logRecorder, String resultFile, String resultFileOut) { + logRecorder.system("download file from : {}", resultFile); + File tmpDir = FileUtil.getTmpDir(); + File fileArchive = FileUtil.file(tmpDir, "jpom", "docker-temp-archive", containerId); + try { + try (InputStream stream = dockerClient.copyArchiveFromContainerCmd(containerId, resultFile).exec(); + TarArchiveInputStream tarStream = new TarArchiveInputStream(stream)) { + TarArchiveEntry tarArchiveEntry; + while ((tarArchiveEntry = tarStream.getNextEntry()) != null) { + if (!tarStream.canReadEntryData(tarArchiveEntry)) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.cannot_read_tar_archive_entry.85d7"), tarArchiveEntry.getName()); + } + if (tarArchiveEntry.isDirectory()) { + continue; + } + String archiveEntryName = tarArchiveEntry.getName(); + // 截取第一级目录 + archiveEntryName = StrUtil.subAfter(archiveEntryName, StrUtil.SLASH, false); + // 可能中包含文件 使用原名称 + archiveEntryName = StrUtil.emptyToDefault(archiveEntryName, tarArchiveEntry.getName()); + File currentFile = FileUtil.file(fileArchive, archiveEntryName); + FileUtil.mkParentDirs(currentFile); + FileUtil.writeFromStream(tarStream, currentFile, false); + } + } catch (NotFoundException notFoundException) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.execution_result_file_not_found_in_container.cf18"), notFoundException.getMessage()); + } catch (Exception e) { + logRecorder.error(I18nMessageUtil.get("i18n.unable_to_get_container_execution_result_file.7b2c"), e); + } + // github pr 71 + // https://github.com/dromara/Jpom/pull/71 + File[] files = fileArchive.listFiles(); + if (files == null) { + logRecorder.systemWarning(I18nMessageUtil.get("i18n.temporary_result_file_does_not_exist.1c7e"), fileArchive.getAbsolutePath()); + return; + } + File resultFileOutFile = FileUtil.file(resultFileOut); + if (ArrayUtil.length(files) == 1) { + FileUtil.mkParentDirs(resultFileOutFile); + FileUtil.move(files[0], resultFileOutFile, true); + } else { + FileUtil.mkdir(resultFileOutFile); + FileUtil.moveContent(fileArchive, resultFileOutFile, true); + } + } finally { + FileUtil.del(fileArchive); + } + } + + /** + * 删除容器 + * + * @param dockerClient docker 连接 + * @param containerId 容器ID + */ + public static void removeContainerCmd(DockerClient dockerClient, String containerId) { + if (containerId == null) { + return; + } + // 清除容器 + dockerClient.removeContainerCmd(containerId) + .withRemoveVolumes(true) + .withForce(true) + .exec(); + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerUtil.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerUtil.java new file mode 100644 index 0000000000..fa008871d5 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerUtil.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONFactory; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.api.model.AuthResponse; +import com.github.dockerjava.api.model.ResponseItem; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.ssh.JschDockerHttpClient; + +import java.io.File; +import java.time.Duration; +import java.util.Map; +import java.util.function.Supplier; + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +@Slf4j +public class DockerUtil { + + static { + // 禁用 jackson + JSONFactory.setUseJacksonAnnotation(false); + // 枚举对象使用 枚举名称 + JSON.config(JSONWriter.Feature.WriteEnumsUsingName); + } + + private static final Map DOCKER_CLIENT_MAP = new SafeConcurrentHashMap<>(); + private static final Map CACHE_CLOSET = new SafeConcurrentHashMap<>(); + + /** + * dockerfile 文件名称 + */ + public static final String DOCKER_FILE = "Dockerfile"; + + /** + * 获取 docker client ,会使用缓存 + *

+ * 如果参数包含 closeBefore 则重新创建 + * + * @param parameter 参数 + * @return DockerClient + */ + public static DockerClient get(Map parameter) { + String host = (String) parameter.get("dockerHost"); + String dockerCertPath = (String) parameter.get("dockerCertPath"); + String key = StrUtil.format("{}-{}", host, StrUtil.emptyToDefault(dockerCertPath, StrUtil.EMPTY)); + if (parameter.containsKey("closeBefore")) { + // 关闭之前的连接 + DockerClient dockerClient = DOCKER_CLIENT_MAP.remove(key); + IoUtil.close(dockerClient); + } + return DOCKER_CLIENT_MAP.computeIfAbsent(key, s -> create(parameter)); + } + + /** + * 构建 docker client 对象 + * + * @param parameter 参数 + * @return DockerClient + */ + private static DockerClient create(Map parameter) { + String host = (String) parameter.get("dockerHost"); + String apiVersion = (String) parameter.get("apiVersion"); + String dockerCertPath = (String) parameter.get("dockerCertPath"); + String registryUsername = (String) parameter.get("registryUsername"); + String registryPassword = (String) parameter.get("registryPassword"); + String registryEmail = (String) parameter.get("registryEmail"); + String registryUrl = (String) parameter.get("registryUrl"); + Supplier sessionSupplier = (Supplier) parameter.get("session"); + // + DefaultDockerClientConfig.Builder defaultConfigBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder(); + defaultConfigBuilder + .withDockerTlsVerify(StrUtil.isNotEmpty(dockerCertPath)) + .withApiVersion(apiVersion) + .withDockerCertPath(dockerCertPath) + .withDockerHost(host); + // + Opt.ofBlankAble(registryUrl).ifPresent(s -> defaultConfigBuilder.withRegistryUrl(registryUrl)); + Opt.ofBlankAble(registryEmail).ifPresent(s -> defaultConfigBuilder.withRegistryEmail(registryEmail)); + Opt.ofBlankAble(registryUsername).ifPresent(s -> defaultConfigBuilder.withRegistryUsername(registryUsername)); + Opt.ofBlankAble(registryPassword).ifPresent(s -> defaultConfigBuilder.withRegistryPassword(registryPassword)); + + DockerClient dockerClient; + DockerClientConfig config = defaultConfigBuilder.build(); + if (sessionSupplier != null) { + // 通过SSH连接Docker + JschDockerHttpClient httpClient = new JschDockerHttpClient(config.getDockerHost(), sessionSupplier); + dockerClient = DockerClientImpl.getInstance(config, httpClient); + } else { + ApacheDockerHttpClient.Builder builder = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100); + int timeout = Convert.toInt(parameter.get("timeout"), 0); + if (timeout > 0) { + builder.connectionTimeout(Duration.ofSeconds(timeout)); + builder.responseTimeout(Duration.ofSeconds(timeout)); + } + ApacheDockerHttpClient httpClient = builder.build(); + dockerClient = DockerClientImpl.getInstance(config, httpClient); + } + if (StrUtil.isNotEmpty(registryUrl)) { + AuthConfig authConfig = dockerClient.authConfig(); + AuthResponse authResponse = dockerClient.authCmd().withAuthConfig(authConfig).exec(); + log.debug("auth cmd:{}", JSONObject.toJSONString(authResponse)); + } + return dockerClient; + } + + /** + * 临时文件目录 + * + * @param name 文件名 + * @param tempDir 临时文件目录 + * @return temp + */ + public static File createTemp(String name, File tempDir) { + return FileUtil.file(tempDir, name); + } + + /** + * 获取进度信息 + * + * @param responseItem 响应结果 + * @return 转化为 字符串 + */ + public static String parseResponseItem(ResponseItem responseItem) { + String stream = responseItem.getStream(); + if (stream == null) { + String status = responseItem.getStatus(); + if (status == null) { + Map rawValues = responseItem.getRawValues(); + return MapUtil.join(rawValues, ",", "=") + StrUtil.LF; + } + String progress = responseItem.getProgress(); + progress = StrUtil.emptyToDefault(progress, StrUtil.EMPTY); + String id = responseItem.getId(); + id = StrUtil.emptyToDefault(id, StrUtil.EMPTY); + return StrUtil.format("{} {} {}", status, id, progress); + } + return stream; + } + + /** + * 深度转换为 json object + * + * @param object 对象 + * @return 转换后的 + */ + public static JSONObject toJSON(Object object) { + String jsonString = JSONObject.toJSONString(object); + return (JSONObject) JSON.parse(jsonString); + } + + public static void putClose(String id, AutoCloseable autoCloseable) { + AutoCloseable closeable = CACHE_CLOSET.put(id, autoCloseable); + // 关闭上一次资源 + IoUtil.close(closeable); + } + + public static void close(String id) { + IoUtil.close(CACHE_CLOSET.remove(id)); + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/IDockerConfigPlugin.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/IDockerConfigPlugin.java new file mode 100644 index 0000000000..643259e4fd --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/IDockerConfigPlugin.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom; + +import cn.hutool.core.io.FileUtil; +import org.dromara.jpom.plugin.IDefaultPlugin; + +import java.io.File; +import java.io.InputStream; + +/** + * @author bwcx_jzy + * @since 2023/1/4 + */ +public interface IDockerConfigPlugin extends IDefaultPlugin { + + + /** + * 获取 配置资源 + * + * @param name 配置文件名称 + * @return 文件流 + */ + @Override + default InputStream getConfigResourceInputStream(String name) { + String newName = "docker/" + name; + return IDefaultPlugin.super.getConfigResourceInputStream(newName); + } + + /** + * 获取配置文件 + * + * @param name 配置文件名称 + * @param tempDir 保存目录 + * @return file + */ + default File getResourceToFile(String name, File tempDir) { + InputStream stream = this.getConfigResourceInputStream(name); + if (stream == null) { + return null; + } + File tempFile = DockerUtil.createTemp(name, tempDir); + FileUtil.writeFromStream(stream, tempFile); + return tempFile; + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/HijackingHttpRequestExecutor.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/HijackingHttpRequestExecutor.java new file mode 100644 index 0000000000..0138503159 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/HijackingHttpRequestExecutor.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.ssh; + +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.impl.io.HttpRequestExecutor; +import org.apache.hc.core5.http.io.HttpClientConnection; +import org.apache.hc.core5.http.io.HttpResponseInformationCallback; +import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.apache.hc.core5.http.message.StatusLine; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.io.Closer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Objects; + +/** + * https://github.com/docker-java/docker-java + * + * @author docker-java-transport-httpclient5 + */ +class HijackingHttpRequestExecutor extends HttpRequestExecutor { + + static final String HIJACKED_INPUT_ATTRIBUTE = "com.github.docker-java.hijackedInput"; + + HijackingHttpRequestExecutor(ConnectionReuseStrategy connectionReuseStrategy) { + super(connectionReuseStrategy); + } + + @Override + public ClassicHttpResponse execute( + ClassicHttpRequest request, + HttpClientConnection conn, + HttpResponseInformationCallback informationCallback, + HttpContext context + ) throws IOException, HttpException { + Objects.requireNonNull(request, "HTTP request"); + Objects.requireNonNull(conn, "Client connection"); + Objects.requireNonNull(context, "HTTP context"); + + InputStream hijackedInput = (InputStream) context.getAttribute(HIJACKED_INPUT_ATTRIBUTE); + if (hijackedInput != null) { + return executeHijacked(request, conn, context, hijackedInput); + } + + return super.execute(request, conn, informationCallback, context); + } + + private ClassicHttpResponse executeHijacked( + ClassicHttpRequest request, + HttpClientConnection conn, + HttpContext context, + InputStream hijackedInput + ) throws HttpException, IOException { + try { + context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession()); + context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails()); + final ProtocolVersion transportVersion = request.getVersion(); + if (transportVersion != null) { + context.setProtocolVersion(transportVersion); + } + + conn.sendRequestHeader(request); + conn.sendRequestEntity(request); + conn.flush(); + + ClassicHttpResponse response = conn.receiveResponseHeader(); + if (response.getCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { + conn.terminateRequest(request); + throw new ProtocolException("Expected 101 Switching Protocols, got: " + new StatusLine(response)); + } + + Thread thread = new Thread(() -> { + try { + BasicClassicHttpRequest fakeRequest = new BasicClassicHttpRequest("POST", "/"); + fakeRequest.setHeader(HttpHeaders.CONTENT_LENGTH, Long.MAX_VALUE); + fakeRequest.setEntity(new HijackedEntity(hijackedInput)); + conn.sendRequestEntity(fakeRequest); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + thread.setName("docker-java-ssh-httpclient5-hijacking-stream-" + System.identityHashCode(request)); + thread.setDaemon(true); + thread.start(); + + // 101 -> 200 + response.setCode(200); + conn.receiveResponseEntity(response); + return response; + + } catch (final HttpException | IOException | RuntimeException ex) { + Closer.closeQuietly(conn); + throw ex; + } + } + + private static class HijackedEntity extends AbstractHttpEntity { + + private final InputStream inStream; + + HijackedEntity(InputStream inStream) { + super((String) null, null, false); + this.inStream = inStream; + } + + @Override + public void writeTo(OutputStream outStream) throws IOException { + byte[] buffer = new byte[1024]; + int read; + while ((read = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, read); + outStream.flush(); + } + } + + @Override + public InputStream getContent() { + return inStream; + } + + @Override + public boolean isStreaming() { + return true; + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public void close() throws IOException { + inStream.close(); + } + + @Override + public long getContentLength() { + return -1; + } + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/JschDockerHttpClient.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/JschDockerHttpClient.java new file mode 100644 index 0000000000..7fedad8a0b --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/JschDockerHttpClient.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.ssh; + +import com.github.dockerjava.transport.DockerHttpClient; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; +import org.apache.hc.core5.http.impl.io.EmptyInputStream; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.protocol.BasicHttpContext; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.URIAuthority; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * https://github.com/docker-java/docker-java + * + * @author docker-java-transport-httpclient5 + */ +@Slf4j +public final class JschDockerHttpClient implements DockerHttpClient { + + + private final Session session; + private final CloseableHttpClient httpClient; + private final HttpHost host; + + public JschDockerHttpClient(URI dockerHostUri, Supplier sessionSupplier) { + this.session = sessionSupplier.get(); + Registry socketFactoryRegistry = createConnectionSocketFactoryRegistry(session); + // + if ("ssh".equals(dockerHostUri.getScheme())) { + host = new HttpHost(dockerHostUri.getScheme(), dockerHostUri.getHost(), 2375); + } else { + host = HttpHost.create(dockerHostUri); + } + httpClient = HttpClients.custom() + .setRequestExecutor(new HijackingHttpRequestExecutor(null)) + .setConnectionManager(new PoolingHttpClientConnectionManager( + socketFactoryRegistry, + new ManagedHttpClientConnectionFactory( + null, + null, + null, + null, + message -> { + Header transferEncodingHeader = message.getFirstHeader(HttpHeaders.TRANSFER_ENCODING); + if (transferEncodingHeader != null) { + if ("identity".equalsIgnoreCase(transferEncodingHeader.getValue())) { + return ContentLengthStrategy.UNDEFINED; + } + } + return DefaultContentLengthStrategy.INSTANCE.determineLength(message); + }, + null + ) + )) + .build(); + } + + + @Override + public Response execute(Request request) { + + HttpContext context = new BasicHttpContext(); + HttpUriRequestBase httpUriRequest = new HttpUriRequestBase(request.method(), URI.create(request.path())); + httpUriRequest.setScheme(host.getSchemeName()); + httpUriRequest.setAuthority(new URIAuthority(host.getHostName(), host.getPort())); + + request.headers().forEach(httpUriRequest::addHeader); + + InputStream body = request.body(); + if (body != null) { + httpUriRequest.setEntity(new InputStreamEntity(body, null)); + } + + if (request.hijackedInput() != null) { + context.setAttribute(HijackingHttpRequestExecutor.HIJACKED_INPUT_ATTRIBUTE, request.hijackedInput()); + httpUriRequest.setHeader("Upgrade", "tcp"); + httpUriRequest.setHeader("Connection", "Upgrade"); + } + + try { + CloseableHttpResponse response = httpClient.execute(host, httpUriRequest, context); + + return new ApacheResponse(httpUriRequest, response); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() throws IOException { + try { + session.disconnect(); + } catch (Exception e) { + log.debug("session error", e); + } + httpClient.close(); + } + + @Slf4j + static class ApacheResponse implements Response { + + + private final HttpUriRequestBase request; + + private final CloseableHttpResponse response; + + ApacheResponse(HttpUriRequestBase httpUriRequest, CloseableHttpResponse response) { + this.request = httpUriRequest; + this.response = response; + } + + @Override + public int getStatusCode() { + return response.getCode(); + } + + @Override + public Map> getHeaders() { + return Stream.of(response.getHeaders()).collect(Collectors.groupingBy( + NameValuePair::getName, + Collectors.mapping(NameValuePair::getValue, Collectors.toList()) + )); + } + + @Override + public String getHeader(String name) { + Header firstHeader = response.getFirstHeader(name); + return firstHeader != null ? firstHeader.getValue() : null; + } + + @Override + public InputStream getBody() { + try { + return response.getEntity() != null + ? response.getEntity().getContent() + : EmptyInputStream.INSTANCE; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + try { + request.abort(); + } catch (Exception e) { + log.debug("Failed to abort the request", e); + } + + try { + response.close(); + } catch (ConnectionClosedException e) { + log.trace("Failed to close the response", e); + } catch (Exception e) { + log.debug("Failed to close the response", e); + } + } + } + + private Registry createConnectionSocketFactoryRegistry(Session session) { + RegistryBuilder socketFactoryRegistryBuilder = RegistryBuilder.create(); + return socketFactoryRegistryBuilder + .register("ssh", new PlainConnectionSocketFactory() { + @Override + public Socket createSocket(HttpContext context) throws IOException { + return new JschSocket(session); + } + }) + .build(); + } +} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/JschSocket.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/JschSocket.java new file mode 100644 index 0000000000..5ce5d3f72b --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/ssh/JschSocket.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.ssh; + +import cn.hutool.system.SystemUtil; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Optional; + +@Slf4j +class JschSocket extends Socket { + + private final Session session; + + private Channel channel; + private InputStream inputStream; + private OutputStream outputStream; + + JschSocket(Session session) { + this.session = session; + } + + @Override + public void connect(SocketAddress endpoint) throws IOException { + connect(0); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + connect(timeout); + } + + @Override + public boolean isConnected() { + return channel.isConnected(); + } + + @Override + public boolean isClosed() { + return channel != null && channel.isClosed(); + } + + private void connect(int timeout) throws IOException { + try { + // only 18.09 and up + channel = session.openChannel("exec"); + boolean jpomCommandUseSudo = SystemUtil.getBoolean("JPOM_COMMAND_USE_SUDO", false); + String command; + if (jpomCommandUseSudo) { + command = "sudo docker system dial-stdio"; + } else { + command = "docker system dial-stdio"; + } + ((ChannelExec) channel).setCommand(command); + log.debug("Using dialer command【{}】", command); + inputStream = channel.getInputStream(); + outputStream = channel.getOutputStream(); + + channel.connect(timeout); + + } catch (JSchException e) { + throw new IOException(e); + } + } + + @Override + public synchronized void close() throws IOException { + Optional.ofNullable(channel).ifPresent(Channel::disconnect); + } + + @Override + public InputStream getInputStream() { + return inputStream; + } + + @Override + public OutputStream getOutputStream() { + return outputStream; + } + +} diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/runs/ubuntu-git/Dockerfile b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/runs/ubuntu-git/Dockerfile new file mode 100644 index 0000000000..d8ff768695 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/runs/ubuntu-git/Dockerfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +FROM ubuntu:latest +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG C.UTF-8 +RUN sed -i.bak 's/archive.ubuntu.com/mirror.nju.edu.cn/' /etc/apt/sources.list \ + && sed -i 's/security.ubuntu.com/mirror.nju.edu.cn/' /etc/apt/sources.list \ + && apt-get update \ + && apt-get install -y curl wget + +RUN apt-get install -y git + +ENTRYPOINT ["/bin/bash", "/tmp/build.sh"] diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/runs/ubuntu-latest/Dockerfile b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/runs/ubuntu-latest/Dockerfile new file mode 100644 index 0000000000..e739dd1c8c --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/runs/ubuntu-latest/Dockerfile @@ -0,0 +1,19 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +FROM ubuntu:latest +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG C.UTF-8 +RUN sed -i.bak 's/archive.ubuntu.com/mirror.nju.edu.cn/' /etc/apt/sources.list \ + && sed -i 's/security.ubuntu.com/mirror.nju.edu.cn/' /etc/apt/sources.list \ + && apt-get update \ + && apt-get install -y curl wget + +ENTRYPOINT ["/bin/bash", "/tmp/build.sh"] diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/go/install.sh b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/go/install.sh new file mode 100644 index 0000000000..84e7c7c926 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/go/install.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + + +if [ $GO_VERSION ]; then + echo "GO_VERSION ${GO_VERSION}" +else + echo "not found GO_VERSION" + exit 1 +fi + +cd /tmp +download_url="" +ARCH_O=`uname -m` +# https://studygolang.com/dl +case "${ARCH_O}" in + aarch64|arm64) + ARCH='arm64'; + ;; + amd64|x86_64) + ARCH='amd64'; + ;; + *) + echo "Unsupported arch: ${ARCH_O}"; + exit 1; + ;; +esac; + +# https://studygolang.com/dl/golang/go1.17.6.linux-amd64.tar.gz + +wget https://studygolang.com/dl/golang/go${GO_VERSION}.linux-${ARCH}.tar.gz -O go.tar.gz +tar -zxf go.tar.gz --strip-components 1 -C /opt/go/ diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/gradle/install.sh b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/gradle/install.sh new file mode 100644 index 0000000000..e661dfcba1 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/gradle/install.sh @@ -0,0 +1,23 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +if [ $GRADLE_VERSION ]; then + echo "GRADLE_VERSION ${GRADLE_VERSION}" +else + echo "not found GRADLE_VERSION" + exit 1 +fi +cd /tmp +download_url="https://downloads.gradle-dn.com/distributions/gradle-${GRADLE_VERSION}-bin.zip" +apt install unzip +wget ${download_url} -O gradle-bin.zip +unzip -q -d /opt/gradle/ -o gradle-bin.zip +mv -f /opt/gradle/gradle-${GRADLE_VERSION}/* /opt/gradle +rm -rf /opt/gradle/gradle-${GRADLE_VERSION} diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/java/install.sh b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/java/install.sh new file mode 100644 index 0000000000..830da9066b --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/java/install.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +if [ $JAVA_VERSION ]; then + echo "JAVA_VERSION ${JAVA_VERSION}" +else + echo "not found JAVA_VERSION" + exit 1 +fi +cd /tmp +download_url="" +ARCH_O=`uname -m` +# https://mirrors.tuna.tsinghua.edu.cn/Adoptium/ +# https://github.com/AdoptOpenJDK/openjdk-docker/blob/master/8/jdk/ubuntu/Dockerfile.hotspot.nightly.full +case "${ARCH_O}" in + aarch64|arm64) + ARCH='aarch64'; + ;; + armhf|armv7l) + ARCH='arm'; + ;; + ppc64el|ppc64le) + ARCH='ppc64le'; + ;; + s390x) + ARCH='s390x'; + ;; + amd64|x86_64) + ARCH='x64'; + ;; + *) + echo "Unsupported arch: ${ARCH_O}"; + exit 1; + ;; +esac; + +download_url=`curl -s https://gitee.com/dromara/Jpom/raw/download_link/jdk/${JAVA_VERSION}/${ARCH}` + +wget ${download_url} -O jdk.tar.gz +tar -zxf jdk.tar.gz --strip-components 1 -C /opt/java/ diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/maven/install.sh b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/maven/install.sh new file mode 100644 index 0000000000..53efa44e2a --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/maven/install.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +if [ $MAVEN_VERSION ]; then + echo "MAVEN_VERSION ${MAVEN_VERSION}" +else + echo "not found MAVEN_VERSION" + exit 1 +fi +cd /tmp +download_url="https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" +wget ${download_url} -O maven.tar.gz +tar -zxf maven.tar.gz --strip-components 1 -C /opt/maven/ diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/node/install.sh b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/node/install.sh new file mode 100644 index 0000000000..83eaf535a1 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/node/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + + +if [ $NODE_VERSION ]; then + echo "NODE_VERSION ${NODE_VERSION}" +else + echo "not found NODE_VERSION" + exit 1 +fi +# https://github.com/nodejs/docker-node/blob/main/12/buster/Dockerfile +ARCH=`uname -m` +case "${ARCH}" in + aarch64|arm64) + BINARY_ARCH='arm64'; + ;; + amd64|x86_64) + BINARY_ARCH='x64'; + ;; + ppc64el) + BINARY_ARCH='ppc64le' + ;; + s390x) + BINARY_ARCH='s390x' + ;; + armhf) + BINARY_ARCH='armv7l' + ;; + i386) + BINARY_ARCH='x86' + ;; + *) + echo "Unsupported arch: ${ARCH}"; + exit 1; + ;; +esac; +cd /tmp +wget https://registry.npmmirror.com/-/binary/node/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${BINARY_ARCH}.tar.gz -O node.tar.gz +tar -zxf node.tar.gz --strip-components 1 -C /opt/node/ diff --git a/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/python3/install.sh b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/python3/install.sh new file mode 100644 index 0000000000..196a871f61 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/main/resources/config_default/docker/uses/python3/install.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + + +if [ $PYTHON3_VERSION ]; then + echo "PYTHON3_VERSION ${PYTHON3_VERSION}" +else + echo "not found PYTHON3_VERSION" + exit 1 +fi + +# https://github.com/docker-library/python/blob/master/3.10/slim-bullseye/Dockerfile + +# https://github.com/cdrx/docker-pyinstaller/blob/master/Dockerfile-py3-amd64 + +pythonPath=/opt/python3/ +pythonTempPath=/usr/local/python3 + +# https://repo.huaweicloud.com/python/3.6.6/Python-3.6.6.tar.xz + +apt-get update; \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + netbase \ + tzdata \ + ; \ +rm -rf /var/lib/apt/lists/* + +savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + dpkg-dev \ + gcc \ + gnupg dirmngr \ + libbluetooth-dev \ + libbz2-dev \ + libc6-dev \ + libexpat1-dev \ + libffi-dev \ + libgdbm-dev \ + liblzma-dev \ + libncursesw5-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + make \ + tk-dev \ + uuid-dev \ + xz-utils \ + zlib1g-dev \ +; + +wget -O python.tar.xz "https://repo.huaweicloud.com/python/${PYTHON3_VERSION}/Python-${PYTHON3_VERSION}.tar.xz"; + +mkdir -p ${pythonTempPath} && tar -xvf python.tar.xz --strip-components 1 -C ${pythonTempPath} + +cd ${pythonTempPath}; + +gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ +./configure \ + --prefix=${pythonPath} + --build="$gnuArch" \ + --enable-loadable-sqlite-extensions \ + --enable-optimizations \ + --enable-option-checking=fatal \ + --enable-shared \ + --with-lto \ + --with-system-expat \ + --with-system-ffi \ + --without-ensurepip \ +; + +nproc="$(nproc)"; +make -j "$nproc" \ + LDFLAGS="-Wl,--strip-all" \ +# setting PROFILE_TASK makes "--enable-optimizations" reasonable: https://bugs.python.org/issue36044 / https://github.com/docker-library/python/issues/160#issuecomment-509426916 + PROFILE_TASK='-m test.regrtest --pgo \ + test_array \ + test_base64 \ + test_binascii \ + test_binhex \ + test_binop \ + test_bytes \ + test_c_locale_coercion \ + test_class \ + test_cmath \ + test_codecs \ + test_compile \ + test_complex \ + test_csv \ + test_decimal \ + test_dict \ + test_float \ + test_fstring \ + test_hashlib \ + test_io \ + test_iter \ + test_json \ + test_long \ + test_math \ + test_memoryview \ + test_pickle \ + test_re \ + test_set \ + test_slice \ + test_struct \ + test_threading \ + test_time \ + test_traceback \ + test_unicode \ + ' \ +; + +make install + diff --git a/modules/sub-plugin/docker-cli/src/test/java/JschDockerHttpClientIT.java b/modules/sub-plugin/docker-cli/src/test/java/JschDockerHttpClientIT.java new file mode 100644 index 0000000000..ac940f0503 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/JschDockerHttpClientIT.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.Info; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.transport.DockerHttpClient; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import org.dromara.jpom.plugins.JschLogger; +import org.dromara.jpom.ssh.JschDockerHttpClient; +import org.junit.Test; + +import java.io.IOException; + + +public class JschDockerHttpClientIT { + + @Test + public void pingViaDialer() throws IOException, JSchException { + + final JSch jSch = new JSch(); + JSch.setLogger(JschLogger.LOGGER); + +// final String configFile = System.getProperty("user.home") + File.separator + ".ssh" + File.separator + "config"; +// final File file = new File(configFile); +// if (file.exists()) { +// final OpenSSHConfig openSSHConfig = OpenSSHConfig.parseFile(file.getAbsolutePath()); +// jSch.setConfigRepository(openSSHConfig); +// } + + + final Session newSession = jSch.getSession("root", "192.168.127.156", 22); + newSession.setPassword("123456+"); + newSession.setConfig("StrictHostKeyChecking", "no"); + newSession.connect(); + + final DefaultDockerClientConfig dockerClientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost("ssh://root@192.168.127.156") + + .build(); + + + try (final DockerHttpClient dockerHttpClient = new JschDockerHttpClient(dockerClientConfig.getDockerHost(), () -> newSession)) { + + + final DockerClient dockerClient = DockerClientImpl.getInstance(dockerClientConfig, dockerHttpClient); + + + Info exec = dockerClient.infoCmd().exec(); + System.out.println(exec); + // + exec = dockerClient.infoCmd().exec(); + System.out.println(exec); + + + dockerClient.close(); + } + } + + +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/Test.java b/modules/sub-plugin/docker-cli/src/test/java/Test.java new file mode 100644 index 0000000000..48b26d1d5e --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/Test.java @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.core.InvocationBuilder; +import com.github.dockerjava.core.exec.ExecStartCmdExec; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.junit.After; +import org.junit.Before; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author bwcx_jzy + * @since 2022/1/25 + */ +@Slf4j +public class Test { + + private DockerClient dockerClient; + private String containerId; + + // @Before + public void beforeLocal() { + // + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + + DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder() + // .withDockerHost("tcp://192.168.163.11:2376").build(); +// .withApiVersion() + .withDockerHost("tcp://127.0.0.1:2375").build(); + + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) +// .connectionTimeout(Duration.ofSeconds(30)) +// .responseTimeout(Duration.ofSeconds(45)) + .build(); + this.dockerClient = DockerClientImpl.getInstance(config, httpClient); + dockerClient.pingCmd().exec(); + } + + @Before + public void before1() { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + + DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder() + // .withDockerHost("tcp://192.168.163.11:2376").build(); +// .withApiVersion() + .withDockerTlsVerify(true) + .withDockerCertPath("/Users/user/fsdownload/docker-ca") + .withDockerHost("tcp://172.19.106.253:2375").build(); + + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) +// .connectionTimeout(Duration.ofSeconds(30)) +// .responseTimeout(Duration.ofSeconds(45)) + .build(); + this.dockerClient = DockerClientImpl.getInstance(config, httpClient); + dockerClient.pingCmd().exec(); + } + + @After + public void after() { + if (containerId == null) { + return; + } + // 清除容器 +// this.dockerClient.removeContainerCmd(containerId) +// .withRemoveVolumes(true) +// .withForce(true) +// .exec(); + } + + @org.junit.Test + public void testInfo() { + PingCmd pingCmd = dockerClient.pingCmd(); + pingCmd.exec(); + System.out.println(pingCmd); + } + + @org.junit.Test + public void tset() { + ListImagesCmd listImagesCmd = dockerClient.listImagesCmd(); + List exec = listImagesCmd.exec(); + exec.forEach(System.out::println); + } + + @org.junit.Test + public void createImage() { + File file = FileUtil.file(""); + file = FileUtil.getParent(file, 5); + String absolutePath = "/Users/user/IdeaProjects/Jpom-demo-case"; + System.out.println(absolutePath); + String image = "maven:3.8.5-jdk-8"; + String workingDir = "/jpom/"; + CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image); + String name = "jpom-test"; + containerCmd.withName(name); + CreateContainerCmd createContainerCmd = containerCmd.withWorkingDir(workingDir); + // +// List mounts = new ArrayList<>(); +// mounts.add(new Mount() +// .withType(MountType.VOLUME) +// .withSource(absolutePath) +// .withTarget(workingDir)); + + + List bindList = new ArrayList<>(); + bindList.add(new Bind(absolutePath, new Volume(workingDir))); + //bindList.add(new Bind("/Users/user/.m2", new Volume("/root/.m2"))); + // + // + HostConfig hostConfig = HostConfig.newHostConfig().withBinds(bindList); +// .withMounts(mounts); + createContainerCmd.withHostConfig(hostConfig); + + String[] entrypoint = {"/bin/sh", "-c"}; + String[] cmd = {"mkdir -p /root/.m2/ && ln -s /root/settings.xml /root/.m2/settings.xml && mvn clean package"}; +// String[] cmd = {""}; + createContainerCmd.withEntrypoint(entrypoint); + + + createContainerCmd.withCmd(cmd); + + // + // 检查镜像是否存在本地 + boolean imagePull = false; + try { + this.dockerClient.inspectImageCmd(image).exec(); + } catch (NotFoundException e) { + log.info("镜像不存在,需要下载"); + imagePull = true; + } + // 拉取镜像 + if (imagePull) { + try { + this.dockerClient.pullImageCmd(image).exec(new ResultCallback.Adapter() { + @Override + public void onNext(PullResponseItem object) { + log.info("镜像下载成功: {} status: {}", object.getId(), object.getStatus()); + } + }).awaitCompletion(); + } catch (InterruptedException | RuntimeException e) { + log.error("镜像下载失败:", e); + // this.publisher.publishEvent(TaskFailedEvent.builder() + // .triggerId(dockerTask.getTriggerId()) + // .taskId(dockerTask.getTaskInstanceId()) + // .errorMsg(e.getMessage()) + // .build()); + Thread.currentThread().interrupt(); + return; + } + } + // + // 创建容器 + CreateContainerResponse containerResponse; + try { + containerResponse = createContainerCmd.exec(); + } catch (RuntimeException e) { + log.error("无法创建容器", e); + return; + } +// String containerId =; + this.containerId = containerResponse.getId(); + + List split = StrUtil.split("/Users/user/.m2/settings.xml:/root/:false", StrUtil.COLON); + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(split.get(0)) + .withRemotePath(split.get(1)) + .withNoOverwriteDirNonDir(true) + .withDirChildrenOnly(Convert.toBool(CollUtil.get(split, 2), true)) + .exec(); + + // 启动容器 + try { + this.dockerClient.startContainerCmd(containerId).exec(); + } catch (RuntimeException e) { + log.error("容器启动失败:", e); + return; + } + // 获取日志 + try { + this.dockerClient.logContainerCmd(containerId) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .withFollowStream(true) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame object) { + String s = new String(object.getPayload(), StandardCharsets.UTF_8); + System.out.print(s); + } + }).awaitCompletion(); + } catch (InterruptedException e) { + log.error("获取容器日志操作被中断:", e); + + Thread.currentThread().interrupt(); + } catch (RuntimeException e) { + log.error("获取容器日志失败", e); + + Thread.currentThread().interrupt(); + } + // 等待容器执行结果 + try { + this.dockerClient.waitContainerCmd(containerId).exec(new ResultCallback.Adapter() { + @Override + public void onNext(WaitResponse object) { + log.info("dockerTask status code is: {}", object.getStatusCode()); + } + }).awaitCompletion(); + } catch (InterruptedException e) { + log.error("获取容器执行结果操作被中断:", e); + Thread.currentThread().interrupt(); + } catch (RuntimeException e) { + log.error("获取容器执行结果失败", e); + + Thread.currentThread().interrupt(); + } + // 获取容器执行结果文件(JSON,非数组),转换为任务输出参数 + String re = "springboot-test-jar/target/"; + String resultFile = FileUtil.file(workingDir, re).getAbsolutePath(); + try ( + InputStream stream = this.dockerClient.copyArchiveFromContainerCmd(containerId, resultFile).exec(); + TarArchiveInputStream tarStream = new TarArchiveInputStream(stream); + //BufferedReader reader = new BufferedReader(new InputStreamReader(tarStream, StandardCharsets.UTF_8)) + ) { +// tarStream.getNextTarEntry() + TarArchiveEntry tarArchiveEntry; + File file1 = FileUtil.file(file, re); + FileUtil.del(file1); + + while ((tarArchiveEntry = tarStream.getNextTarEntry()) != null) { + if (!tarStream.canReadEntryData(tarArchiveEntry)) { + log.info("不能读取tarArchiveEntry"); + } + if (tarArchiveEntry.isDirectory()) { + continue; + } + log.info("tarArchiveEntry's name: {}", tarArchiveEntry.getName()); + File currentFile = FileUtil.file(file, re, tarArchiveEntry.getName()); + FileUtil.mkParentDirs(currentFile); + IoUtil.copy(tarStream, new FileOutputStream(currentFile)); +// resultFile = IOUtils.toString(reader); + // 将文件写出到解压的目录 + //IOUtils.copy(fin, new FileOutputStream(curfile)); + } + +// CompressionFileUtil +// if (!tarArchiveEntry.isFile()) { +// log.info("执行结果文件必须是文件类型, 不支持目录或其他类型"); +// } + + + //FileUtil.del(file1); + //Extractor extractor = CompressUtil.createExtractor(StandardCharsets.UTF_8, tarStream); + //extractor.extract(file1); + //IoUtil.copy(reader, new FileWriter(file1)); +// resultFile = IOUtils.toString(reader); + log.info("结果文件内容: {}", resultFile); + } catch (Exception e) { + log.warn("无法获取容器执行结果文件: {}", e.getMessage()); + } + + + } + + + @org.junit.Test + public void test2() throws InterruptedException, IOException { + this.exec("ls"); +// this.exec("cd /lib"); +// this.exec("ls"); + } + + /** + * https://blog.csdn.net/will0532/article/details/78335280 + * + * @param cmd + * @throws InterruptedException + * @see ExecStartCmdExec + */ + private void exec(String cmd) throws InterruptedException, IOException { + ExecCreateCmd execCreateCmd = dockerClient.execCreateCmd("5848fd613ea4"); + execCreateCmd.withAttachStdout(true).withAttachStdin(true).withAttachStderr(true).withTty(true).withCmd("/bin/sh"); + ExecCreateCmdResponse exec = execCreateCmd.exec(); + String execId = exec.getId(); + ExecStartCmd execStartCmd = dockerClient.execStartCmd(execId); + execStartCmd.withDetach(false).withTty(true); + + PipedInputStream in = new PipedInputStream(); + PipedOutputStream out = new PipedOutputStream(in); +// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(stringWriter); + //IoUtil.toStream(stringWriter.toString(), CharsetUtil.CHARSET_UTF_8); + execStartCmd.withStdIn(in); + InputStream stdin = execStartCmd.getStdin(); + System.out.println(stdin); + +// IoUtil.readLines(stdin, Charset.defaultCharset(), new LineHandler() { +// @Override +// public void handle(String line) { +// System.out.println(line); +// } +// }); + ThreadUtil.execute(() -> { + while (true) { + try { + out.write(StrUtil.bytes("ls \n")); + } catch (IOException e) { + e.printStackTrace(); + } + ThreadUtil.sleep(2, TimeUnit.SECONDS); + } + }); + InvocationBuilder.AsyncResultCallback resultCallback = execStartCmd.exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame frame) { + System.out.println(frame); + } + }); + resultCallback.awaitCompletion(); + } + + @org.junit.Test + public void testDockerfile() throws InterruptedException { + File dir = FileUtil.file("/Users/user/IdeaProjects/Jpom-demo-case/springboot-test-jar/"); + BuildImageCmd buildImageCmd = dockerClient.buildImageCmd(); + buildImageCmd + .withBaseDirectory(FileUtil.file(dir, "target/classes")) + .withDockerfile(FileUtil.file(dir, "Dockerfile")) +// .withQuiet() + .withTags(CollUtil.newHashSet("jpom-test2")); + buildImageCmd.exec(new InvocationBuilder.AsyncResultCallback() { + + + @Override + public void onNext(BuildResponseItem object) { + String stream = object.getStream(); + if (stream == null) { + String status = object.getStatus(); + if (status == null) { + return; + } + System.out.print(StrUtil.format("{} {} {}", status, object.getId(), object.getProgressDetail())); + } + System.out.print(stream); + } + }).awaitCompletion(); + } + + @org.junit.Test + public void testContainerCmd() { + CreateContainerCmd containerCmd = dockerClient.createContainerCmd("e6cf7db033e2"); + CreateContainerCmd createContainerCmd = containerCmd.withName("jpom-build"); + + HostConfig hostConfig = HostConfig.newHostConfig(); + PortBinding portBinding = PortBinding.parse("8084:8084"); + hostConfig.withPortBindings(portBinding); + createContainerCmd.withHostConfig(hostConfig); + CreateContainerResponse exec = containerCmd.exec(); + } + + @org.junit.Test + public void testin() { + InspectImageCmd inspectImageCmd = dockerClient.inspectImageCmd("e6cf7db033e2"); + InspectImageResponse inspectImageResponse = inspectImageCmd.exec(); + System.out.println(inspectImageResponse); + } + + @org.junit.Test + public void testinpull() throws InterruptedException { + //docker pull jpomdocker/jpom:latest + PullImageCmd pullImageCmd = dockerClient.pullImageCmd("jpomdocker/jpom:2.8.6"); +// pullImageCmd.withTag(); + pullImageCmd.exec(new InvocationBuilder.AsyncResultCallback() { + + @Override + public void onNext(PullResponseItem object) { + System.out.println(object); + } + + }).awaitCompletion(); + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestAuth.java b/modules/sub-plugin/docker-cli/src/test/java/TestAuth.java new file mode 100644 index 0000000000..af5c55b8d2 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestAuth.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import cn.hutool.core.util.StrUtil; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.api.model.AuthResponse; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * @author bwcx_jzy + * @since 2022/2/18 + */ +public class TestAuth { + + private DockerClient dockerClient; + private String containerId; + + private String node1 = "192.168.105.13"; + private String node2 = "192.168.105.177"; + private String node3 = "192.168.105.182"; + + private String nodeLt = "172.19.106.253"; + + @Before + public void beforeLocal() { + // + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + +// this.dockerClient = this.client(node1); +// dockerClient.pingCmd().exec(); + } + + private DockerClient client(String host) { + DefaultDockerClientConfig.Builder builder = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost("tcp://" + host + ":2375"); + if (StrUtil.equals(host, nodeLt)) { + builder.withDockerTlsVerify(true) + .withDockerCertPath("/Users/user/fsdownload/docker-ca"); + } + DockerClientConfig config = builder.build(); + + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) +// .connectionTimeout(Duration.ofSeconds(30)) +// .responseTimeout(Duration.ofSeconds(45)) + .build(); + DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient); + dockerClient.pingCmd().exec(); + return dockerClient; + } + + @Test + public void testNetwork2() { + DockerClient client = this.client("172.19.106.252"); + AuthConfig authConfig = client.authConfig(); + System.out.println(authConfig); + + AuthResponse authResponse = client.authCmd().withAuthConfig(authConfig).exec(); + System.out.println(authResponse); + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestDockerFIle.java b/modules/sub-plugin/docker-cli/src/test/java/TestDockerFIle.java new file mode 100644 index 0000000000..7c1753d2bb --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestDockerFIle.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.BuildImageCmd; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * @author bwcx_jzy + * @since 2022/1/27 + */ +public class TestDockerFIle { + + private DockerClient dockerClient; + private String containerId; + + @Before + public void before() { + DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder() + // .withDockerHost("tcp://192.168.163.11:2376").build(); +// .withApiVersion() + .withDockerHost("tcp://127.0.0.1:2375").build(); + + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) +// .connectionTimeout(Duration.ofSeconds(30)) +// .responseTimeout(Duration.ofSeconds(45)) + .build(); + this.dockerClient = DockerClientImpl.getInstance(config, httpClient); + dockerClient.pingCmd().exec(); + + // + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + } + + @Test + public void tset() { + BuildImageCmd buildImageCmd = dockerClient.buildImageCmd(); + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestFormatter.java b/modules/sub-plugin/docker-cli/src/test/java/TestFormatter.java new file mode 100644 index 0000000000..a33fe55825 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestFormatter.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import com.github.dockerjava.core.NameParser; +import org.junit.Test; + +import java.util.Formatter; + +/** + * @author bwcx_jzy + * @since 2022/2/7 + */ +public class TestFormatter { + + @Test + public void test() { + System.out.printf("${a}%n", "1"); + Formatter formatter = new Formatter(); + System.out.println(formatter.format("${a}", "1")); + } + + @Test + public void testTag() { + NameParser.ReposTag reposTag = NameParser.parseRepositoryTag("192.168.33.106:10087/library/sso:3.0.0.RELEASE"); + System.out.println(reposTag); + + reposTag = NameParser.parseRepositoryTag("sso:3.0.0.RELEASE"); + System.out.println(reposTag); + + reposTag = NameParser.parseRepositoryTag("sso"); + System.out.println(reposTag); + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestJson.java b/modules/sub-plugin/docker-cli/src/test/java/TestJson.java new file mode 100644 index 0000000000..0a3e3d1f59 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestJson.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONFactory; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.github.dockerjava.api.model.SwarmNodeState; +import com.github.dockerjava.api.model.SwarmNodeStatus; +import org.junit.Test; + +/** + * @author bwcx_jzy + * @since 2022/12/27 + */ +public class TestJson { + + static { + JSONFactory.setUseJacksonAnnotation(false); + JSON.config(JSONWriter.Feature.WriteEnumsUsingName); + System.out.println(JSON.isEnabled(JSONWriter.Feature.WriteEnumsUsingName)); + } + + @Test + public void test() { + SwarmNodeStatus swarmNodeStatus = new SwarmNodeStatus(); + swarmNodeStatus.withState(SwarmNodeState.DISCONNECTED); + + System.out.println(JSONObject.toJSONString(swarmNodeStatus)); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("state", SwarmNodeState.DOWN); + System.out.println(jsonObject); + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestLocal.java b/modules/sub-plugin/docker-cli/src/test/java/TestLocal.java new file mode 100644 index 0000000000..3021f4baad --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestLocal.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import cn.hutool.core.io.unit.DataSizeUtil; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.core.InvocationBuilder; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import org.dromara.jpom.DockerUtil; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +/** + * https://docs.docker.com/engine/api/v1.41/#operation/ContainerKill + *

+ * https://docs.docker.com/engine/api/v1.41/#operation/ContainerUpdate + * + * @author bwcx_jzy + * @since 2022/1/25 + */ +public class TestLocal { + private DockerClient dockerClient; + private AuthConfig authConfig; + + @Before + public void init() { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + + DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withRegistryUsername(null) + // .withDockerHost("tcp://192.168.163.11:2376").build(); +// .withApiVersion() +// .withDockerHost("tcp://127.0.0.1:2375") + .build(); + + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) +// .connectionTimeout(Duration.ofSeconds(30)) +// .responseTimeout(Duration.ofSeconds(45)) + .build(); + this.dockerClient = DockerClientImpl.getInstance(config, httpClient); + + // + authConfig = new AuthConfig(); + authConfig.withRegistryAddress("registry.cn-shanghai.aliyuncs.com"); + authConfig.withEmail("bwcx_jzy@163.com111"); + authConfig.withPassword("xxx"); + authConfig.withUsername("bwcx_jzy@163.com"); + } + + @Test + public void test() { + + dockerClient.pingCmd().exec(); + VersionCmd versionCmd = dockerClient.versionCmd(); + Version exec = versionCmd.exec(); + System.out.println(exec); + } + + @Test + public void tset2() throws InterruptedException { + StatsCmd statsCmd = dockerClient.statsCmd("socat"); + Statistics statistics = statsCmd.exec(new InvocationBuilder.AsyncResultCallback<>()).awaitResult(); + System.out.println(statistics); + System.out.println(JSONObject.toJSONString(statistics)); + } + + @Test + public void test3() { +// dockerClient.inspectContainerCmd("socat") + UpdateContainerCmd containerCmd = dockerClient.updateContainerCmd("socat"); +// containerCmd.withCpusetCpus("1"); +// containerCmd.withCpusetMems("1"); +// containerCmd.withCpuPeriod(1); +// containerCmd.withCpuQuota(1); +// containerCmd.withCpuShares(1); +// containerCmd.withBlkioWeight(1); +// containerCmd.withMemoryReservation(DataSizeUtil.parse("10M")); +// containerCmd.withKernelMemory(DataSizeUtil.parse("10M")); +// containerCmd.withMemory(DataSizeUtil.parse("10M")); +// containerCmd.withMemorySwap(DataSizeUtil.parse("10M")); + + + UpdateContainerResponse containerResponse = containerCmd.exec(); + System.out.println(containerResponse); + } + + @Test + public void testSize() { + System.out.println(DataSizeUtil.parse("-1")); + System.out.println(DataSizeUtil.parse("0")); + } + + @Test + public void test4() { + InspectContainerCmd socat = dockerClient.inspectContainerCmd("socat").withSize(true); + InspectContainerResponse exec = socat.exec(); + System.out.println(JSONObject.toJSONString(exec.getHostConfig(), JSONWriter.Feature.PrettyFormat)); + } + + @Test + public void testAuth() { + AuthConfig authConfig = dockerClient.authConfig(); + System.out.println(authConfig); + + // + // Info exec = dockerClient.infoCmd().exec(); + //System.out.println(JSONObject.toJSONString(exec)); + // + + AuthResponse authResponse = dockerClient.authCmd().withAuthConfig(authConfig).exec(); + System.out.println(authResponse); + } + + @Test + public void testPull() throws InterruptedException { + PullImageCmd imageCmd = dockerClient.pullImageCmd("registry.cn-shanghai.aliyuncs.com/jpom-demo/jpomtestdocker:1.0"); + AuthResponse authResponse = dockerClient.authCmd() + //.withAuthConfig(authConfig) + .exec(); + imageCmd.withAuthConfig(authConfig); + imageCmd.exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(PullResponseItem object) { + String responseItem = DockerUtil.parseResponseItem(object); + System.out.println(responseItem); + } + + }).awaitCompletion(); + + } + + @Test + public void testConfig() { + + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestSwarm.java b/modules/sub-plugin/docker-cli/src/test/java/TestSwarm.java new file mode 100644 index 0000000000..d4dcc935c1 --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestSwarm.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.core.command.RemoveSwarmNodeCmdImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * https://blog.csdn.net/qq_36609501/article/details/93138036 + *

+ * https://www.cnblogs.com/vinsent/p/11691562.html + *

+ * https://www.runoob.com/docker/docker-swarm.html + *

+ * https://zhuanlan.zhihu.com/p/22918583 + * + * @author bwcx_jzy + * @since 2022/2/13 + */ +@Slf4j +public class TestSwarm { + + private DockerClient dockerClient; + private String containerId; + + private String node1 = "192.168.105.13"; + private String node2 = "192.168.105.177"; + private String node3 = "192.168.105.182"; + + private String nodeLt = "172.19.106.253"; + + @Before + public void beforeLocal() { + // + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + +// this.dockerClient = this.client(node1); +// dockerClient.pingCmd().exec(); + } + + private DockerClient client(String host) { + DefaultDockerClientConfig.Builder builder = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost("tcp://" + host + ":2375"); + if (StrUtil.equals(host, nodeLt)) { + builder.withDockerTlsVerify(true) + .withDockerCertPath("/Users/user/fsdownload/docker-ca"); + } + DockerClientConfig config = builder.build(); + + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) +// .connectionTimeout(Duration.ofSeconds(30)) +// .responseTimeout(Duration.ofSeconds(45)) + .build(); + DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient); + dockerClient.pingCmd().exec(); + return dockerClient; + } + +// private DockerCmdExecFactory client2(String host) { +// Map map = new HashMap<>(10); +// map.put("dockerHost", "tcp://" + host + ":2375"); +// +// if (StrUtil.equals(host, nodeLt)) { +// map.put("dockerCertPath", "/Users/user/fsdownload/docker-ca"); +// } +// DockerCmdExecFactory factory = DockerUtil.buildJersey(map, 1); +// PingCmd.Exec pingCmdExec = factory.createPingCmdExec(); +// PingCmdImpl command = new PingCmdImpl(pingCmdExec); +// Void exec = pingCmdExec.exec(command); +// return factory; +// } + + @After + public void after() { + if (containerId == null) { + return; + } + // 清除容器 +// this.dockerClient.removeContainerCmd(containerId) +// .withRemoveVolumes(true) +// .withForce(true) +// .exec(); + } + + @Test + public void testInfo() { + SwarmSpec swarmSpec = new SwarmSpec(); +// swarmSpec.withDispatcher() + swarmSpec.withName("default"); + dockerClient.initializeSwarmCmd(swarmSpec).exec(); + } + + @Test + public void testInSpectSwarmCmd() { + this.dockerClient = this.client(node2); + Swarm exec = dockerClient.inspectSwarmCmd().exec(); + JSONObject toJSON = (JSONObject) JSON.toJSON(exec); + System.out.println(toJSON.toString(JSONWriter.Feature.PrettyFormat)); + } + + @Test + public void testListNodeSwarmCmd() { + this.dockerClient = this.client(nodeLt); + ListSwarmNodesCmd listSwarmNodesCmd = dockerClient.listSwarmNodesCmd(); + List exec = listSwarmNodesCmd.exec(); + JSONArray toJSON = (JSONArray) JSON.toJSON(exec); + System.out.println(toJSON.toString()); + } + + @Test + public void tsetInfo() { + this.dockerClient = this.client(node1); + InfoCmd infoCmd = dockerClient.infoCmd(); + Info exec = infoCmd.exec(); + JSONObject toJSON = (JSONObject) JSON.toJSON(exec); + System.out.println(toJSON.toString(JSONWriter.Feature.PrettyFormat)); + } + + @Test + public void tsetRemove() { + DockerClient client = this.client(nodeLt); + DockerCmdExecFactory dockerCmdExecFactory = (DockerCmdExecFactory) ReflectUtil.getFieldValue(client, "dockerCmdExecFactory"); + + + RemoveSwarmNodeCmdImpl removeSwarmNodeCmd = new RemoveSwarmNodeCmdImpl(dockerCmdExecFactory.removeSwarmNodeCmdExec(), "hvtr4gy520x67m97h2ds8wcnu"); + + removeSwarmNodeCmd.exec(); + } + + @Test + public void update() { + DockerClient client = this.client(node1); + + + String nodeId = "rk2gxpql2449t0s1ymtivtyoy"; + List nodes = client.listSwarmNodesCmd().withIdFilter(CollUtil.newArrayList(nodeId)).exec(); + System.out.println(nodes); + SwarmNode swarmNode = CollUtil.getFirst(nodes); + UpdateSwarmNodeCmd swarmNodeCmd = client.updateSwarmNodeCmd(); + + swarmNodeCmd.withSwarmNodeId(nodeId); + SwarmNodeSpec swarmNodeSpec = new SwarmNodeSpec(); + swarmNodeSpec.withAvailability(SwarmNodeAvailability.PAUSE); + swarmNodeSpec.withRole(SwarmNodeRole.WORKER); + swarmNodeCmd.withSwarmNodeSpec(swarmNodeSpec); + + swarmNodeCmd.withVersion(swarmNode.getVersion().getIndex()); + + swarmNodeCmd.exec(); + } + + @Test + public void testJoin() { + + JoinSwarmCmd joinSwarmCmd = dockerClient.joinSwarmCmd() + .withRemoteAddrs(CollUtil.newArrayList(node2)) + .withJoinToken("SWMTKN-1-1g25kun6dsy76akteqxwww87d5i0y7dnbn8sy38x7asv5fkpre-0btwn3t33sjzi6ofzuwei5c1f"); + joinSwarmCmd.exec(); + } + + @Test + public void testService() { + DockerClient client = this.client(node1); + ListServicesCmd listServicesCmd = client.listServicesCmd(); + + List exec = listServicesCmd.exec(); + JSONArray toJSON = (JSONArray) JSON.toJSON(exec); + System.out.println(toJSON.toString()); + } + + @Test + public void testServiceInspect() { + DockerClient client = this.client(node1); + InspectServiceCmd inspectServiceCmd = client.inspectServiceCmd("whyf2udreftogvwzwf3pnl32g"); + + Service exec = inspectServiceCmd.exec(); + + System.out.println(exec); + } + + @Test + public void testServiceLog() throws InterruptedException { + DockerClient client = this.client(node1); + LogSwarmObjectCmd swarmObjectCmd = client.logServiceCmd("esjx0f126tvvicfizymdxymxq"); + swarmObjectCmd.withDetails(true); + swarmObjectCmd.withStderr(true); + swarmObjectCmd.withStdout(true); + swarmObjectCmd.exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame object) { + String s = new String(object.getPayload(), StandardCharsets.UTF_8); + System.out.print(s); + } + }).awaitCompletion(); + } + + @Test + public void tsetTask() { + DockerClient client = this.client(node1); + ListTasksCmd listTasksCmd = client.listTasksCmd(); + listTasksCmd.withServiceFilter("esjx0f126tvvicfizymdxymxq"); + List exec = listTasksCmd.exec(); + System.out.println(exec); + } + + @Test + public void updateServiceCmd() { + DockerClient client = this.client(node1); + ServiceSpec serviceSpec = new ServiceSpec(); + + { + ServiceModeConfig serviceModeConfig = new ServiceModeConfig(); + // 副本数 + ServiceReplicatedModeOptions serviceReplicatedModeOptions = new ServiceReplicatedModeOptions(); + serviceReplicatedModeOptions.withReplicas(5); + serviceModeConfig.withReplicated(serviceReplicatedModeOptions); + // + // serviceModeConfig.withGlobal(new ServiceGlobalModeOptions()); + serviceSpec.withMode(serviceModeConfig); + } + { + EndpointSpec endpointSpec = new EndpointSpec(); + endpointSpec.withMode(EndpointResolutionMode.VIP); + PortConfig config = new PortConfig(); + config.withName("s"); + config.withProtocol(PortConfigProtocol.TCP); + config.withPublishedPort(80); + config.withTargetPort(80); + config.withPublishMode(PortConfig.PublishMode.host); + endpointSpec.withPorts(CollUtil.newArrayList(config)); + serviceSpec.withEndpointSpec(endpointSpec); + } + { + UpdateConfig updateConfig = new UpdateConfig(); + + serviceSpec.withUpdateConfig(updateConfig); + } + TaskSpec taskSpec = new TaskSpec(); + ContainerSpec containerSpec = new ContainerSpec(); +// containerSpec.with + taskSpec.withContainerSpec(containerSpec); + serviceSpec.withTaskTemplate(taskSpec); + // +// serviceSpec.withUpdateConfig() + // + UpdateServiceCmd updateServiceCmd = client.updateServiceCmd("esjx0f126tvvicfizymdxymxq", serviceSpec); + updateServiceCmd.exec(); + } + + @Test + public void updateServiceCmd3() { + DockerClient client = this.client(node1); + RemoveServiceCmd removeServiceCmd = client.removeServiceCmd("esjx0f126tvvicfizymdxymxq"); + removeServiceCmd.exec(); + } + + @Test + public void updateServiceCmd2() { + DockerClient client = this.client(node1); + ServiceSpec serviceSpec = new ServiceSpec(); + serviceSpec.withName("helloworld"); + { + ServiceModeConfig serviceModeConfig = new ServiceModeConfig(); + ServiceReplicatedModeOptions serviceReplicatedModeOptions = new ServiceReplicatedModeOptions(); + serviceReplicatedModeOptions.withReplicas(1); + serviceModeConfig.withReplicated(serviceReplicatedModeOptions); + // + // serviceModeConfig.withGlobal(new ServiceGlobalModeOptions()); + serviceSpec.withMode(serviceModeConfig); + } + { + UpdateConfig updateConfig = new UpdateConfig(); + + serviceSpec.withUpdateConfig(updateConfig); + } + { + TaskSpec taskSpec = new TaskSpec(); + ContainerSpec containerSpec = new ContainerSpec(); + containerSpec.withImage("alpine"); + containerSpec.withCommand(CollUtil.newArrayList("ping docker.com")); + + taskSpec.withContainerSpec(containerSpec); + serviceSpec.withTaskTemplate(taskSpec); + } +// serviceSpec.withRollbackConfig() + serviceSpec.withEndpointSpec(new EndpointSpec()); + String serviceId = "tf2r29awevz2fcprybv6cvlm9"; + InspectServiceCmd inspectServiceCmd = client.inspectServiceCmd(serviceId); + Service service = inspectServiceCmd.exec(); + UpdateServiceCmd updateServiceCmd = client.updateServiceCmd(serviceId, serviceSpec); + updateServiceCmd.withVersion(service.getVersion().getIndex()); + updateServiceCmd.exec(); + } + + @Test + public void testNetwork() { + DockerClient client = this.client(node1); + ListNetworksCmd listNetworksCmd = client.listNetworksCmd(); + List exec = listNetworksCmd.exec(); + System.out.println(exec); + } + + @Test + public void testUpdate() { +// String serviceId = "eo0l430mf2v524rlgn5550d7w"; +// eo0l430mf2v524rlgn5550d7w + ServiceSpec serviceSpec = new ServiceSpec(); + DockerClient client = this.client("172.19.106.253"); +// TaskSpec taskSpec = new TaskSpec(); +// ContainerSpec containerSpec = new ContainerSpec(); +// containerSpec.withImage("jpom-test:1.0"); +// taskSpec.withContainerSpec(containerSpec); +// serviceSpec.withTaskTemplate(taskSpec); + String name = "jpom-test"; + serviceSpec.withName(name); + // + InspectServiceCmd inspectServiceCmd = client.inspectServiceCmd(name); + Service service = inspectServiceCmd.exec(); + ServiceSpec spec = service.getSpec(); + TaskSpec taskTemplate = spec.getTaskTemplate(); + ContainerSpec templateContainerSpec = taskTemplate.getContainerSpec(); + templateContainerSpec.withImage("jpom-test"); + // + // + UpdateServiceCmd updateServiceCmd = client.updateServiceCmd(name, spec); + updateServiceCmd.withVersion(service.getVersion().getIndex()); + updateServiceCmd.exec(); + } + + @Test + public void testNetwork2() { + DockerClient client = this.client("172.19.106.252"); + InspectNetworkCmd inspectNetworkCmd = client.inspectNetworkCmd() + .withNetworkId("903gj9lnisp5dbf77zpzti35x"); + Network exec = inspectNetworkCmd.exec(); + System.out.println(exec); + } +} diff --git a/modules/sub-plugin/docker-cli/src/test/java/TestTsl.java b/modules/sub-plugin/docker-cli/src/test/java/TestTsl.java new file mode 100644 index 0000000000..5513b9296e --- /dev/null +++ b/modules/sub-plugin/docker-cli/src/test/java/TestTsl.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.VersionCmd; +import com.github.dockerjava.api.model.Version; +import org.dromara.jpom.DockerUtil; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/1/26 + */ +public class TestTsl { + + @Test + public void test() { + + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + logger.setLevel(Level.INFO); + + String dockerHost = "tcp://172.19.106.253:2375"; + String dockerCertPath = "/Users/user/fsdownload/docker-ca"; + Map map = new HashMap<>(); + map.put("dockerHost", dockerHost); + map.put("dockerCertPath", dockerCertPath); + DockerClient dockerClient = DockerUtil.get(map); + + dockerClient.pingCmd().exec(); + VersionCmd versionCmd = dockerClient.versionCmd(); + Version exec = versionCmd.exec(); + System.out.println(exec); + + } +} diff --git a/modules/sub-plugin/email/pom.xml b/modules/sub-plugin/email/pom.xml new file mode 100644 index 0000000000..d9042e404c --- /dev/null +++ b/modules/sub-plugin/email/pom.xml @@ -0,0 +1,101 @@ + + + + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + 4.0.0 + email + plugin-email + + + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + + com.sun.mail + javax.mail + 1.6.2 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + false + + script/release.xml + + + + + + + + make-assembly + package + + single + + + + + + + + diff --git a/modules/sub-plugin/email/script/release.xml b/modules/sub-plugin/email/script/release.xml new file mode 100644 index 0000000000..799a92f801 --- /dev/null +++ b/modules/sub-plugin/email/script/release.xml @@ -0,0 +1,43 @@ + + + + release + false + + jar + + + + + / + false + true + runtime + + + com.sun.mail:javax.mail + + + + + + + + ${project.build.directory}/classes + / + + + + diff --git a/modules/sub-plugin/email/src/main/java/org/dromara/jpom/email/DefaultEmailPluginImpl.java b/modules/sub-plugin/email/src/main/java/org/dromara/jpom/email/DefaultEmailPluginImpl.java new file mode 100644 index 0000000000..f362376b79 --- /dev/null +++ b/modules/sub-plugin/email/src/main/java/org/dromara/jpom/email/DefaultEmailPluginImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.email; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.mail.MailAccount; +import cn.hutool.extra.mail.MailException; +import cn.hutool.extra.mail.MailUtil; +import cn.keepbx.jpom.plugins.PluginConfig; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.plugin.IDefaultPlugin; + +import javax.mail.Session; +import javax.mail.Transport; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2021/12/22 + */ +@PluginConfig(name = "email") +@Slf4j +public class DefaultEmailPluginImpl implements IDefaultPlugin { + + @Override + public Object execute(Object main, Map parameter) throws Exception { + if (main instanceof JSONObject) { + MailAccount mailAccount = getAccount(main); + // + String toEmail = (String) parameter.get("toEmail"); + String title = (String) parameter.get("title"); + String context = (String) parameter.get("context"); + List list = StrUtil.split(toEmail, StrUtil.COMMA, true, true); + try { + return MailUtil.send(mailAccount, list, title, context, false); + } catch (MailException mailException) { + Exception cause = (Exception) mailException.getCause(); + if (cause != null) { + throw cause; + } + throw mailException; + } + } else if (main instanceof String && StrUtil.equals("checkInfo", main.toString())) { + try { + Object data = parameter.get("data"); + MailAccount account = this.getAccount(data); + Session session = MailUtil.getSession(account, false); + try (Transport transport = session.getTransport("smtp")) { + transport.connect(); + } + return true; + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.check_email_error.636c"), e.getMessage()); + return false; + } + } + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.unsupported_type_with_colon2.7de2") + main); + } + + /** + * 创建邮件对象 + * + * @param main 传人参数 + * @return MailAccount + */ + private MailAccount getAccount(Object main) { + if (!(main instanceof JSONObject)) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.plugin_parameter_incorrect.a355")); + } + JSONObject data = (JSONObject) main; + MailAccount mailAccount = new MailAccount(); + String user = data.getString("user"); + String pass = data.getString("pass"); + String from = data.getString("from"); + Integer port = data.getInteger("port"); + String host = data.getString("host"); + mailAccount.setUser(user); + mailAccount.setPass(pass); + mailAccount.setFrom(from); + mailAccount.setPort(port); + mailAccount.setHost(host); + // + Integer timeout = data.getInteger("timeout"); + timeout = ObjectUtil.defaultIfNull(timeout, 10); + timeout = Math.max(3, timeout); + mailAccount.setTimeout(timeout * 1000); + mailAccount.setConnectionTimeout(timeout * 1000); + boolean sslEnable = data.getBooleanValue("sslEnable"); + // + mailAccount.setSslEnable(sslEnable); + //Integer socketFactoryPort = data.getInteger("socketFactoryPort"); +// if (socketFactoryPort != null) { + if (sslEnable) { + mailAccount.setSocketFactoryPort(port); + } +// } + mailAccount.setAuth(true); + return mailAccount; + } +} diff --git a/modules/sub-plugin/encrypt/pom.xml b/modules/sub-plugin/encrypt/pom.xml new file mode 100644 index 0000000000..87057fb8b6 --- /dev/null +++ b/modules/sub-plugin/encrypt/pom.xml @@ -0,0 +1,61 @@ + + + + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + 4.0.0 + encrypt + plugin-encrypt + + + + cn.hutool + hutool-crypto + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + + + diff --git a/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/AESEncryptor.java b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/AESEncryptor.java new file mode 100644 index 0000000000..cc38128e38 --- /dev/null +++ b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/AESEncryptor.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.encrypt; + +import cn.hutool.core.util.SystemPropsUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.AES; + +/** + * aes + * + * @author loyal.f + * @since 2023/3/9 + */ +public class AESEncryptor implements Encryptor { + + private final byte[] keyByte; + + private static volatile AESEncryptor singleton; + + private AESEncryptor(String key) { + //构造器私有化,防止new,导致多个实例 + this.keyByte = key.getBytes(); + } + + public static Encryptor getInstance() { + //向外暴露一个静态的公共方法 getInstance + //第一层检查 + if (singleton == null) { + //同步代码块 + synchronized (AESEncryptor.class) { + //第二层检查 + if (singleton == null) { + String aesKey = SystemPropsUtil.get("JPOM_ENCRYPT_AES_KEY", "Djnn3runZBzdv9Nv"); + singleton = new AESEncryptor(aesKey); + } + } + } + return singleton; + } + + + @Override + public String name() { + return "aes"; + } + + @Override + public String encrypt(String input) throws Exception { + if (input == null) { + return null; + } + AES aes = SecureUtil.aes(keyByte); + return aes.encryptHex(input); + } + + @Override + public String decrypt(String input) throws Exception { + if (input == null) { + return null; + } + AES aes = SecureUtil.aes(keyByte); + return aes.decryptStr(input); + } + +} diff --git a/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/BASE64Encryptor.java b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/BASE64Encryptor.java new file mode 100644 index 0000000000..82429f9418 --- /dev/null +++ b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/BASE64Encryptor.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.encrypt; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.CharsetUtil; + +/** + * base64 + * + * @author loyal.f + * @since 2023/3/9 + */ +public class BASE64Encryptor implements Encryptor { + + private static volatile BASE64Encryptor singleton; + + private BASE64Encryptor() { + //构造器私有化,防止new,导致多个实例 + } + + public static Encryptor getInstance() { + //向外暴露一个静态的公共方法 getInstance + //第一层检查 + if (singleton == null) { + //同步代码块 + synchronized (BASE64Encryptor.class) { + //第二层检查 + if (singleton == null) { + singleton = new BASE64Encryptor(); + } + } + } + return singleton; + } + + @Override + public String name() { + return "base64"; + } + + @Override + public String encrypt(String input) { + if (input == null) { + return null; + } + return Base64.encode(input, CharsetUtil.CHARSET_UTF_8); + } + + @Override + public String decrypt(String input) { + if (input == null) { + return null; + } + return Base64.decodeStr(input); + } +} diff --git a/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/EncryptFactory.java b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/EncryptFactory.java new file mode 100644 index 0000000000..bd56059306 --- /dev/null +++ b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/EncryptFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.encrypt; + +import java.security.NoSuchAlgorithmException; + +/** + * @author loyal.f + * @since 2023/3/9 + */ +public class EncryptFactory { + + public static Encryptor createEncryptor(Integer type) throws NoSuchAlgorithmException { + switch (type) { + case 0: + return NotEncryptor.getInstance(); + case 1: + return BASE64Encryptor.getInstance(); + case 2: + return AESEncryptor.getInstance(); + default: + throw new NoSuchAlgorithmException("Unsupported encrypt type"); + } + } +} diff --git a/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/Encryptor.java b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/Encryptor.java new file mode 100644 index 0000000000..baeddcd7ce --- /dev/null +++ b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/Encryptor.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.encrypt; + +/** + * @author loyal.f + * @since 2023/3/9 + */ +public interface Encryptor { + + + /** + * 加密方法 + * + * @return 名称 + */ + String name(); + + /** + * 加密 + * + * @param input 传入的测试 + * @return 加密后的字符串 + * @throws Exception 异常 + */ + String encrypt(String input) throws Exception; + + /** + * 解密 + * + * @param input 要解密的密文 + * @return 解密后的明文 + * @throws Exception 异常 + */ + String decrypt(String input) throws Exception; + +} diff --git a/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/NotEncryptor.java b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/NotEncryptor.java new file mode 100644 index 0000000000..ca621bffa8 --- /dev/null +++ b/modules/sub-plugin/encrypt/src/main/java/org/dromara/jpom/encrypt/NotEncryptor.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.encrypt; + +/** + * @author loyal.f + * @since 2023/3/9 + */ +public class NotEncryptor implements Encryptor { + + private static volatile NotEncryptor singleton; + + private NotEncryptor() { + //构造器私有化,防止new,导致多个实例 + } + + public static Encryptor getInstance() { + //向外暴露一个静态的公共方法 getInstance + //第一层检查 + if (singleton == null) { + //同步代码块 + synchronized (NotEncryptor.class) { + //第二层检查 + if (singleton == null) { + singleton = new NotEncryptor(); + } + } + } + return singleton; + } + + @Override + public String name() { + return "no"; + } + + @Override + public String encrypt(String input) { + return input; + } + + @Override + public String decrypt(String input) throws Exception { + return input; + } +} diff --git a/modules/sub-plugin/git-clone/pom.xml b/modules/sub-plugin/git-clone/pom.xml new file mode 100644 index 0000000000..a4b8c0bf26 --- /dev/null +++ b/modules/sub-plugin/git-clone/pom.xml @@ -0,0 +1,117 @@ + + + + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + 4.0.0 + git-clone + plugin-git-clone + + 8 + 8 + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + com.github.mwiede + jsch + + + + org.bouncycastle + bcprov-jdk18on + + + + org.eclipse.jgit + org.eclipse.jgit.ssh.jsch + 5.13.3.202401111512-r + + + com.jcraft + jsch + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + false + + script/release.xml + + + + + + + + make-assembly + package + + single + + + + + + + diff --git a/modules/sub-plugin/git-clone/script/release.xml b/modules/sub-plugin/git-clone/script/release.xml new file mode 100644 index 0000000000..c30d078f22 --- /dev/null +++ b/modules/sub-plugin/git-clone/script/release.xml @@ -0,0 +1,43 @@ + + + + release + false + + jar + + + + + / + false + true + runtime + + + org.eclipse.jgit:org.eclipse.jgit.ssh.jsch + + + + + + + + ${project.build.directory}/classes + / + + + + diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/AbstractGitProcess.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/AbstractGitProcess.java new file mode 100644 index 0000000000..add4bea832 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/AbstractGitProcess.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +import java.io.File; +import java.util.Map; + +/** + * GIt执行基类 + *
+ * Created By Hong on 2023/3/31 + * + * @author Hong + **/ +@Slf4j +public abstract class AbstractGitProcess implements GitProcess { + + private final IWorkspaceEnvPlugin workspaceEnvPlugin; + protected final Map parameter; + + protected AbstractGitProcess(IWorkspaceEnvPlugin workspaceEnvPlugin, Map parameter) { + this.workspaceEnvPlugin = workspaceEnvPlugin; + this.parameter = decryptParameter(parameter); + } + + /** + * 解密参数 + * + * @param parameter 参数 + */ + protected Map decryptParameter(Map parameter) { + try { + parameter.put("password", workspaceEnvPlugin.convertRefEnvValue(parameter, "password")); + parameter.put("username", workspaceEnvPlugin.convertRefEnvValue(parameter, "username")); + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.decrypt_parameter_failure.d10a"), e); + } + return parameter; + } + + + /** + * 获取保存路径 + */ + protected File getSaveFile() { + return (File) parameter.get("savePath"); + } + + /** + * 获取分支Name + */ + protected String getBranchName() { + return (String) parameter.get("branchName"); + } + + /** + * 获取TagName + */ + protected String getTagName() { + return (String) parameter.get("tagName"); + } +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/DefaultGitPluginImpl.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/DefaultGitPluginImpl.java new file mode 100644 index 0000000000..35a3582a35 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/DefaultGitPluginImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.keepbx.jpom.plugins.PluginConfig; +import org.eclipse.jgit.api.Git; + +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/2/22 + */ +@PluginConfig(name = "git-clone") +public class DefaultGitPluginImpl implements IWorkspaceEnvPlugin { + + @Override + public Object execute(Object main, Map parameter) throws Exception { + String type = main.toString(); + GitProcess gitProcess = GitProcessFactory.get(parameter, this); + switch (type) { + case "branchAndTagList": + return gitProcess.branchAndTagList(); + case "pull": { + return gitProcess.pull(); + } + case "pullByTag": { + return gitProcess.pullByTag(); + } + case "systemGit": { + return GitProcessFactory.existsSystemGit(); + } + default: + break; + } + return null; + } + + @Override + public void close() throws Exception { + Git.shutdown(); + } +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/GitProcess.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/GitProcess.java new file mode 100644 index 0000000000..a5c644aae9 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/GitProcess.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.lang.Tuple; + +/** + * Git处理 + *
+ * Created By Hong on 2023/3/31 + * + * @author Hong + */ +public interface GitProcess { + + /** + * 分支和标签列表 + * + * @return tuple + * @throws Exception 异常 + */ + Tuple branchAndTagList() throws Exception; + + /** + * 拉取指定分支 + * + * @return 拉取结果 + * @throws Exception 异常 + */ + String[] pull() throws Exception; + + /** + * 拉取指定标签 + * + * @return 拉取结果 + * @throws Exception 异常 + */ + String[] pullByTag() throws Exception; + +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/GitProcessFactory.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/GitProcessFactory.java new file mode 100644 index 0000000000..6364c90c15 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/GitProcessFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.OsInfo; +import cn.hutool.system.SystemUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.CommandUtil; + +import java.util.Map; + +/** + * GIt执行器 + *
+ * Created By Hong on 2023/3/31 + * + * @author Hong + **/ +@Slf4j +public class GitProcessFactory { + + private static final String WIN_EXISTS_GIT = "where git"; + private static final String LINUX_EXISTS_GIT = "which git"; + + private static Boolean result; + + private static final String DEFAULT_GIT_PROCESS = "JGit"; + private static final String SYSTEM_GIT_PROCESS = "SystemGit"; + + public static GitProcess get(Map parameter, IWorkspaceEnvPlugin workspaceEnvPlugin) { + String processType = (String) parameter.getOrDefault("gitProcessType", DEFAULT_GIT_PROCESS); + if (SYSTEM_GIT_PROCESS.equalsIgnoreCase(processType) && GitProcessFactory.existsSystemGit()) { + return new SystemGitProcess(workspaceEnvPlugin, parameter); + } else { + return new JGitProcess(workspaceEnvPlugin, parameter); + } + } + + + /** + * 操作系统是否有GIT环境 + */ + public static boolean existsSystemGit() { + if (result == null) { + result = existsSystemGit2(); + } + return result; + } + + /** + * 操作系统是否有GIT环境 + */ + private static boolean existsSystemGit2() { + String result; + OsInfo osInfo = SystemUtil.getOsInfo(); + if (osInfo.isWindows()) { + result = CommandUtil.execSystemCommand(WIN_EXISTS_GIT); + if (StrUtil.contains(result, ".exe")) { + log.info(I18nMessageUtil.get("i18n.git_installation_location.7984"), result); + return true; + } + } else if (osInfo.isLinux() || osInfo.isMac()) { + result = CommandUtil.execSystemCommand(LINUX_EXISTS_GIT); + if (StrUtil.containsAny(result, "no git", "not found")) { + return false; + } + log.info(I18nMessageUtil.get("i18n.git_installation_location.7984"), result); + return true; + } else { + log.warn(I18nMessageUtil.get("i18n.unsupported_system_type_with_placeholder.d5cc"), osInfo.getName()); + return false; + } + return false; + } + +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitOldUtil.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitOldUtil.java new file mode 100644 index 0000000000..af89763e5f --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitOldUtil.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +//package org.dromara.jpom.plugin; +// +//import cn.hutool.core.io.FileUtil; +//import cn.hutool.core.lang.Tuple; +//import cn.hutool.crypto.SecureUtil; +//import org.dromara.jpom.system.JpomRuntimeException; +//import org.eclipse.jgit.api.CloneCommand; +//import org.eclipse.jgit.api.Git; +//import org.eclipse.jgit.api.ListBranchCommand; +//import org.eclipse.jgit.api.PullCommand; +//import org.eclipse.jgit.api.errors.GitAPIException; +//import org.eclipse.jgit.api.errors.TransportException; +//import org.eclipse.jgit.lib.Constants; +//import org.eclipse.jgit.lib.Ref; +// +//import java.io.File; +//import java.io.IOException; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Map; +// +///** +// * 兼容低版本的分支获取 +// * +// * @author bwcx_jzy +// * @since 2024/4/12 +// */ +//public class JGitOldUtil { +// +// /** +// * 删除重新clone +// * +// * @param url url +// * @param file 文件 +// * @param parameter 参数 +// * @return git +// * @throws GitAPIException api +// * @throws IOException 删除文件失败 +// */ +// private static Git reClone(String url, Map parameter, File file) throws GitAPIException, IOException { +// if (!FileUtil.clean(file)) { +// FileUtil.del(file.toPath()); +// //throw new IOException("del error:" + file.getPath()); +// } +// CloneCommand cloneCommand = Git.cloneRepository(); +// +// CloneCommand command = cloneCommand.setURI(url) +// .setDirectory(file); +// JGitUtil.setCredentials(command, parameter); +// return command +// .call(); +// } +// +// private static Git initGit(String url, Map parameter, File file) throws IOException, GitAPIException { +// Git git; +// if (FileUtil.file(file, Constants.DOT_GIT).exists()) { +// if (JGitUtil.checkRemoteUrl(url, file)) { +// git = Git.open(file); +// // +// PullCommand pull = git.pull(); +// JGitUtil.setCredentials(pull, parameter); +// pull.call(); +// } else { +// git = reClone(url, parameter, file); +// } +// } else { +// git = reClone(url, parameter, file); +// } +// return git; +// } +// +// /** +// * 获取仓库远程的所有分支 +// * +// * @param url 远程url +// * @param file 仓库clone到本地的文件夹 +// * @param parameter 参数 +// * @return list +// * @throws GitAPIException api +// * @throws IOException IO +// */ +// private static Tuple branchList(String url, Map parameter, File file) throws Exception { +// try (Git git = initGit(url, parameter, file)) { +// // +// List list = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call(); +// List all = new ArrayList<>(list.size()); +// +// list.forEach(ref -> { +// String name = ref.getName(); +// if (name.startsWith(Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME)) { +// all.add(name.substring((Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME).length() + 1)); +// } +// }); +// List call = git.tagList().call(); +// List tag = new ArrayList<>(call.size()); +// call.forEach(ref -> { +// String name = ref.getName(); +// if (name.startsWith(Constants.R_TAGS)) { +// tag.add(name.substring((Constants.R_TAGS).length())); +// } +// }); +// return new Tuple(all, tag); +// } catch (TransportException t) { +// JGitUtil.checkTransportException(t, file, null); +// } +// return null; +// } +// +// public static Tuple getBranchList(String url, Map parameter) throws Exception { +// // 生成临时路径 +// String tempId = SecureUtil.md5(url); +// File tmpDir = FileUtil.getTmpDir(); +// File gitFile = FileUtil.file(tmpDir, "jpom", "git-temp", tempId); +// try { +// Tuple list = branchList(url, parameter, gitFile); +// if (list == null) { +// throw new JpomRuntimeException("该仓库还没有任何分支"); +// } +// return list; +// } catch (org.eclipse.jgit.errors.RepositoryNotFoundException ignored) { +// FileUtil.del(gitFile); +// } +// return null; +// } +//} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitProcess.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitProcess.java new file mode 100644 index 0000000000..28acaa43e5 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitProcess.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.lang.Tuple; + +import java.io.PrintWriter; +import java.util.Map; + +/** + * JGit操作 + *
+ * Created By Hong on 2023/3/31 + * + * @author Hong + **/ +public class JGitProcess extends AbstractGitProcess { + + protected JGitProcess(IWorkspaceEnvPlugin workspaceEnvPlugin, Map parameter) { + super(workspaceEnvPlugin, parameter); + } + + @Override + public Tuple branchAndTagList() throws Exception { + return JGitUtil.getBranchAndTagList(parameter); + } + + @Override + public String[] pull() throws Exception { + PrintWriter printWriter = (PrintWriter) parameter.get("logWriter"); + return JGitUtil.checkoutPull(parameter, getSaveFile(), getBranchName(), printWriter); + } + + @Override + public String[] pullByTag() throws Exception { + PrintWriter printWriter = (PrintWriter) parameter.get("logWriter"); + return JGitUtil.checkoutPullTag(parameter, getSaveFile(), getTagName(), printWriter); + } +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitUtil.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitUtil.java new file mode 100644 index 0000000000..3cf91a5cbf --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/JGitUtil.java @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.VersionComparator; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.StrUtil; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import lombok.Lombok; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.eclipse.jgit.api.*; +import org.eclipse.jgit.api.errors.CheckoutConflictException; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoHeadException; +import org.eclipse.jgit.api.errors.TransportException; +import org.eclipse.jgit.errors.NoRemoteRepositoryException; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; +import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.*; +import org.eclipse.jgit.util.FS; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * git工具 + *

+ * https://developer.aliyun.com/ask/275691 + *

+ * https://github.com/centic9/jgit-cookbook + * + * @author bwcx_jzy + * @author Hotstrip + * add git with ssh key to visit repository + * @since 2019/7/15 + **/ +public class JGitUtil { + + /** + * 检查本地的remote是否存在对应的url + * + * @param url 要检查的url + * @param file 本地仓库文件 + * @return true 存在对应url + * @throws IOException IO + * @throws GitAPIException E + */ + public static boolean checkRemoteUrl(String url, File file) throws IOException, GitAPIException { + try (Git git = Git.open(file)) { + RemoteListCommand remoteListCommand = git.remoteList(); + boolean urlTrue = false; + List list = remoteListCommand.call(); + end: + for (RemoteConfig remoteConfig : list) { + for (URIish urIish : remoteConfig.getURIs()) { + if (urIish.toString().equals(url)) { + urlTrue = true; + break end; + } + } + } + return urlTrue; + } + } + + /** + * 检查本地的仓库是否存在对应的分支 + * + * @param branchName 要检查的 branchName + * @param file 本地仓库文件 + * @return true 存在对应url + * @throws IOException IO + * @throws GitAPIException E + */ + private static boolean checkBranchName(String branchName, File file) throws IOException, GitAPIException { + try (Git pullGit = Git.open(file)) { + // 判断本地是否存在对应分支 + List list = pullGit.branchList().call(); + for (Ref ref : list) { + String name = ref.getName(); + if (StrUtil.equals(name, Constants.R_HEADS + branchName)) { + return true; + } + } + } + return false; + } + + /** + * 删除重新clone + * + * @param parameter 参数 + * @param branchName 分支 + * @param tagName 标签 + * @param printWriter 日志流 + * @param file 文件 + * @return git + * @throws GitAPIException api + * @throws IOException 删除文件失败 + */ + private static Git reClone(Map parameter, String branchName, String tagName, File file, PrintWriter printWriter) throws GitAPIException, IOException { + println(printWriter, StrUtil.EMPTY); + println(printWriter, "JGit: Automatically re-clones repositories"); + if (!FileUtil.clean(file)) { + FileUtil.del(file.toPath()); + } + CloneCommand cloneCommand = Git.cloneRepository(); + if (printWriter != null) { + Integer progressRatio = (Integer) parameter.get("reduceProgressRatio"); + cloneCommand.setProgressMonitor(new SmallTextProgressMonitor(printWriter, progressRatio)); + } + if (branchName != null) { + cloneCommand.setBranch(Constants.R_HEADS + branchName); + cloneCommand.setBranchesToClone(Collections.singletonList(Constants.R_HEADS + branchName)); + } + if (tagName != null) { + cloneCommand.setBranch(Constants.R_TAGS + tagName); + } + String url = (String) parameter.get("url"); + CloneCommand command = cloneCommand.setURI(url) + .setDirectory(file) + .setCloneSubmodules(true); + // 设置凭证 + setCredentials(command, parameter); + return command.call(); + } + + /** + * 设置仓库凭证 + * + * @param transportCommand git 相关操作 + * @param parameter 参数 + */ + public static void setCredentials(TransportCommand transportCommand, Map parameter) { + // 设置超时时间 秒 + Integer timeout = (Integer) parameter.get("timeout"); + // 设置账号密码 + Integer protocol = (Integer) parameter.get("protocol"); + String username = (String) parameter.get("username"); + String password = StrUtil.emptyToDefault((String) parameter.get("password"), StrUtil.EMPTY); + if (protocol == 0) { + // http + CredentialsProvider credentialsProvider = new SslVerifyUsernamePasswordCredentialsProvider(username, password); + transportCommand.setCredentialsProvider(credentialsProvider); + // + Optional.ofNullable(timeout) + .map(integer -> integer <= 0 ? null : integer) + .ifPresent(transportCommand::setTimeout); + } else if (protocol == 1) { + // ssh + //File rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModel); + File rsaFile = (File) parameter.get("rsaFile"); + transportCommand.setTransportConfigCallback(transport -> { + SshTransport sshTransport = (SshTransport) transport; + sshTransport.setSshSessionFactory(new JschConfigSessionFactory() { + @Override + protected void configure(OpenSshConfig.Host hc, Session session) { + session.setConfig("StrictHostKeyChecking", "no"); + // ssh 需要单独设置超时 + Optional.ofNullable(timeout) + .map(integer -> integer <= 0 ? null : integer) + .ifPresent(integer -> { + try { + session.setTimeout(integer * 1000); + } catch (JSchException e) { + throw Lombok.sneakyThrow(e); + } + }); + } + + @Override + protected JSch createDefaultJSch(FS fs) throws JSchException { + JSch jSch = super.createDefaultJSch(fs); + if (rsaFile == null) { + return jSch; + } + // 添加私钥文件 + //String rsaPass = repositoryModel.getPassword(); + if (StrUtil.isEmpty(password)) { + jSch.addIdentity(rsaFile.getPath()); + } else { + jSch.addIdentity(rsaFile.getPath(), password); + } + return jSch; + } + }); + }); + } else { + throw new IllegalStateException(I18nMessageUtil.get("i18n.protocol_type_not_supported.7a66")); + } + } + + private static Git initGit(Map parameter, String branchName, String tagName, File file, PrintWriter printWriter) { + String url = (String) parameter.get("url"); + return Optional.of(file).flatMap(file12 -> { + // 文件信息 + if (FileUtil.file(file12, Constants.DOT_GIT).exists()) { + return Optional.of(true); + } + return Optional.empty(); + }).flatMap(status -> { + try { + // 远程地址 + if (checkRemoteUrl(url, file)) { + return Optional.of(true); + } + } catch (IOException | GitAPIException e) { + throw Lombok.sneakyThrow(e); + } + return Optional.empty(); + }).flatMap(aBoolean -> { + if (StrUtil.isEmpty(tagName)) { + // 分支模式,继续验证 + return Optional.of(true); + } + // 标签模式直接中断 + return Optional.empty(); + }) + .flatMap(status -> { + // 本地分支 + try { + // 远程地址 + if (checkBranchName(branchName, file)) { + return Optional.of(true); + } + } catch (IOException | GitAPIException e) { + throw Lombok.sneakyThrow(e); + } + return Optional.empty(); + }).map(aBoolean -> { + try { + return aBoolean ? Git.open(file) : reClone(parameter, branchName, tagName, file, printWriter); + } catch (IOException | GitAPIException e) { + throw Lombok.sneakyThrow(e); + } + }).orElseGet(() -> { + try { + return reClone(parameter, branchName, tagName, file, printWriter); + } catch (GitAPIException | IOException e) { + throw Lombok.sneakyThrow(e); + } + }); + } + + /** + * 获取仓库远程的所有分支 + * + * @param parameter 参数 + * @return Tuple + * @throws GitAPIException api + */ + public static Tuple getBranchAndTagList(Map parameter) throws Exception { + + String url = (String) parameter.get("url"); + try { + LsRemoteCommand lsRemoteCommand = Git.lsRemoteRepository() + .setRemote(url); + // 更新凭证 + setCredentials(lsRemoteCommand, parameter); + // + Collection call = lsRemoteCommand + .setHeads(true) + .setTags(true) + .call(); + if (CollUtil.isEmpty(call)) { + return null; + } + Map> refMap = CollStreamUtil.groupByKey(call, ref -> { + String name = ref.getName(); + if (name.startsWith(Constants.R_TAGS)) { + return Constants.R_TAGS; + } else if (name.startsWith(Constants.R_HEADS)) { + return Constants.R_HEADS; + } + return null; + }); + + // branch list + List branchListRef = refMap.get(Constants.R_HEADS); + if (branchListRef == null) { + return null; + } + List branchList = branchListRef.stream() + .map(ref -> { + String name = ref.getName(); + if (name.startsWith(Constants.R_HEADS)) { + return name.substring((Constants.R_HEADS).length()); + } + return null; + }) + .filter(Objects::nonNull) + .sorted((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)) + .collect(Collectors.toList()); + + // list tag + List tagListRef = refMap.get(Constants.R_TAGS); + List tagList = tagListRef == null ? new ArrayList<>() : tagListRef.stream() + .map(ref -> { + String name = ref.getName(); + if (name.startsWith(Constants.R_TAGS)) { + return name.substring((Constants.R_TAGS).length()); + } + return null; + }) + .filter(Objects::nonNull) + .sorted((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)) + .collect(Collectors.toList()); + return new Tuple(branchList, tagList); + } catch (Exception t) { + checkTransportException(t, null, null); + return null; + } + } + + /** + * 拉取对应分支最新代码 + * + * @param parameter 参数 + * @param file 仓库路径 + * @param branchName 分支名 + * @return 返回最新一次提交信息 + * @throws IOException IO + * @throws GitAPIException api + */ + public static String[] checkoutPull(Map parameter, File file, String branchName, PrintWriter printWriter) throws Exception { + String url = (String) parameter.get("url"); + String path = FileUtil.getAbsolutePath(file); + synchronized (StrUtil.concat(false, url, path).intern()) { + try (Git git = initGit(parameter, branchName, null, file, printWriter)) { + // 拉取代码 + PullResult pull = pull(git, parameter, branchName, printWriter); + // 最后一次提交记录 + return getLastCommitMsg(file, false, branchName); + } catch (Exception t) { + checkTransportException(t, file, printWriter); + } + } + return new String[]{StrUtil.EMPTY, StrUtil.EMPTY}; + } + + /** + * 拉取远程最新代码 + * + * @param git 仓库对象 + * @param branchName 分支 + * @param parameter 参数 + * @param printWriter 日志流 + * @return pull result + * @throws Exception 异常 + */ + private static PullResult pull(Git git, Map parameter, String branchName, PrintWriter printWriter) throws Exception { + Integer progressRatio = (Integer) parameter.get("reduceProgressRatio"); + SmallTextProgressMonitor progressMonitor = new SmallTextProgressMonitor(printWriter, progressRatio); + // 放弃本地修改 + git.checkout().setName(branchName).setForced(true).setProgressMonitor(progressMonitor).call(); + // + PullCommand pull = git.pull(); + // + setCredentials(pull, parameter); + // + PullResult call = pull + .setRemoteBranchName(branchName) + .setProgressMonitor(progressMonitor) + .setRecurseSubmodules(FetchRecurseSubmodulesMode.YES) + .call(); + // 输出拉取结果 + if (call != null) { + String fetchedFrom = call.getFetchedFrom(); + FetchResult fetchResult = call.getFetchResult(); + MergeResult mergeResult = call.getMergeResult(); + RebaseResult rebaseResult = call.getRebaseResult(); + if (mergeResult != null) { + println(printWriter, "mergeResult {}", mergeResult); + } + if (rebaseResult != null) { + println(printWriter, "rebaseResult {}", rebaseResult); + } + if (fetchedFrom != null) { + println(printWriter, "fetchedFrom {}", fetchedFrom); + } + // if (fetchResult != null) { + // println(printWriter, "fetchResult {}", fetchResult); + // } + } + // + SubmoduleUpdateCommand subUpdate = git.submoduleUpdate(); + setCredentials(subUpdate, parameter); + Collection rst = subUpdate + .setProgressMonitor(progressMonitor) + .setFetch(true) + .setStrategy(MergeStrategy.THEIRS) + .call(); + println(printWriter, String.join("\n", rst)); + return call; + } + + /** + * 拉取对应分支最新代码 + * + * @param printWriter 日志输出流 + * @param parameter 参数 + * @param file 仓库路径 + * @param tagName 标签名 + * @throws IOException IO + * @throws GitAPIException api + */ + public static String[] checkoutPullTag(Map parameter, File file, String tagName, PrintWriter printWriter) throws Exception { + String url = (String) parameter.get("url"); + String path = FileUtil.getAbsolutePath(file); + synchronized (StrUtil.concat(false, url, path).intern()) { + try (Git git = initGit(parameter, null, tagName, file, printWriter)) { + // 获取最后提交信息 + return getLastCommitMsg(file, true, tagName); + } catch (Exception t) { + checkTransportException(t, file, printWriter); + } + } + return new String[]{StrUtil.EMPTY, StrUtil.EMPTY}; + } + + /** + * 检查异常信息 + * + * @param ex 异常信息 + * @param gitFile 仓库地址 + * @param printWriter 日志流 + * @throws TransportException 非账号密码异常 + */ + public static void checkTransportException(Exception ex, File gitFile, PrintWriter printWriter) throws Exception { + println(printWriter, ""); + Throwable causedBy = ExceptionUtil.getCausedBy(ex, NotSupportedException.class); + if (causedBy != null) { + println(printWriter, I18nMessageUtil.get("i18n.current_address_may_not_be_git.41c6") + causedBy.getMessage()); + throw new IllegalStateException(I18nMessageUtil.get("i18n.current_address_may_not_be_git.41c6") + causedBy.getMessage(), ex); + } + causedBy = ExceptionUtil.getCausedBy(ex, NoRemoteRepositoryException.class); + if (causedBy != null) { + println(printWriter, I18nMessageUtil.get("i18n.remote_repository_does_not_exist.7009") + causedBy.getMessage()); + throw new IllegalStateException(I18nMessageUtil.get("i18n.remote_repository_does_not_exist.7009") + causedBy.getMessage(), ex); + } + causedBy = ExceptionUtil.getCausedBy(ex, RepositoryNotFoundException.class); + if (causedBy != null) { + println(printWriter, I18nMessageUtil.get("i18n.current_address_no_repository.db31") + causedBy.getMessage()); + throw new IllegalStateException(I18nMessageUtil.get("i18n.current_address_no_repository.db31") + causedBy.getMessage(), ex); + } + if (ex instanceof TransportException) { + String msg = ex.getMessage(); + if (StrUtil.containsAny(msg, JGitText.get().notAuthorized, JGitText.get().authenticationNotSupported)) { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.incorrect_account_credentials_or_unsupported_auth.1ef9") + msg, ex); + } + throw ex; + } else if (ex instanceof NoHeadException) { + println(printWriter, I18nMessageUtil.get("i18n.pull_code_exception_with_cleanup.a887") + ex.getMessage()); + if (gitFile == null) { + throw ex; + } else { + FileUtil.del(gitFile); + } + throw ex; + } else if (ex instanceof CheckoutConflictException) { + println(printWriter, I18nMessageUtil.get("i18n.code_pull_conflict.6e8e") + ex.getMessage()); + throw ex; + } else { + println(printWriter, I18nMessageUtil.get("i18n.unknown_exception_on_pull_code.2b2e") + ex.getMessage()); + throw ex; + } + } + + /** + * 输出日志信息 + * + * @param printWriter 日志流 + * @param template 日志内容模版 + * @param params 参数 + */ + private static void println(PrintWriter printWriter, CharSequence template, Object... params) { + if (printWriter == null) { + return; + } + printWriter.println(StrUtil.format(template, params)); + // 需要 flush 让输出立即生效 + IoUtil.flush(printWriter); + } + + /** + * 解析仓库指定分支最新提交 + * + * @param git 仓库 + * @param branchName 分支 + * @return objectID + * @throws GitAPIException 异常 + */ + private static ObjectId getBranchAnyObjectId(Git git, String branchName) throws GitAPIException { + List list = git.branchList().call(); + for (Ref ref : list) { + String name = ref.getName(); + if (name.startsWith(Constants.R_HEADS + branchName)) { + return ref.getObjectId(); + } + } + return null; + } + + /** + * 解析仓库指定分支最新提交 + * + * @param git 仓库 + * @param tagName 标签 + * @return objectID + * @throws GitAPIException 异常 + */ + private static ObjectId getTagAnyObjectId(Git git, String tagName) throws GitAPIException { + List list = git.tagList().call(); + for (Ref ref : list) { + String name = ref.getName(); + if (name.startsWith(Constants.R_TAGS + tagName)) { + return ref.getObjectId(); + } + } + return null; + } + + /** + * 获取对应分支的最后一次提交记录 + * + * @param file 仓库文件夹 + * @param refName 名称 + * @return String[] 第一个元素为最后一次 hash 值, 第二个元素为描述 + * @throws IOException IO + * @throws GitAPIException api + */ + public static String[] getLastCommitMsg(File file, boolean tag, String refName) throws IOException, GitAPIException { + try (Git git = Git.open(file)) { + ObjectId anyObjectId = tag ? getTagAnyObjectId(git, refName) : getBranchAnyObjectId(git, refName); + Objects.requireNonNull(anyObjectId, StrUtil.format(I18nMessageUtil.get("i18n.no_branch_or_tag_message.8ae3"), refName)); + //System.out.println(anyObjectId.getName()); + String lastCommitMsg = getLastCommitMsg(file, refName, anyObjectId); + return new String[]{anyObjectId.getName(), lastCommitMsg}; + } + } + + /** + * 解析提交信息 + * + * @param file 仓库文件夹 + * @param desc 描述 + * @param objectId 提交信息 + * @return 描述 + * @throws IOException IO + */ + private static String getLastCommitMsg(File file, String desc, ObjectId objectId) throws IOException { + try (Git git = Git.open(file)) { + RevWalk walk = new RevWalk(git.getRepository()); + RevCommit revCommit = walk.parseCommit(objectId); + String time = new DateTime(revCommit.getCommitTime() * 1000L).toString(); + PersonIdent personIdent = revCommit.getAuthorIdent(); + return StrUtil.format("{} {} {}[{}] {} {}", + desc, + revCommit.getShortMessage(), + personIdent.getName(), + personIdent.getEmailAddress(), + time, + revCommit.getParentCount()); + } + } +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SmallTextProgressMonitor.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SmallTextProgressMonitor.java new file mode 100644 index 0000000000..3ec81124f3 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SmallTextProgressMonitor.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import org.eclipse.jgit.lib.TextProgressMonitor; + +import java.io.Writer; +import java.util.HashSet; +import java.util.Set; + +/** + * 少量的输出,只在进度 x%5=0 的时候输出 + * + * @author bwcx_jzy + * @see 2022/11/28 + */ +public class SmallTextProgressMonitor extends TextProgressMonitor { + + private final Set progressRangeList; + private final int reduceProgressRatio; + + /** + * @param out 输出流 + * @param reduceProgressRatio 压缩折叠显示进度比例 范围 1-100 + */ + public SmallTextProgressMonitor(Writer out, int reduceProgressRatio) { + super(out); + this.reduceProgressRatio = reduceProgressRatio; + this.progressRangeList = new HashSet<>((int) Math.floor((float) 100 / reduceProgressRatio)); + } + + @Override + protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) { + int progressRange = (int) Math.floor((float) pcnt / this.reduceProgressRatio); + if (progressRangeList.add(progressRange)) { + super.onUpdate(taskName, cmp, totalWork, pcnt); + } + } +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SslVerifyUsernamePasswordCredentialsProvider.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SslVerifyUsernamePasswordCredentialsProvider.java new file mode 100644 index 0000000000..362b5b5f8f --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SslVerifyUsernamePasswordCredentialsProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.*; + +/** + * https://stackoverflow.com/questions/33998477/turn-ssl-verification-off-for-jgit-clone-command/35587504 + * + * @author bwcx_jzy + * @see ChainingCredentialsProvider + * @see HttpTransport + * @see TransportHttp#trustInsecureSslConnection(Throwable) + * @since 2023/1/29 + */ +public class SslVerifyUsernamePasswordCredentialsProvider extends UsernamePasswordCredentialsProvider { + + public SslVerifyUsernamePasswordCredentialsProvider(String username, String password) { + super(username, password); + } + + @Override + public boolean supports(CredentialItem... items) { + for (CredentialItem item : items) { + if ((item instanceof CredentialItem.YesNoType)) { + return true; + } + } + return super.supports(items); + } + + @Override + public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { + for (CredentialItem item : items) { + if (item instanceof CredentialItem.YesNoType) { + ((CredentialItem.YesNoType) item).setValue(true); + return true; + } + } + return super.get(uri, items); + } +} diff --git a/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SystemGitProcess.java b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SystemGitProcess.java new file mode 100644 index 0000000000..73d6183508 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/main/java/org/dromara/jpom/plugin/SystemGitProcess.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.comparator.VersionComparator; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Tuple; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.util.CommandUtil; +import org.eclipse.jgit.lib.Constants; +import org.springframework.util.Assert; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.*; + +/** + * @author Hong + * @since 2023/4/10 + */ +@Slf4j +public class SystemGitProcess extends AbstractGitProcess { + + protected SystemGitProcess(IWorkspaceEnvPlugin workspaceEnvPlugin, Map parameter) { + super(workspaceEnvPlugin, parameter); + PrintWriter printWriter = (PrintWriter) parameter.get("logWriter"); + if (printWriter != null) { + printWriter.println(); + printWriter.println("use system git"); + printWriter.flush(); + } + } +// ssh-agent bash -c 'ssh-add ; git clone git@:/.git' + // ssh-agent bash -c 'ssh-add /path/to/private_key && ssh -o StrictHostKeyChecking=yes git@github.com && git clone git@github.com:user/repo.git' + + /** + * 获取仓库地址 需要拼接账号密码 + * + * @return url + * @throws MalformedURLException url 错误 + */ + private String getCovertUrl() throws MalformedURLException { + String url = (String) parameter.get("url"); + String username = (String) parameter.getOrDefault("username", ""); + String password = (String) parameter.getOrDefault("password", ""); + int protocol = (int) parameter.getOrDefault("protocol", 0); + if (protocol == 0) { + if (StrUtil.contains(url, "@")) { + // 已经配置 + return url; + } + username = URLUtil.encode(username); + password = URLUtil.encode(password); + String userInfo = username + ":" + password; + URL url1 = new URL(null, url, new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return null; + } + + @Override + protected void setURL(URL u, String protocol, String host, int port, String authority, String userInfo2, String path, String query, String ref) { + super.setURL(u, protocol, host, port, StrUtil.format("{}@{}", userInfo, authority), userInfo, path, query, ref); + } + }); + return url1.toString(); + } + // ssh 原样返回 + return url; + } + + private String warpSsh(String command) { + int protocol = (int) parameter.getOrDefault("protocol", 0); + if (protocol == 0) { + return command; + } else if (protocol == 1) { + // TODO 需要实现本地 git ssh 指定证书拉取 + File rsaFile = (File) parameter.get("rsaFile"); + if (FileUtil.isFile(rsaFile)) { + throw new IllegalStateException(I18nMessageUtil.get("i18n.local_git_certificate_not_supported.b395")); + } + // 默认的方式去执行 + return command; + } else { + throw new IllegalArgumentException(I18nMessageUtil.get("i18n.protocol_not_supported.b906") + protocol); + } + } + + @Override + public Tuple branchAndTagList() throws Exception { + String command = StrUtil.format("git ls-remote {}", this.getCovertUrl()); + command = this.warpSsh(command); + String result = CommandUtil.execSystemCommand(command); + List branchRemote = new ArrayList<>(); + List tagRemote = new ArrayList<>(); + List list = StrUtil.splitTrim(result, StrUtil.LF); + for (String branch : list) { + List list1 = StrUtil.splitTrim(branch, StrUtil.TAB); + String last = CollUtil.getLast(list1); + if (StrUtil.startWith(last, Constants.R_HEADS)) { + branchRemote.add(StrUtil.removePrefix(last, Constants.R_HEADS)); + } else if (StrUtil.startWith(last, Constants.R_TAGS)) { + tagRemote.add(StrUtil.removePrefix(last, Constants.R_TAGS)); + } + } + branchRemote.sort((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)); + tagRemote.sort((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)); + return new Tuple(branchRemote, tagRemote); + } + + @Override + public String[] pull() throws Exception { + String branchName = (String) parameter.get("branchName"); + Assert.hasText(branchName, I18nMessageUtil.get("i18n.no_branch_name.1879")); + return pull(branchName); + } + + @Override + public String[] pullByTag() throws Exception { + String tagName = (String) parameter.get("tagName"); + Assert.hasText(tagName, I18nMessageUtil.get("i18n.no_tag_name.40ff")); + return pull(tagName); + } + + private String[] pull(String branchOrTag) throws IOException { + PrintWriter printWriter = (PrintWriter) parameter.get("logWriter"); + boolean needClone = this.needClone(); + if (needClone) { + // clone + this.reClone(printWriter, branchOrTag); + } + File saveFile = getSaveFile(); + + { + Boolean strictlyEnforce = (Boolean) parameter.get("strictlyEnforce"); + strictlyEnforce = strictlyEnforce != null && strictlyEnforce; + // 更新 + /*CommandUtil.exec(saveFile, null, line -> { + printWriter.println(line); + printWriter.flush(); + }, "git", "pull");*/ + int code = CommandUtil.exec(saveFile, null, line -> { + printWriter.println(line); + printWriter.flush(); + }, "git", "fetch", "--all"); + if (code != 0 && strictlyEnforce) { + return new String[]{null, null, I18nMessageUtil.get("i18n.git_fetch_failed_status_code.5187") + code}; + } + code = CommandUtil.exec(saveFile, null, line -> { + printWriter.println(line); + printWriter.flush(); + }, "git", "reset", "--hard", "origin/" + branchOrTag); + if (code != 0 && strictlyEnforce) { + return new String[]{null, null, I18nMessageUtil.get("i18n.git_reset_hard_failed_status_code.d818") + code}; + } + code = CommandUtil.exec(saveFile, null, line -> { + printWriter.println(line); + printWriter.flush(); + }, "git", "submodule", "update", "--init", "--remote", "-f", "--recursive"); + if (code != 0 && strictlyEnforce) { + return new String[]{null, null, I18nMessageUtil.get("i18n.git_submodule_update_failed_status_code.2218") + code}; + } + } + // 获取提交日志 + String[] command = {"git", "log", "-1", branchOrTag}; + String[] commitId = new String[1]; + + CommandUtil.exec(saveFile, null, line -> { + printWriter.println(line); + printWriter.flush(); + if (StrUtil.isEmpty(commitId[0]) && StrUtil.startWithIgnoreCase(line, "commit")) { + List list = StrUtil.splitTrim(line, StrUtil.SPACE); + commitId[0] = CollUtil.get(list, 1); + } + }, command); + return new String[]{commitId[0], StrUtil.EMPTY}; + } + + private void reClone(PrintWriter printWriter, String branchOrTag) throws IOException { + printWriter.println("SystemGit: Automatically re-clones repositories"); + // 先删除本地目录 + File savePath = getSaveFile(); + if (!FileUtil.clean(savePath)) { + FileUtil.del(savePath.toPath()); + } + String depthStr = Optional.ofNullable((Integer) parameter.get("depth")) + .map(integer -> { + if (integer > 0) { + return integer; + } + return null; + }) + .map(integer -> "--depth=" + integer) + .orElse(StrUtil.EMPTY); + Map env = new HashMap<>(4); + Optional.ofNullable((Integer) parameter.get("timeout")) + .map(integer -> { + if (integer > 0) { + return integer; + } + return null; + }).ifPresent(integer -> env.put("GIT_HTTP_TIMEOUT", String.valueOf(integer))); + // + String[] command = new String[]{"git", "clone", "--recursive", depthStr, "-b", branchOrTag, this.getCovertUrl(), savePath.getAbsolutePath()}; + FileUtil.mkdir(savePath); + CommandUtil.exec(savePath, env, line -> { + printWriter.println(line); + printWriter.flush(); + }, command); + } + + /** + * 是否存在GIT仓库 + */ + private boolean needClone() throws MalformedURLException { + File savePath = getSaveFile(); + File file = FileUtil.file(savePath, Constants.DOT_GIT); + if (!FileUtil.exist(file)) { + return true; + } + // 判断远程 + String url = (String) parameter.get("url"); + String checkRemote = CommandUtil.execSystemCommand("git remote -v", savePath); + if (!StrUtil.containsAny(checkRemote, url, this.getCovertUrl())) { + return true; + } + String branchName = getBranchName(); + if (StrUtil.isNotEmpty(branchName)) { + String checkBranch = CommandUtil.execSystemCommand("git rev-parse --abbrev-ref HEAD", savePath); + checkBranch = StrUtil.trim(checkBranch); + return !StrUtil.equals(checkBranch, branchName); + } + // tag 模式 + return true; + } +} diff --git a/modules/sub-plugin/git-clone/src/test/java/git/TestGit.java b/modules/sub-plugin/git-clone/src/test/java/git/TestGit.java new file mode 100644 index 0000000000..2f092e1dcd --- /dev/null +++ b/modules/sub-plugin/git-clone/src/test/java/git/TestGit.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package git; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.StrUtil; +import org.dromara.jpom.plugin.JGitUtil; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ListBranchCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +/** + * @author bwcx_jzy + * @since 2019/7/9 + **/ +public class TestGit { + + @Test + public void test() { + + //System.out.println(Files.exists(FileUtil.file("D:\\jpom\\server\\data\\temp\\gitTemp\\6323b009deeca9367a1e393acd9f26b8\\objects").toPath(), LinkOption.NOFOLLOW_LINKS)); + } + + @Test + public void test2() throws IOException { + String fileOrDirPath = "D:\\jpom\\server\\data\\temp\\"; + List strings = FileUtil.loopFiles(fileOrDirPath); +// for (File file : strings) { +// FilePermission filePermission = new FilePermission(file.getAbsolutePath(), "write"); +// System.out.println(filePermission.implies(filePermission)); +// } + System.out.println(strings.size()); + for (File string : strings) { + string.setWritable(true); + try { + Path path = Paths.get(string.getAbsolutePath()); + Files.delete(path); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + FileUtil.clean(fileOrDirPath); + Path path = Paths.get(fileOrDirPath); + Files.delete(path); +// FilePermission filePermission = new FilePermission(fileOrDirPath, "write"); +// System.out.println(filePermission.implies(filePermission)); +// FileUtil.clean(fileOrDirPath); +// FileUtil.del(fileOrDirPath); + + } + + + public static void main(String[] args) throws GitAPIException, IOException, URISyntaxException { + + Git git = Git.init().setDirectory(new File("D:\\tttt")).call(); + RemoteConfig remoteConfig = Git.open(new File("D:\\tttt")).remoteAdd().setUri(new URIish("https://gitee.com/keepbx/Jpom.git")).call(); + System.out.println(remoteConfig); + + List call = git.remoteList().call(); + System.out.println(call); + git.pull().call(); + List list = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call(); + List all = new ArrayList<>(list.size()); + list.forEach(ref -> { + String name = ref.getName(); + if (name.startsWith(Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME)) { + all.add(name.substring((Constants.R_REMOTES + Constants.DEFAULT_REMOTE_NAME).length() + 1)); + } + }); + System.out.println(all); + } + + + @Test + public void testTag() throws Exception { + + + // +// Git.cloneRepository().setTagOption(TagOpt.FETCH_TAGS) + + String uri = "https://gitee.com/dromara/Jpom.git"; + File file = FileUtil.file("~/test/jpomgit"); + String tagName = "v2.5.2"; + String branchName = "stand-alone"; + +// Git call = Git.cloneRepository() +// .setURI(uri) +// .setDirectory(file) +// .setCredentialsProvider(credentialsProvider) +// .call(); + + PrintWriter printWriter = new PrintWriter(System.out); + Map map = new HashMap<>(10); + map.put("url", uri); + map.put("protocol", 0); + map.put("username", "a"); + map.put("password", "a"); + + + JGitUtil.checkoutPullTag(map, file, tagName, printWriter); + + //GitUtil.checkoutPull(uri, file, branchName, credentialsProvider, printWriter); + + + } + + + @Test + public void testTag2() throws Exception { + String uri = "https://gitee.com/keepbx/Jpom-demo-case.git"; + File file = FileUtil.file("~/test/jpomgit2"); + String tagName = "1.2"; + String branchName = "master"; + UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider("a", "a"); + + PrintWriter printWriter = new PrintWriter(System.out); + + Map map = new HashMap<>(10); + map.put("url", uri); + map.put("protocol", 0); + map.put("username", "a"); + map.put("password", "a"); +// map.put("rsaFile", BuildUtil.getRepositoryRsaFile(this)); + +// RepositoryModel repositoryModel = new RepositoryModel(); +// repositoryModel.setGitUrl(uri); +// repositoryModel.setRepoType(0); +// repositoryModel.setUserName("a"); +// repositoryModel.setPassword("a"); + String[] msg = JGitUtil.checkoutPullTag(map, file, tagName, printWriter); + System.out.println(Arrays.toString(msg)); + //GitUtil.checkoutPull(uri, file, branchName, credentialsProvider, printWriter); + + + } + + @Test + public void testUrl() throws MalformedURLException { + String url = "https://12:222@gitee.com/keepbx/Jpom-demo-case.git"; + UrlBuilder urlBuilder = UrlBuilder.of(url); + URL url2 = new URL(url); + System.out.println(url2); + URL url1 = new URL(null, "https://gitee.com/keepbx/Jpom-demo-case.git", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return null; + } + + @Override + protected void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref) { + System.out.println(userInfo); + String userInfo1 = "abc:321"; + super.setURL(u, protocol, host, port, StrUtil.format("{}@{}", userInfo1, authority), userInfo1, path, query, ref); + System.out.println(u); + } + }); + System.out.println(url1); + URL url3 = new URL("git@gitee.com:dromara/Jpom.git"); + System.out.println(url3); + } + +} diff --git a/modules/sub-plugin/git-clone/src/test/java/git/TestGitEnv.java b/modules/sub-plugin/git-clone/src/test/java/git/TestGitEnv.java new file mode 100644 index 0000000000..291231c080 --- /dev/null +++ b/modules/sub-plugin/git-clone/src/test/java/git/TestGitEnv.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package git; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.plugin.GitProcessFactory; +import org.junit.Test; + +/** + *
+ * Created By Hong on 2023/3/31 + * + * @author Hong + **/ +@Slf4j +public class TestGitEnv { + + @Test + public void test() { + log.info("系统中是否存在GIT环境:{}", GitProcessFactory.existsSystemGit()); + } + +} diff --git a/modules/sub-plugin/git-clone/src/test/java/git/TestGithub.java b/modules/sub-plugin/git-clone/src/test/java/git/TestGithub.java new file mode 100644 index 0000000000..463432928c --- /dev/null +++ b/modules/sub-plugin/git-clone/src/test/java/git/TestGithub.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package git; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import org.junit.Test; + +import java.util.function.Consumer; + +/** + * @author bwcx_jzy + * @since 2021/12/19 + */ +public class TestGithub { + + @Test + public void testUser() { + HttpRequest request = HttpUtil.createGet("https://api.github.com/user"); + + request.header("Authorization", "token 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"); + request.header("Accept", "application/vnd.github.v3+json"); + request.then(new Consumer() { + @Override + public void accept(HttpResponse httpResponse) { + String body = httpResponse.body(); + System.out.println(body); + } + }); + System.out.println("123"); + + } + + @Test + public void testGielab() { + String value = "glpat-rkreLo8NkZShSVyhgWyq"; + HttpRequest request = HttpUtil.createGet("https://gitlab.com/api/v4/user"); + request.form("access_token", value); + String body = request.execute().body(); + System.out.println(body); + + HttpRequest httpRequest = HttpUtil.createGet("https://gitlab.com/api/v4/projects"); + + httpRequest.form("private_token", value); + httpRequest.form("membership", true); + httpRequest.form("simple", true); + + httpRequest.form("page", 1); + httpRequest.form("per_page", "15"); + String body1 = httpRequest.execute().body(); + System.out.println(body1); + } + + + @Test + public void test() { + HttpRequest post = HttpUtil.createPost("https://api.github.com/repositories?since=824"); + String body = post.execute().body(); + System.out.println(body); + } +} diff --git a/modules/sub-plugin/git-clone/src/test/java/git/TestPluginGit.java b/modules/sub-plugin/git-clone/src/test/java/git/TestPluginGit.java new file mode 100644 index 0000000000..090f1958de --- /dev/null +++ b/modules/sub-plugin/git-clone/src/test/java/git/TestPluginGit.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package git; + +import cn.hutool.core.util.RuntimeUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.plugin.DefaultGitPluginImpl; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class TestPluginGit { + + + @Test + public void testHttp() throws Exception { + Map map = new HashMap<>(); +// map.put("gitProcessType", "JGit"); + map.put("gitProcessType", "SystemGit"); +// map.put("url", "git@gitee.com:keepbx/Jpom-demo-case.git");//git@github.com:emqx/emqx-operator.git + map.put("url", "https://gitee.com/keepbx/Jpom-demo-case"); + map.put("reduceProgressRatio", 1); + map.put("timeout", 60); + map.put("depth", 0); + map.put("protocol", 1); + map.put("username", ""); + map.put("password", ""); + map.put("rsaFile", new File("C:\\Users\\hong_\\.ssh\\id_rsa_gitee")); + map.put("savePath", new File("C:\\Users\\hong_\\Desktop\\test\\Jpom-demo-case")); + map.put("branchName", "master"); + map.put("tagName", "1.1"); + map.put("logWriter", new PrintWriter("C:\\Users\\hong_\\Desktop\\test\\aaa.txt")); +// Object obj = new DefaultGitPluginImpl().execute("branchAndTagList", map); +// Object obj = new DefaultGitPluginImpl().execute("pull", map); + Object obj = new DefaultGitPluginImpl().execute("pullByTag", map); + if (obj instanceof String[]) { + String[] strs = (String[]) obj; + for (String str : strs) { + System.err.println(str); + } + return; + } + System.err.println(obj); + } + + @Test + public void testHttp2() throws Exception { + Map map = new HashMap<>(); +// map.put("gitProcessType", "JGit"); + map.put("gitProcessType", "SystemGit"); + map.put("url", "git@gitee.com:keepbx/Jpom-demo-case.git");//git@github.com:emqx/emqx-operator.git +// map.put("url", "https://gitee.com/keepbx/Jpom-demo-case"); + map.put("reduceProgressRatio", 1); + map.put("timeout", 60); + map.put("protocol", 1); + map.put("username", ""); + map.put("password", ""); + map.put("rsaFile", new File("C:\\Users\\hong_\\.ssh\\id_rsa_gitee")); + map.put("savePath", new File("C:\\Users\\hong_\\Desktop\\test\\Jpom-demo-case")); + map.put("branchName", "master"); + map.put("tagName", "1.1"); + map.put("logWriter", new PrintWriter("C:\\Users\\hong_\\Desktop\\test\\aaa.txt")); + Object obj = new DefaultGitPluginImpl().execute("branchAndTagList", map); +// Object obj = new DefaultGitPluginImpl().execute("pull", map); +// Object obj = new DefaultGitPluginImpl().execute("pullByTag", map); + if (obj instanceof String[]) { + String[] strs = (String[]) obj; + for (String str : strs) { + System.err.println(str); + } + return; + } + System.err.println(obj); + } + + @Test + public void testSsh() { + String repoUrl = "git@gitee.com:keepbx/Jpom-demo-case.git"; + String privateKeyPath = "C:/Users/bwcx_/.ssh/id_rsa"; +// String localPath = "/path/to/local/repo"; + + try { + ProcessBuilder pb = new ProcessBuilder("git", "ls-remote", "git@gitee.com/keepbx/Jpom-demo-case.git"); +// pb.environment().put("GIT_SSH_COMMAND", "ssh -i " + privateKeyPath + " -o StrictHostKeyChecking=no"); + Map environment = pb.environment(); + environment.put("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no git@gitee.com -o PubkeyAuthentication=yes -o PreferredAuthentications=publickey -o IdentityFile=C:/Users/bwcx_/.ssh/id_rsa -o IdentitiesOnly=yes "); + environment.put("GIT_SSH_VARIANT", "ssh"); + Process process = pb.start(); + String result = RuntimeUtil.getResult(process); + String errorResult = RuntimeUtil.getErrorResult(process); + System.out.println(result); + System.out.println(errorResult); + int exitCode = process.waitFor(); + if (exitCode == 0) { + System.out.println("Git pull success!"); + } else { + System.out.println("Git pull failed!"); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/modules/sub-plugin/git-clone/src/test/java/git/TestSort.java b/modules/sub-plugin/git-clone/src/test/java/git/TestSort.java new file mode 100644 index 0000000000..ebfdcebb4d --- /dev/null +++ b/modules/sub-plugin/git-clone/src/test/java/git/TestSort.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package git; + +import cn.hutool.core.comparator.VersionComparator; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author bwcx_jzy + * @since 2023/9/15 + */ +public class TestSort { + + @Test + public void test() { + File file = FileUtil.file("D:\\System-Data\\Documents\\WeChat Files\\A22838106\\FileStorage\\File\\2023-09\\list"); + List list = FileUtil.readLines(file, CharsetUtil.CHARSET_UTF_8); + // + list = list.stream() + .map(StrUtil::trim).map(name -> { + //String name = ref.getName(); + if (name.startsWith("remotes/origin/")) { + return name.substring("remotes/origin/".length()); + } + return null; + }) + .filter(Objects::nonNull) + .sorted((o1, o2) -> { + int compare = VersionComparator.INSTANCE.compare(o2, o1); + System.out.println(compare); + System.out.println(o1 + " " + o2); + return compare; + }) + .collect(Collectors.toList()); + //list.sort((o1, o2) -> VersionComparator.INSTANCE.compare(o2, o1)); + System.out.println(list); + } + + @Test + public void test2() { + List list = new ArrayList<>(); + list.add("lester-hotfix-202308002-1"); + list.add("lester-invoice-release"); + list.sort(new Comparator() { + @Override + public int compare(String o1, String o2) { + int compare = VersionComparator.INSTANCE.compare(o2, o1); + return compare; + } + }); + + } +} diff --git a/modules/server/src/test/java/git/TestSshGit.java b/modules/sub-plugin/git-clone/src/test/java/git/TestSshGit.java similarity index 79% rename from modules/server/src/test/java/git/TestSshGit.java rename to modules/sub-plugin/git-clone/src/test/java/git/TestSshGit.java index 9357435509..160a4b2501 100644 --- a/modules/server/src/test/java/git/TestSshGit.java +++ b/modules/sub-plugin/git-clone/src/test/java/git/TestSshGit.java @@ -1,29 +1,15 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 码之科技工作室 - * - * 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. + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. */ package git; import cn.hutool.core.io.FileUtil; -import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.PemUtil; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; @@ -40,9 +26,7 @@ import org.junit.Test; import java.io.*; -import java.security.KeyStore; import java.security.PrivateKey; -import java.security.PublicKey; /** * https://qa.1r1g.com/sf/ask/875171671/ @@ -50,7 +34,7 @@ * https://www.jianshu.com/p/036072b45a2d * * @author bwcx_jzy - * @date 2019/7/10 + * @since 2019/7/10 **/ public class TestSshGit { public static void main(String[] args) throws GitAPIException, IOException { diff --git a/modules/sub-plugin/pom-release.xml b/modules/sub-plugin/pom-release.xml new file mode 100644 index 0000000000..f958c513d4 --- /dev/null +++ b/modules/sub-plugin/pom-release.xml @@ -0,0 +1,173 @@ + + + + pom + + webhook + email + svn-clone + maven-plugin + auto-charset-jchardet + db-h2 + docker-cli + git-clone + + 4.0.0 + org.dromara.jpom.plugins + jpom-plugins-parent + 2.8.14 + Jpom 插件模块 + + Jpom java 插件模块 + + + UTF-8 + 1.8 + + true + true + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + attach-javadoc + package + + jar + + + + + + + date + a + 创建时间 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + once + -Dfile.encoding=UTF-8 + + + + + + + + release + + + maven-repo + https://oss.sonatype.org/content/repositories/snapshots/ + + + maven-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + verify-gpg + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + + + + + master + git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + + + + bwcx_jzy + bwcx_jzy@163.com + bwcx_jzy + + + + diff --git a/modules/sub-plugin/pom.xml b/modules/sub-plugin/pom.xml new file mode 100644 index 0000000000..5d8c9ec010 --- /dev/null +++ b/modules/sub-plugin/pom.xml @@ -0,0 +1,178 @@ + + + + + jpom-parent + org.dromara.jpom + 2.11.6.6 + ../../pom.xml + + pom + + webhook + email + svn-clone + docker-cli + git-clone + encrypt + ssh-jsch + + 4.0.0 + 2.11.6.6 + org.dromara.jpom.plugins + jpom-plugins-parent + Jpom Plugins + + Jpom java 插件模块 + + + UTF-8 + 1.8 + + true + true + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + once + -Dfile.encoding=UTF-8 + + + + + + + + release + + + maven-repo + https://oss.sonatype.org/content/repositories/snapshots/ + + + maven-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + attach-javadoc + package + + jar + + + + + + + date + a + 创建时间 + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + verify-gpg + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + + + + + master + git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + scm:git:git@gitee.com:dromara/Jpom.git + + + + bwcx_jzy + bwcx_jzy@163.com + bwcx_jzy + + + + diff --git a/modules/sub-plugin/ssh-jsch/pom.xml b/modules/sub-plugin/ssh-jsch/pom.xml new file mode 100644 index 0000000000..bc6a21b25f --- /dev/null +++ b/modules/sub-plugin/ssh-jsch/pom.xml @@ -0,0 +1,105 @@ + + + + 4.0.0 + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + plugin-ssh-jsch + ssh-jsch + + + 8 + 8 + UTF-8 + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + org.bouncycastle + bcprov-jdk18on + + + + com.github.mwiede + jsch + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + false + + script/release.xml + + + + + + + + make-assembly + package + + single + + + + + + + diff --git a/modules/sub-plugin/ssh-jsch/script/release.xml b/modules/sub-plugin/ssh-jsch/script/release.xml new file mode 100644 index 0000000000..2c3f7898b3 --- /dev/null +++ b/modules/sub-plugin/ssh-jsch/script/release.xml @@ -0,0 +1,43 @@ + + + + release + false + + jar + + + + + / + false + true + runtime + + + com.jcraft:jsch + + + + + + + + ${project.build.directory}/classes + / + + + + diff --git a/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/ISshInfo.java b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/ISshInfo.java new file mode 100644 index 0000000000..2442185b98 --- /dev/null +++ b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/ISshInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugins; + +import java.nio.charset.Charset; + +/** + * @author bwcx_jzy + * @since 2023/4/6 + */ +public interface ISshInfo { + + int timeout(); + + String host(); + + ConnectType connectType(); + + Charset charset(); + + int port(); + + String user(); + + String password(); + + /** + * 私钥 + * + * @return 私钥 + */ + String privateKey(); + + /** + * id + * + * @return 数据id + */ + String id(); + + enum ConnectType { + /** + * 账号密码 + */ + PASS, + /** + * 密钥 + */ + PUBKEY + } +} diff --git a/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschLogger.java b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschLogger.java new file mode 100644 index 0000000000..d509ebaccf --- /dev/null +++ b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschLogger.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugins; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; + +/** + * @author bwcx_ + */ +@Slf4j +public class JschLogger implements com.jcraft.jsch.Logger { + + public static final JschLogger LOGGER = new JschLogger(); + + @Override + public boolean isEnabled(int level) { + switch (level) { + case DEBUG: + return log.isDebugEnabled(); + case INFO: + return log.isInfoEnabled(); + case WARN: + return log.isWarnEnabled(); + case ERROR: + case FATAL: + return log.isErrorEnabled(); + default: + log.warn(I18nMessageUtil.get("i18n.unknown_jsch_log_level.6a5c"), level); + return false; + } + } + + @Override + public void log(int level, String message) { + switch (level) { + case DEBUG: + // info 日志太多 记录维 debug + case INFO: + log.debug(message); + break; + case WARN: + if (StrUtil.isWrap(message, "Permanently added", "to the list of known hosts.")) { + // 避免过多日志 + log.debug(message); + } else { + log.warn(message); + } + break; + case ERROR: + case FATAL: + log.error(message); + break; + default: + log.warn(I18nMessageUtil.get("i18n.unknown_jsch_log_level_with_details.1f9a"), level, message); + } + } +} 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 new file mode 100644 index 0000000000..77e1238d60 --- /dev/null +++ b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschUtils.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugins; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.ssh.ChannelType; +import cn.hutool.extra.ssh.JschUtil; +import cn.hutool.extra.ssh.Sftp; +import com.jcraft.jsch.*; +import lombok.Lombok; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.ExtConfigBean; +import org.dromara.jpom.util.StringUtil; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.function.Function; + +/** + * hutool 默认封装的 SSH 工具,仅支持传入 SSL Private Key File,不支持 Private Key Content。 + *

+ * 本实现用于直接采用 Private Key Content 登录。 + * + *

+ *
+ * Created by zhenqin.
+ * User: zhenqin
+ * Date: 2022/3/25
+ * Time: 下午2:56
+ * Email: zhzhenqin@163.com
+ *
+ * 
+ * + * @author zhenqin + */ +@Slf4j +public class JschUtils { + + + public final static String HEADER = "-----BEGIN RSA PRIVATE KEY-----"; + + public final static String FOOTER = "-----END RSA PRIVATE KEY-----"; + + /** + * GETKEYTYPENAME_METHOD getKeyTypeName 是私有的 + * + * @see KeyPair#getKeyTypeName + */ + private static final Method GET_KEY_TYPE_NAME_METHOD = ReflectUtil.getMethod(KeyPair.class, "getKeyTypeName"); + + private static class ContentIdentity implements Identity { + + private KeyPair kpair; + private final String identity; + + + static ContentIdentity newInstance(byte[] prvContent, byte[] pubContent, String username, JSch jsch) throws Exception { + KeyPair kpair = KeyPair.load(jsch, prvContent, pubContent); + return new ContentIdentity(username, kpair); + } + + private ContentIdentity(String name, KeyPair kpair) { + this.identity = name; + this.kpair = kpair; + } + + + /** + * Decrypts this identity with the specified pass-phrase. + * + * @param passphrase the pass-phrase for this identity. + * @return true if the decryption is succeeded + * or this identity is not cyphered. + */ + @Override + public boolean setPassphrase(byte[] passphrase) { + return kpair.decrypt(passphrase); + } + + /** + * Returns the public-key blob. + * + * @return the public-key blob + */ + @Override + public byte[] getPublicKeyBlob() { + return kpair.getPublicKeyBlob(); + } + + /** + * Signs on data with this identity, and returns the result. + * + * @param data data to be signed + * @return the signature + */ + @Override + public byte[] getSignature(byte[] data) { + return kpair.getSignature(data); + } + + /** + * @see #setPassphrase(byte[] passphrase) + * @deprecated This method should not be invoked. + */ + @Override + public boolean decrypt() { + throw new RuntimeException("not implemented"); + } + + /** + * Returns the name of the key algorithm. + * + * @return "ssh-rsa" or "ssh-dss" + */ + @Override + public String getAlgName() { + try { + byte[] name = ReflectUtil.invoke(kpair, GET_KEY_TYPE_NAME_METHOD); + return new String(name, CharsetUtil.CHARSET_UTF_8); + } catch (Exception e) { + return null; + } + } + + /** + * Returns the name of this identity. + * It will be useful to identify this object in the {@link IdentityRepository}. + */ + @Override + public String getName() { + return identity; + } + + /** + * Returns true if this identity is cyphered. + * + * @return true if this identity is cyphered. + */ + @Override + public boolean isEncrypted() { + return kpair.isEncrypted(); + } + + /** + * Disposes internally allocated data, like byte array for the private key. + */ + @Override + public void clear() { + kpair.dispose(); + kpair = null; + } + + /** + * Returns an instance of {@link KeyPair} used in this {@link Identity}. + * + * @return an instance of {@link KeyPair} used in this {@link Identity}. + */ + public KeyPair getKeyPair() { + return kpair; + } + } + + + /** + * 通过私钥获取 Session + * + * @param sshHost ssh 目标节点IP、域名、机器名 + * @param sshPort ssh 服务端口号,一般为 22 + * @param sshUser ssh 目标节点登录用户 + * @param privateKey 采用的私钥,一般由 ssh-keygen 生成。必须包含完整的前后缀:-----BEGIN RSA PRIVATE KEY----- + * @param passphrase 密码 + * @return Session + */ + public static Session createSession(String sshHost, int sshPort, String sshUser, String privateKey, byte[] passphrase) { + final JSch jsch = new JSch(); + try { + byte[] privateKeyByte = StrUtil.bytes(privateKey); + Identity identity = ContentIdentity.newInstance(privateKeyByte, null, sshUser, jsch); + jsch.addIdentity(identity, passphrase); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + return JschUtil.createSession(jsch, sshHost, sshPort, sshUser); + } + + /** + * ssh 执行模版命令(并自动删除执行后的命令脚本文件) + * + * @param charset 编码格式 + * @param session 会话 + * @param timeout 超时时间 + * @param function 回调,是为了保证在执行完成后能自动删除名 + * @param command 命令 + * @throws IOException io + */ + public static int uploadCommandCallback(Session session, Charset charset, int timeout, Function function, String command) throws IOException { + if (StrUtil.isEmpty(command)) { + return -100; + } + ChannelSftp channel = null; + try { + Sftp sftp = new Sftp(session, charset, timeout); + channel = sftp.getClient(); + String tempId = IdUtil.fastSimpleUUID(); + File tmpDir = FileUtil.getTmpDir(); + File buildSsh = FileUtil.file(tmpDir, "ssh_temp", tempId + ".sh"); + try (InputStream sshExecTemplateInputStream = ExtConfigBean.getConfigResourceInputStream("/ssh/template.sh")) { + String sshExecTemplate = IoUtil.readUtf8(sshExecTemplateInputStream); + FileUtil.writeString(sshExecTemplate + command + StrUtil.LF, buildSsh, charset); + } + // 上传文件 + String path = StrUtil.format("{}/.jpom/", sftp.home()); + String destFile = StrUtil.format("{}{}.sh", path, tempId); + sftp.mkDirs(path); + sftp.upload(destFile, buildSsh); + // 执行命令 + try { + String commandSh = "bash " + destFile; + return function.apply(commandSh); + } finally { + try { + // 删除 ssh 中临时文件 + sftp.delFile(destFile); + } catch (Exception e) { + log.warn(I18nMessageUtil.get("i18n.delete_ssh_temp_file_failure.6e5f"), e); + } + // 删除临时文件 + FileUtil.del(buildSsh); + } + } finally { + JschUtil.close(channel); + } + } + + /** + * 执行命令回调 + * + * @param session 会话 + * @param charset 字符编码格式 + * @param timeout 超时时间 + * @param lineHandler 消息回调 + * @param command 命令 + * @param commandParamsLine 执行参数 + * @throws IOException io + */ + public static void execCallbackLine(Session session, Charset charset, int timeout, String command, String commandParamsLine, Map env, LineHandler lineHandler) throws IOException { + String command2 = StringUtil.formatStrByMap(command, env); + execCallbackLine(session, charset, timeout, command2, commandParamsLine, lineHandler); + } + + /** + * 执行命令回调 + * + * @param session 会话 + * @param charset 字符编码格式 + * @param timeout 超时时间 + * @param lineHandler 消息回调 + * @param command 命令 + * @param commandParamsLine 执行参数 + * @throws IOException io + */ + public static int execCallbackLine(Session session, Charset charset, int timeout, String command, String commandParamsLine, LineHandler lineHandler) throws IOException { + // + return execCallbackLine(session, charset, timeout, command, commandParamsLine, lineHandler, lineHandler); + } + + /** + * 执行命令回调 + * + * @param session 会话 + * @param charset 字符编码格式 + * @param timeout 超时时间 + * @param normal 消息回调 + * @param error 错误消息 + * @param command 命令 + * @param commandParamsLine 执行参数 + * @throws IOException io + */ + public static int execCallbackLine(Session session, Charset charset, int timeout, String command, String commandParamsLine, LineHandler normal, LineHandler error) throws IOException { + // + return JschUtils.uploadCommandCallback(session, charset, timeout, (s) -> { + ChannelExec channel = (ChannelExec) JschUtil.createChannel(session, ChannelType.EXEC); + channel.setCommand(StrUtil.bytes(s + StrUtil.SPACE + commandParamsLine, charset)); + channel.setInputStream(null); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + channel.setErrStream(outputStream, true); + try (InputStream in = channel.getInputStream()) { + // 不添加超时,添加超时后可能存在阻塞 + channel.connect(); + IoUtil.readLines(in, charset, normal); + } + // 输出错误信息 + int size = outputStream.size(); + if (size > 0) { + error.handle(outputStream.toString(charset.name())); + } + return channel.getExitStatus(); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } finally { + JschUtil.close(channel); + } + + }, command); + } +} diff --git a/modules/sub-plugin/svn-clone/pom.xml b/modules/sub-plugin/svn-clone/pom.xml new file mode 100644 index 0000000000..5a5e280cc1 --- /dev/null +++ b/modules/sub-plugin/svn-clone/pom.xml @@ -0,0 +1,147 @@ + + + + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + 4.0.0 + svn-clone + plugin-svn-clone + + 8 + 8 + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + org.tmatesoft.svnkit + svnkit + 1.10.11 + + + net.java.dev.jna + jna + + + net.java.dev.jna + jna-platform + + + com.trilead + trilead-ssh2 + + + org.apache.sshd + sshd-core + + + org.apache.sshd + sshd-common + + + com.trilead + trilead-ssh2 + + + + + org.apache.sshd + sshd-core + 2.12.0 + + + com.trilead + trilead-ssh2 + 1.0.0-build222 + + + net.java.dev.jna + jna + ${jna-version} + + + net.java.dev.jna + jna-platform + ${jna-version} + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + false + + script/release.xml + + + + + + + + make-assembly + package + + single + + + + + + + + diff --git a/modules/sub-plugin/svn-clone/script/release.xml b/modules/sub-plugin/svn-clone/script/release.xml new file mode 100644 index 0000000000..3dd7b569ed --- /dev/null +++ b/modules/sub-plugin/svn-clone/script/release.xml @@ -0,0 +1,43 @@ + + + + release + false + + jar + + + + + / + false + true + runtime + + + org.tmatesoft.svnkit:svnkit + + + + + + + + ${project.build.directory}/classes + / + + + + diff --git a/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/AuthenticationManager.java b/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/AuthenticationManager.java new file mode 100644 index 0000000000..7027524497 --- /dev/null +++ b/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/AuthenticationManager.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider; +import org.tmatesoft.svn.core.auth.ISVNProxyManager; +import org.tmatesoft.svn.core.auth.SVNAuthentication; +import org.tmatesoft.svn.core.io.SVNRepository; + +import javax.net.ssl.TrustManager; +import java.util.Optional; + +/** + * svn 授权管理 + * + * @author bwcx_jzy + * @since 2023/2/23 + */ +public class AuthenticationManager implements ISVNAuthenticationManager { + + private final ISVNAuthenticationManager delegate; + private final Integer timeout; + + public AuthenticationManager(ISVNAuthenticationManager delegate, + Integer timeout) { + this.delegate = delegate; + this.timeout = timeout; + } + + @Override + public void setAuthenticationProvider(ISVNAuthenticationProvider provider) { + delegate.setAuthenticationProvider(provider); + } + + @Override + public ISVNProxyManager getProxyManager(SVNURL url) throws SVNException { + return delegate.getProxyManager(url); + } + + @Override + public TrustManager getTrustManager(SVNURL url) throws SVNException { + return delegate.getTrustManager(url); + } + + @Override + public SVNAuthentication getFirstAuthentication(String kind, String realm, SVNURL url) throws SVNException { + return delegate.getFirstAuthentication(kind, realm, url); + } + + @Override + public SVNAuthentication getNextAuthentication(String kind, String realm, SVNURL url) throws SVNException { + return delegate.getNextAuthentication(kind, realm, url); + } + + @Override + public void acknowledgeAuthentication(boolean accepted, String kind, String realm, SVNErrorMessage errorMessage, SVNAuthentication authentication) throws SVNException { + delegate.acknowledgeAuthentication(accepted, kind, realm, errorMessage, authentication); + } + + @Override + public void acknowledgeTrustManager(TrustManager manager) { + delegate.acknowledgeTrustManager(manager); + } + + @Override + public boolean isAuthenticationForced() { + return delegate.isAuthenticationForced(); + } + + @Override + public int getReadTimeout(SVNRepository repository) { + return Optional.ofNullable(timeout) + .map(integer -> integer <= 0 ? null : integer) + .map(integer -> integer * 1000) + .orElseGet(() -> delegate.getReadTimeout(repository)); + } + + @Override + public int getConnectTimeout(SVNRepository repository) { + return Optional.ofNullable(timeout) + .map(integer -> integer <= 0 ? null : integer) + .map(integer -> integer * 1000) + .orElseGet(() -> delegate.getConnectTimeout(repository)); + } +} diff --git a/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/DefaultSvnPluginImpl.java b/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/DefaultSvnPluginImpl.java new file mode 100644 index 0000000000..8182253852 --- /dev/null +++ b/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/DefaultSvnPluginImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.keepbx.jpom.plugins.PluginConfig; + +import java.io.File; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2021/12/23 + */ +@PluginConfig(name = "svn-clone") +public class DefaultSvnPluginImpl implements IWorkspaceEnvPlugin { + + @Override + public Object execute(Object main, Map parameter) throws Exception { + File savePath = (File) main; + // 转化密码字段 + parameter.put("password", convertRefEnvValue(parameter, "password")); + parameter.put("username", convertRefEnvValue(parameter, "username")); + return SvnKitUtil.checkOut(parameter, savePath); + } +} diff --git a/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/SvnKitUtil.java b/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/SvnKitUtil.java new file mode 100644 index 0000000000..f9d8db52be --- /dev/null +++ b/modules/sub-plugin/svn-clone/src/main/java/org/dromara/jpom/plugin/SvnKitUtil.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.plugin; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.SystemUtil; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.system.JpomRuntimeException; +import org.dromara.jpom.util.CommandUtil; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; +import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; +import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; +import org.tmatesoft.svn.core.internal.io.svn.ssh.SessionPoolFactory; +import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; +import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.Map; + +/** + * svn 工具 + *

+ * https://www.cnblogs.com/lekko/p/6005382.html + * + * @author bwcx_jzy + * @since 2019/8/6 + */ +public class SvnKitUtil { + + static { + // 指定使用 trilead 不使用 apache + SystemUtil.set(SessionPoolFactory.SVNKIT_SSH_CLIENT, SessionPoolFactory.TRILEAD); + // 初始化库。 必须先执行此操作。具体操作封装在setupLibrary方法中。 + /* + * For using over http:// and https:// + */ + DAVRepositoryFactory.setup(); + /* + * For using over svn:// and svn+xxx:// + */ + SVNRepositoryFactoryImpl.setup(); + + /* + * For using over file:/// + */ + FSRepositoryFactory.setup(); + } + + private static final DefaultSVNOptions OPTIONS = SVNWCUtil.createDefaultOptions(true); + + /** + * 对SVNKit连接进行认证,并获取连接 + * + * @param map 仓库 + */ + public static SVNClientManager getAuthClient(Map map) { + String protocol = (String) map.get("protocolStr"); + //repositoryModel.getProtocol(); + String userName = (String) map.get("username"); + String password = StrUtil.emptyToDefault((CharSequence) map.get("password"), StrUtil.EMPTY); + // + ISVNAuthenticationManager authManager; + // + if (StrUtil.equalsIgnoreCase(protocol, "http")) { + authManager = SVNWCUtil.createDefaultAuthenticationManager(userName, password.toCharArray()); + } else if (StrUtil.equalsIgnoreCase(protocol, "ssh")) { + File dir = SVNWCUtil.getDefaultConfigurationDirectory(); + // ssh + File rsaFile = (File) map.get("rsaFile"); + //BuildUtil.getRepositoryRsaFile(repositoryModel); + char[] pwdEmpty = StrUtil.EMPTY.toCharArray(); + if (rsaFile == null) { + authManager = SVNWCUtil.createDefaultAuthenticationManager(dir, userName, pwdEmpty, null, pwdEmpty, true); + } else { + if (StrUtil.isEmpty(password)) { + authManager = SVNWCUtil.createDefaultAuthenticationManager(dir, userName, pwdEmpty, rsaFile, pwdEmpty, true); + } else { + authManager = SVNWCUtil.createDefaultAuthenticationManager(dir, userName, pwdEmpty, rsaFile, password.toCharArray(), true); + } + } + } else { + throw new IllegalStateException(I18nMessageUtil.get("i18n.protocol_type_not_supported2.e519")); + } + // 超时时间 + Integer timeout = (Integer) map.get("timeout"); + AuthenticationManager authenticationManager = new AuthenticationManager(authManager, timeout); + // 实例化客户端管理类 + return SVNClientManager.newInstance(OPTIONS, authenticationManager); + } + + /** + * 判断当前仓库url是否匹配 + * + * @param wcDir 仓库路径 + * @param map 参数 + * @return true 匹配 + * @throws SVNException 异常 + */ + private static Boolean checkUrl(File wcDir, Map map) throws SVNException { + String url = (String) map.get("url"); + // 实例化客户端管理类 + SVNClientManager clientManager = getAuthClient(map); + try { + // 通过客户端管理类获得updateClient类的实例。 + SVNWCClient wcClient = clientManager.getWCClient(); + SVNInfo svnInfo = null; + do { + try { + svnInfo = wcClient.doInfo(wcDir, SVNRevision.HEAD); + } catch (SVNException svn) { + if (svn.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { + checkOut(clientManager, url, wcDir); + } else { + throw svn; + } + } + } while (svnInfo == null); + String reUrl = svnInfo.getURL().toString(); + return reUrl.equals(url); + } finally { + clientManager.dispose(); + } + } + + /** + * SVN检出 + * + * @param map 参数 + * @param targetPath 目录 + * @return Boolean + * @throws SVNException svn + */ + public static String[] checkOut(Map map, File targetPath) throws SVNException { + // 实例化客户端管理类 + SVNClientManager ourClientManager = getAuthClient(map); + String url = (String) map.get("url"); + String path = FileUtil.getAbsolutePath(targetPath); + synchronized (StrUtil.concat(false, url, path).intern()) { + try { + if (targetPath.exists()) { + if (!FileUtil.file(targetPath, SVNFileUtil.getAdminDirectoryName()).exists()) { + CommandUtil.systemFastDel(targetPath); + } else { + // 判断url是否变更 + if (!checkUrl(targetPath, map)) { + CommandUtil.systemFastDel(targetPath); + } else { + ourClientManager.getWCClient().doCleanup(targetPath); + } + } + } + return checkOut(ourClientManager, url, targetPath); + } finally { + ourClientManager.dispose(); + } + } + } + + /** + * SVN检出 + * + * @param ourClientManager svn client + * @param url 仓库地址 + * @param targetPath 保存目录 + * @return String[] 第一个元素为版本号,第二个元素为描述 + * @throws SVNException svn 异常 + */ + private static String[] checkOut(SVNClientManager ourClientManager, String url, File targetPath) throws SVNException { + // 通过客户端管理类获得updateClient类的实例。 + SVNUpdateClient updateClient = ourClientManager.getUpdateClient(); + updateClient.setIgnoreExternals(false); + // 相关变量赋值 + SVNURL svnurl = SVNURL.parseURIEncoded(url); + try { + // 要把版本库的内容check out到的目录 + long workingVersion = updateClient.doCheckout(svnurl, targetPath, SVNRevision.HEAD, SVNRevision.HEAD, SVNDepth.INFINITY, true); + return new String[]{workingVersion + "", String.format(I18nMessageUtil.get("i18n.checkout_version.a586"), workingVersion)}; + } catch (SVNAuthenticationException s) { + throw new JpomRuntimeException(I18nMessageUtil.get("i18n.incorrect_account_credentials.b2c5"), s); + } + } +} diff --git a/modules/sub-plugin/svn-clone/src/test/java/svn/TestSvn.java b/modules/sub-plugin/svn-clone/src/test/java/svn/TestSvn.java new file mode 100644 index 0000000000..43d4c527de --- /dev/null +++ b/modules/sub-plugin/svn-clone/src/test/java/svn/TestSvn.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package svn; + +import org.dromara.jpom.plugin.SvnKitUtil; +import org.tmatesoft.svn.core.SVNException; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2019/8/6 + */ +public class TestSvn { + public static void main(String[] args) throws SVNException { + +// RepositoryModel repositoryModel = new RepositoryModel(); +// repositoryModel.setGitUrl("svn://gitee.com/keepbx/Jpom-demo-case"); +// repositoryModel.setRepoType(RepositoryModel.RepoType.Svn.getCode()); +// repositoryModel.setUserName("a"); +// repositoryModel.setPassword("a"); +// repositoryModel.setProtocol(GitProtocolEnum.SSH.getCode()); + + Map map = new HashMap<>(10); + map.put("url", "svn://gitee.com/keepbx/Jpom-demo-case"); + map.put("username", "a"); + map.put("password", "a"); + map.put("protocol", "ssh"); + //File rsaFile = BuildUtil.getRepositoryRsaFile(repositoryModel); + //map.put("rsaFile", rsaFile); + + String[] s = SvnKitUtil.checkOut(map, new File("/test/tt")); + System.out.println(Arrays.toString(s)); + } +} diff --git a/modules/sub-plugin/webhook/pom.xml b/modules/sub-plugin/webhook/pom.xml new file mode 100644 index 0000000000..891e2e8c24 --- /dev/null +++ b/modules/sub-plugin/webhook/pom.xml @@ -0,0 +1,66 @@ + + + + + jpom-plugins-parent + org.dromara.jpom.plugins + 2.11.6.6 + ../pom.xml + + 4.0.0 + webhook + plugin-webhook + + + + + + + org.dromara.jpom + common + provided + ${project.version} + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + true + + ./ + + + + ${project.version} + + ${maven.build.timestamp} + ${project.artifactId} + https://gitee.com/dromara/Jpom + + + + + + + + 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 new file mode 100644 index 0000000000..bf77e6ec4b --- /dev/null +++ b/modules/sub-plugin/webhook/src/main/java/org/dromara/jpom/webhook/DefaultWebhookPluginImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Of Him Code Technology Studio + * Jpom is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +package org.dromara.jpom.webhook; + +import cn.hutool.core.text.CharPool; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.keepbx.jpom.plugins.PluginConfig; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.i18n.I18nMessageUtil; +import org.dromara.jpom.plugin.IDefaultPlugin; + +import java.util.Map; + +/** + * 默认到 webhook 实现 + * + * @author bwcx_jzy + * @since 2021/12/22 + */ +@PluginConfig(name = "webhook") +@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); + try (HttpResponse execute = httpRequest.execute()) { + String body = execute.body(); + log.info(webhook + CharPool.COLON + body); + return body; + } + } catch (Exception e) { + log.error(I18nMessageUtil.get("i18n.webhooks_invocation_error.9792"), e); + return "WebHooks error:" + e.getMessage(); + } + } +} diff --git a/pom.xml b/pom.xml index d15e62cd1a..95507fb1db 100644 --- a/pom.xml +++ b/pom.xml @@ -1,219 +1,316 @@ + - 4.0.0 - pom - - modules/agent - modules/server - modules/common - - - org.springframework.boot - spring-boot-starter-parent - 2.4.8 - - io.jpom - jpom-parent - Jpom Java项目在线管理 - - 一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件 - - 2017 - 2.8.0 - https://gitee.com/dromara/Jpom - - UTF-8 - 1.8 - - 2.4.8 - - [2.1.0,2.1.99] - - - true - true - true - 5.7.16 - - - - cn.jiangzeyin.fast-boot - common-boot - ${common-boot.version} - - - - cn.hutool - hutool-all - - - - - - cn.hutool - hutool-all - ${hutool-all.version} - - - - org.springframework.boot - spring-boot-starter-websocket - - - - org.springframework.boot - spring-boot-starter-aop - - - - junit - junit - test - - - - - org.databene - contiperf - 2.3.4 - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.0 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.0 - - - attach-javadoc - package - - jar - - - - - - - date - a - 创建时间 - - - - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - - attach-sources - - jar - - - - - - - com.mycila - license-maven-plugin - 4.1 - - - generate-sources - - remove - format - - - - - true - -

LICENSE
- - - script/** - docs/** - web-vue/** - **/*.yml - **/*.md - **/*.xml - **/*.sql - .editorconfig - .gitignore - - - true - - SLASHSTAR_STYLE - - - - - - - - - install-plugin-profile - - - release-plugin-profile - - - maven-repo - https://oss.sonatype.org/content/repositories/snapshots/ - - - maven-repo - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - verify-gpg - verify - - sign - - - - - - - - - - - - MIT License - https://opensource.org/licenses/MIT - repo - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + pom + + modules/agent + modules/server + modules/common + modules/sub-plugin + modules/agent-transport + modules/storage-module + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + org.dromara.jpom + jpom-parent + Jpom (Java Project Online Manage) + + 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件 + + 2017 + 2.11.6.6 + https://gitee.com/dromara/Jpom + + UTF-8 + 1.8 + + 2.10.0 + + + true + true + + 5.8.27 + 2.0.46 + 2.0.46 + 5.14.0 + 0.2.16 + 6.4.12 + 9.0.86 + 2.2 + + 2.2.220 + 42.3.10 + + + + + junit + junit + test + + + + + org.databene + contiperf + 2.3.4 + test + + + + org.projectlombok + lombok + provided + + + + + + + cn.hutool + hutool-bom + ${hutool.version} + pom + + import + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + + org.bouncycastle + bcprov-jdk18on + 1.78 + + + + com.github.mwiede + jsch + ${mwiede.jsch-version} + + + + snakeyaml + org.yaml + ${yaml.version} + + + + tomcat-embed-core + org.apache.tomcat.embed + ${tomcat.version} + + + + tomcat-embed-el + org.apache.tomcat.embed + ${tomcat.version} + + + + tomcat-embed-websocket + org.apache.tomcat.embed + ${tomcat.version} + + + com.h2database + h2 + ${h2.version} + + + + org.postgresql + postgresql + ${postgresql.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + + + com.mycila + license-maven-plugin + 4.3 + + + generate-sources + + remove + format + + + + + true + + + +
HEADER.txt
+ + + ** + web-vue/index.html + **/dist/index.html + + + + + docs/** + + + + **/*.md + **/i18n/*.properties + **/*.txt + **/dist/assets/** + +
+
+ + true + + SLASHSTAR_STYLE + +
+
+
+
+ + + + release + + + maven-repo + https://oss.sonatype.org/content/repositories/snapshots/ + + + maven-repo + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + attach-javadoc + package + + jar + + + + -Xdoclint:none + + + + + + + date + a + 创建时间 + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + verify-gpg + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + + + + + + + MulanPSL2 License + http://license.coscl.org.cn/MulanPSL2 + repo + + diff --git a/script/docker-buildx.md b/script/docker-buildx.md new file mode 100644 index 0000000000..9dd293499e --- /dev/null +++ b/script/docker-buildx.md @@ -0,0 +1,57 @@ +# 使用 buildx 构建多平台 Docker 镜像 + +[https://blog.bwcxtech.com/posts/43dd6afb/](https://blog.bwcxtech.com/posts/43dd6afb/) + +## 1. 启用 buildx 插件 + +开启 Dockerd 的实验特性 + +要想使用 buildx,首先要确保 Docker 版本不低于 19.03,同时还要通过设置环境变量 DOCKER_CLI_EXPERIMENTAL +来启用。可以通过下面的命令来为当前终端启用 buildx 插件: + +编辑 /etc/docker/daemon.json,新增如下条目 + +```json +{ + "experimental": true +} +``` + +## 2 启用 binfmt_misc + +如果您使用的是 Docker 桌面版(MacOS 和 Windows),默认已经启用了 binfmt_misc,可以跳过这一步。 + +如果您使用的是 Linux,需要手动启用 binfmt_misc。大多数 Linux 发行版都很容易启用,不过还有一个更容易的办法,直接运行一个特权容器,容器里面写好了设置脚本: + +```shell +docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64 +``` + +建议将 Linux 内核版本升级到 4.x 以上,特别是 CentOS 用户,您可能会遇到错误。 + +在软件依赖中我们提到需要 Linux 内核版本>= 4.8.0;如果在内核版本为3.10.0的系统(比如 CentOS)上运行 docker/binfmt,会出现报错 +Cannot write to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid +argument,这是由于内核不支持(F)标志造成的。出现这种情况,建议您升级系统内核或者换使用较高版本内核的 Linux 发行版。 + +验证是 binfmt_misc 否开启: + +```shell +ls -al /proc/sys/fs/binfmt_misc/ +``` + +## 3 从默认的构建器切换到多平台构建器 + +Docker 默认会使用不支持多 CPU 架构的构建器,我们需要手动切换。 + +先创建一个新的构建器: + +```shell +docker buildx create --use --name mybuilder +docker buildx use mybuilder +``` +启动构建器: + +```shell +docker buildx inspect mybuilder --bootstrap +``` + diff --git a/script/docker-tls.sh b/script/docker-tls.sh new file mode 100644 index 0000000000..83be2f27a5 --- /dev/null +++ b/script/docker-tls.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# +# ------------------------------------------------------------- +# 自动创建 Docker TLS 证书 +# wget https://gitee.com/dromara/Jpom/raw/master/script/docker-tls.sh +# curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun +# systemctl daemon-reload && systemctl restart docker +# ------------------------------------------------------------- +# 以下是配置信息 +# --[BEGIN]------------------------------ +NOW_PATH=$( + cd "$(dirname "$0")" || exit + pwd +)"/" +echo "当前目录:${NOW_PATH} 证书文件将保存在此文件夹下" +read -p "请输入证书使用的 IP 地址或者 HOST: " HOST +# +echo "您输入的是:${HOST} 证书只能在这个 IP 或者 HOST 下使用,证书密码和输入的一致" +# --[INIT PARAMETER]------------------------------ +PASSWORD="$HOST" +COUNTRY="CN" +STATE="$HOST" +CITY="$HOST" +ORGANIZATION="$HOST" +ORGANIZATIONAL_UNIT="Dev" +COMMON_NAME="$HOST" +EMAIL="$HOST@docker-tls.com" +# --[END]-- +# Generate CA key +openssl genrsa -aes256 -passout "pass:$PASSWORD" -out "ca-key.pem" 4096 +# Generate CA +openssl req -new -x509 -days 365 -key "ca-key.pem" -sha256 -out "ca.pem" -passin "pass:$PASSWORD" -subj "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORGANIZATION/OU=$ORGANIZATIONAL_UNIT/CN=$COMMON_NAME/emailAddress=$EMAIL" +# Generate Server key +openssl genrsa -out "server-key.pem" 4096 +# Generate Server Certs. +openssl req -subj "/CN=$COMMON_NAME" -sha256 -new -key "server-key.pem" -out server.csr +rm -f extfile.cnf +echo "subjectAltName = DNS.1:$HOST,IP.1:127.0.0.1,IP.2:$HOST" >>extfile.cnf +echo "extendedKeyUsage = serverAuth" >>extfile.cnf +openssl x509 -req -days 365 -sha256 -in server.csr -passin "pass:$PASSWORD" -CA "ca.pem" -CAkey "ca-key.pem" -CAcreateserial -out "server-cert.pem" -extfile extfile.cnf +# Generate Client Certs. +rm -f extfile.cnf +openssl genrsa -out "key.pem" 4096 +openssl req -subj '/CN=client' -new -key "key.pem" -out client.csr +echo "extendedKeyUsage = clientAuth" >>extfile.cnf +openssl x509 -req -days 365 -sha256 -in client.csr -passin "pass:$PASSWORD" -CA "ca.pem" -CAkey "ca-key.pem" -CAcreateserial -out "cert.pem" -extfile extfile.cnf +rm -f client.csr server.csr ca.srl extfile.cnf + +# check +if [ -f "${NOW_PATH}key.pem" -a -f "${NOW_PATH}ca.pem" -a -f "${NOW_PATH}ca-key.pem" -a -f "${NOW_PATH}server-cert.pem" -a -f "${NOW_PATH}server-key.pem" ]; then + echo "证书生成完成" + echo "客户端使用文件:key.pem ca.pem cert.pem" + echo "Docker 端使用文件:ca.pem server-cert.pem server-key.pem" + echo "Docker 推荐配置内容:-H tcp://0.0.0.0:2375 --tlsverify --tlscacert=${NOW_PATH}ca.pem --tlscert=${NOW_PATH}server-cert.pem --tlskey=${NOW_PATH}server-key.pem" +else + echo "证书生成不完成,请检查配置和根据错误日志排查" +fi diff --git a/script/docker.sh b/script/docker.sh new file mode 100644 index 0000000000..f55a6b7e0f --- /dev/null +++ b/script/docker.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +#----------------------------------------------------------- +# 此脚本用于每次发布 docker 镜像 +#----------------------------------------------------------- + +# https://hub.docker.com/r/jpomdocker/jpom + +# docker buildx create --use + +# 服务端 +docker buildx build --platform linux/amd64,linux/arm64,linux/ppc64le,linux/arm64/v8 -t jpomdocker/jpom:2.11.6.1 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push . + + + +docker buildx build --platform linux/amd64,linux/arm64,linux/ppc64le,linux/arm64/v8 -t jpomdocker/jpom:2-test -f ./modules/server/DockerfileBeta --push . +# +#docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push . + +# docker logs --tail="100" jpom-server +# docker run -d -p 2122:2122 --name jpom-server -v /etc/localtime:/etc/localtime:ro -v jpom-server-vol:/usr/local/jpom-server jpomdocker/jpom:mac-arm-2.11.6.1 +# docker run -d -p 2122:2122 --name jpom-server -v D:/home/jpom-server/logs:/usr/local/jpom-server/logs -v D:/home/jpom-server/data:/usr/local/jpom-server/data -v D:/home/jpom-server/conf:/usr/local/jpom-server/conf jpomdocker/jpom +# docker stop jpom-server +# docker rm jpom-server +# docker exec -it jpom-server /bin/bash +# docker-compose up -d --build +# docker buildx imagetools inspect jpomdocker/jpom + diff --git a/script/gitee_pages.sh b/script/gitee_pages.sh new file mode 100644 index 0000000000..191972d847 --- /dev/null +++ b/script/gitee_pages.sh @@ -0,0 +1,104 @@ +#!/usr/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + + +# Script to deploy certificate to a Gitee hosted page + +# The following variables exported from environment will be used. +# If not set then values previously saved in domain.conf file are used. + +# All the variables are required +# acme.sh --deploy -d xxx --deploy-hook gitee_pages + +# export GITEE_TOKEN="xxx" +# export GITEE_OWNER_ID="xxx" +# export GITEE_REPO_ID="xxx" + +gitee_pages_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if [ -z "$GITEE_TOKEN" ]; then + if [ -z "$Le_Deploy_gitee_token" ]; then + _err "GITEE_TOKEN not defined." + return 1 + fi + else + Le_Deploy_gitee_token="$GITEE_TOKEN" + _savedomainconf Le_Deploy_gitee_token "$Le_Deploy_gitee_token" + fi + + if [ -z "$GITEE_OWNER_ID" ]; then + if [ -z "$Le_Deploy_gitee_owner_id" ]; then + _err "GITEE_OWNER_ID not defined." + return 1 + fi + else + Le_Deploy_gitee_owner_id="$GITEE_OWNER_ID" + _savedomainconf Le_Deploy_gitee_owner_id "$Le_Deploy_gitee_owner_id" + fi + + if [ -z "$GITEE_REPO_ID" ]; then + if [ -z "$Le_Deploy_gitee_repo_id" ]; then + _err "GITEE_REPO_ID not defined." + return 1 + fi + else + Le_Deploy_gitee_repo_id="$GITEE_REPO_ID" + _savedomainconf Le_Deploy_gitee_repo_id "$Le_Deploy_gitee_repo_id" + fi + + string_fullchain=$(_base64 <"$_cfullchain") + string_key=$(_base64 <"$_ckey") + + body="access_token=$Le_Deploy_gitee_token&ssl_certificate_crt=$string_fullchain&ssl_certificate_key=$string_key&domain=$_cdomain" + + gitee_url="https://gitee.com/api/v5/repos/$Le_Deploy_gitee_owner_id/$Le_Deploy_gitee_repo_id/pages" + + _response=$(_post "$body" "$gitee_url" 0 PUT | _dbase64) + + error_response="error" + + if test "${_response#*$error_response}" != "$_response"; then + _err "Error in deploying certificate:" + _err "$_response" + return 1 + fi + + _debug response "$_response" + + body="access_token=$Le_Deploy_gitee_token" + gitee_url="https://gitee.com/api/v5/repos/$Le_Deploy_gitee_owner_id/$Le_Deploy_gitee_repo_id/pages/builds" + + _response=$(_post "$body" "$gitee_url" 0 POST | _dbase64) + + error_response="error" + + if test "${_response#*$error_response}" != "$_response"; then + _err "Error in deploying builds certificate:" + _err "$_response" + return 1 + fi + _debug response "$_response" + + _info "Certificate successfully deployed" + + return 0 +} diff --git a/script/install-node.sh b/script/install-node.sh new file mode 100644 index 0000000000..0bc70cb734 --- /dev/null +++ b/script/install-node.sh @@ -0,0 +1,42 @@ +#!/usr/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# shellcheck disable=SC2016,SC2119,SC2155,SC2206,SC2207,SC2254 + +node_version=18.19.0 + +ARCH="$(dpkg --print-architecture)" +case "${ARCH}" in +aarch64 | arm64) + BINARY_ARCH='arm64' + ;; +amd64 | x86_64) + BINARY_ARCH='x64' + ;; +*) + echo "Unsupported arch: ${ARCH}" + exit 1 + ;; +esac + +curl -LfsSo /opt/node-v${node_version}-linux-${BINARY_ARCH}.tar.gz https://npmmirror.com/mirrors/node/v${node_version}/node-v${node_version}-linux-${BINARY_ARCH}.tar.gz +tar -zxvf /opt/node-v${node_version}-linux-${BINARY_ARCH}.tar.gz -C /opt/ + + +cat >"/etc/profile" < ${ESUM}" + +curl -LfsSo $1-${jpom_version}-release.$2 https://download.jpom.top/${jpom_tag}/${jpom_version}/$1-${jpom_version}-release.$2 + +echo "${ESUM} $1-${jpom_version}-release.$2" | sha1sum -c -; + +rm -f $1-${jpom_version}-release.$2.sha1 $1-${jpom_version}-release.$2 +} + +# check agent +checkItem agent tar.gz +checkItem agent zip + +# check server +checkItem server tar.gz +checkItem server zip + diff --git a/script/release.bat b/script/release.bat index a8dae53eff..5b075295b6 100644 --- a/script/release.bat +++ b/script/release.bat @@ -1,9 +1,19 @@ +@REM +@REM Copyright (c) 2019 Of Him Code Technology Studio +@REM Jpom is licensed under Mulan PSL v2. +@REM You can use this software according to the terms and conditions of the Mulan PSL v2. +@REM You may obtain a copy of Mulan PSL v2 at: +@REM http://license.coscl.org.cn/MulanPSL2 +@REM THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +@REM See the Mulan PSL v2 for more details. +@REM + @echo off -@REM ٴĿű +@REM ���ٴ����Ŀ�ű� -@REM ǰ +@REM ����ǰ�� call cd ../ && cd web-vue && npm i && npm run build -@REM Java +@REM ���� Java call cd ../ && mvn clean package diff --git a/script/release.sh b/script/release.sh index 9175b8cc3e..754b0cefbc 100644 --- a/script/release.sh +++ b/script/release.sh @@ -1,4 +1,14 @@ #!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + # 快速打包项目脚本 @@ -9,4 +19,4 @@ cd ../ && cd web-vue && rm -rf node_modules cd ../ && cd web-vue && npm i && npm run build # 构建 Java -cd ../ && mvn clean package +cd ../ && mvn clean && mvn clean package diff --git a/script/replaceVersion.sh b/script/replaceVersion.sh new file mode 100644 index 0000000000..f1c8499c71 --- /dev/null +++ b/script/replaceVersion.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +#----------------------------------------------------------- +# 此脚本用于每次升级时替换相应位置的版本号 +#----------------------------------------------------------- + +set -o errexit + +current_path=$(pwd) +case "$(uname)" in +Linux) + bin_abs_path=$(readlink -f "$(dirname "$0")") + ;; +*) + bin_abs_path=$( + cd "$(dirname "$0")" + pwd + ) + ;; +esac +base=${bin_abs_path}/../ + +tag="$2" +echo "当前路径:${current_path} 脚本路径:${bin_abs_path} $tag" + +if [ -n "$1" ]; then + new_version="$1" + old_version=$(cat "${base}/script/tag.$tag.txt") + echo "$old_version 替换为新版本 $new_version" +else + # 参数错误,退出 + echo "ERROR: 请指定新版本!" 2>&2 + exit 1 +fi + +if [ ! -n "$old_version" ]; then + echo "ERROR: 旧版本不存在,请确认 /script/tag.$tag.txt 中信息正确" 2>&2 + exit 1 +fi + +echo "替换配置文件版本号 $new_version" + +if [ "$tag" == "release" ]; then + # 替换 Dockerfile 中的版本 + sed -i.bak "s/${old_version}/${new_version}/g" "$base/modules/server/Dockerfile" + sed -i.bak "s/${old_version}/${new_version}/g" "$base/modules/agent/Dockerfile" + sed -i.bak "s/${old_version}/${new_version}/g" "$base/script/docker.sh" + sed -i.bak "s/${old_version}/${new_version}/g" "$base/modules/server/DockerfileRelease" + sed -i.bak "s/${old_version}/${new_version}/g" "$base/modules/server/DockerfileReleaseJdk17" + # vue version + sed -i.bak "s/${old_version}/${new_version}/g" "$base/web-vue/package.json" + # 替换 docker 中的版本 + sed -i.bak "s/${old_version}/${new_version}/g" "$base/.env" + # gitee go + sed -i.bak "s/${old_version}/${new_version}/g" "$base/.workflow/MasterPipeline.yml" +elif [ "$tag" == "beta" ]; then + sed -i.bak "s/${old_version}/${new_version}/g" "$base/modules/server/DockerfileBeta" + sed -i.bak "s/${old_version}/${new_version}/g" "$base/modules/server/DockerfileBetaJdk17" +else + echo "不支持的模式 $tag" 2>&2 + exit 2 +fi + +# 替换所有模块pom.xml中的版本 +cd "${base}" && mvn -s "$base/script/settings.xml" versions:set -DnewVersion=$new_version + +# 替换 docker 中的版本 +sed -i.bak "s/${old_version}/${new_version}/g" "$base/env-$tag.env" + +# logo +cat >"$base/modules/common/src/main/resources/banner.txt" <"${base}/script/tag.$tag.txt" + +echo "版本号替换成功 $new_version" diff --git a/script/settings.xml b/script/settings.xml new file mode 100644 index 0000000000..50af7dfc2d --- /dev/null +++ b/script/settings.xml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aliyun + * + https://maven.aliyun.com/repository/public + + + + + + + + + + + + + + diff --git a/script/tag.beta.txt b/script/tag.beta.txt new file mode 100644 index 0000000000..d187d93659 --- /dev/null +++ b/script/tag.beta.txt @@ -0,0 +1 @@ +2.11.6.6 diff --git a/script/tag.release.txt b/script/tag.release.txt new file mode 100644 index 0000000000..03b7bd6915 --- /dev/null +++ b/script/tag.release.txt @@ -0,0 +1 @@ +2.11.6 diff --git a/script/temp.sh b/script/temp.sh new file mode 100644 index 0000000000..141dc57a40 --- /dev/null +++ b/script/temp.sh @@ -0,0 +1,11 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + +# mvn dependency:tree -Dverbose -Dincludes=: diff --git a/typography-specification.md b/typography-specification.md new file mode 100644 index 0000000000..9d61797cbe --- /dev/null +++ b/typography-specification.md @@ -0,0 +1,497 @@ +# 中英文排版规范 + +## 编写目的 + +统一团队成员的书写风格与规范,增强对外网站用户的浏览体验。 + + + +## 和其他排版指南的区别 + +市面上已经有很多相关的排版指南了,但是我个人阅读后感觉都不是特别好,要么就是一个点一个点的列举,然后下面跟着一大堆例子,这样不方便我们查阅某个点;要么就是只列出了点,没有具体的例子和说明,读完之后还是不知所以然。本指南整合了这两种的优缺点,先把点列出来,可以方便查阅,如果想查看具体的例子可以点击超链接跳转,或者在文章的末尾查看例子和说明。 + +**注意:本文所有的超链接都可以进行点击,点击后可以跳转到对应的示例或者原文。** + +文章的末尾是编写本文所有参考过的排版指南以及资料,感谢前辈们的贡献。 + + + +## 正确使用单词的大小写 + +以下例子均为**正例**,下面列出经常用错的大小写单词。 + +在使用的时候,如果您不确定单词正确的拼写,请前往官网查看。 + +- [Java](https://www.oracle.com/java/technologies/downloads/) +- [JDK](https://www.oracle.com/java/technologies/downloads/) +- [OpenJDK](https://openjdk.java.net/) +- [GitHub](https://github.com/) +- [GitLab](https://gitlab.com/) +- [HTML](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics) +- [CSS](https://www.w3schools.com/css/) +- [JavaScript](https://www.javascript.com/) +- [Spring](https://spring.io/) +- [MyBatis](https://mybatis.org/) +- [MyBatis-Plus](https://baomidou.com/) +- [Android](https://www.android.com/) +- [iOS](https://www.apple.com/ios/ios-15/) +- [Google](https://www.google.com/) +- [Docker](https://www.docker.com/) + +> 备注: +> +> 在 Docker 的 logo 中,使用的是小写的 docker,但是在文档中均使用的首字母大写的 Docker。可以参考 GitLab 官方中通过 Docker 方式安装 GitLab 的文档:[https://docs.gitlab.com/ee/install/docker.html](https://docs.gitlab.com/ee/install/docker.html) + + + +## 空格的使用 + +### 加空格的情况 + +- [英文半角句号后接单词要加空格](#英文半角句号后接单词要加空格) +- [英文半角逗号后接单词要加空格](#英文半角逗号后接单词要加空格) +- [英文半角小括号前后都要加空格](#英文半角小括号前后都要加空格) +- [中英文之间要加空格](#中英文之间要加空格) +- [中文与数字之间要加空格](#中文与数字之间要加空格) +- [数字与单位之间要加空格](#数字与单位之间要加空格) +- [超链接英文文本前后都要加空格](#超链接英文文本前后都要加空格) + +- [英文单词和英文单词之间要有一个空格](#英文单词和英文单词之间要有一个空格) +- [中文和英文单词之间要有一个空格](#中文和英文单词之间要有一个空格) + +### 不加空格的情况 + +- [中文全角标点符号前后都不加空格](#中文全角标点符号前后都不加空格) + +- [数字与度之间不加空格](#数字与度之间不加空格) +- [数字与百分比之间不加空格](#数字与百分比之间不加空格) +- [全角标点与其他字符之间不加空格](#全角标点与其他字符之间不加空格) +- [英文单词和半角句号之间不加空格](#英文单词和半角句号之间不加空格) +- [超链接中文文本前后都不加空格](#超链接中文文本前后都不加空格) + + + +## 标点符号的使用 + +- [中文句子中使用破折号 ——](#中文句子中使用破折号 ——) +- [不连续使用相同标点符号](#不连续使用相同标点符号) +- [中文句子中使用全角标点符号](#中文句子中使用全角标点符号) +- [英文句子中使用半角标点符号](#英文句子中使用半角标点符号) +- [中英文混用时使用全角标点符号](#中英文混用时使用全角标点符号) +- [数字使用半角字符](#数字使用半角字符) +- [遇到完整的英文整句、特殊名词,其内容使用半角标点](#遇到完整的英文整句、特殊名词,其内容使用半角标点) + + + +## 参考例子 + +下面的例子可以帮助您进一步理解对应的规范,以及为什么要有这样的规范,大公司的网站是怎么用到的。 + +### 空格的使用 + +#### 加空格的情况 + +##### 英文半角句号后接单词要加空格 + +正例: + +``` +GitHub is a code hosting platform for version control and collaboration. It lets you and others work together on projects from anywhere. +``` + +反例: + +``` +GitHub is a code hosting platform for version control and collaboration.It lets you and others work together on projects from anywhere. +``` + +> 参考资料:[https://docs.github.com/en/get-started/quickstart/hello-world](https://docs.github.com/en/get-started/quickstart/hello-world) + + + +##### 英文半角逗号后接单词要加空格 + +正例: + +``` +You can download, install and maintain your own GitLab instance. +``` + +反例: + +``` +You can download,install and maintain your own GitLab instance. +``` + +> 参考资料:[https://about.gitlab.com/install/](https://about.gitlab.com/install/) + + + +##### 英文半角小括号前后都要加空格 + +正例: + +``` +Virtual machines (VMs) are an abstraction of physical hardware turning one server into many servers. +``` + +反例: + +``` +Virtual machines(VMs)are an abstraction of physical hardware turning one server into many servers. +``` + +> 参考资料:[https://www.docker.com/resources/what-container/](https://www.docker.com/resources/what-container/) + + + +##### 中英文之间要加空格 + +正例: + +``` +将使用情况统计信息和崩溃报告自动发送给 Google,帮助我们完善 Google Chrome。 +``` + +反例: + +``` +将使用情况统计信息和崩溃报告自动发送给 Google,帮助我们完善 Google Chrome。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/](https://www.google.com/intl/zh-CN/chrome/) + + + +##### 中文与数字之间要加空格 + +正例: + +``` +适用于 Windows 11/10/8.1/8/7 64 位。 +``` + +反例: + +``` +适用于 Windows 11/10/8.1/8/7 64 位。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/](https://www.google.com/intl/zh-CN/chrome/) + + + +##### 数字与单位之间要加空格 + +正例: + +``` +The Omnibus GitLab package requires about 2.5 GB of storage space for installation. +``` + +> 说明:数字 2.5 和单位 GB 之间需要添加一个空格。 + +反例: + +``` +The Omnibus GitLab package requires about 2.5GB of storage space for installation. +``` + +> 参考资料:[https://docs.gitlab.com/ee/install/requirements.html](https://docs.gitlab.com/ee/install/requirements.html) + + + +##### 超链接英文文本前后都要加空格 + +正例: + +``` +如果您是来自 React 的开发者,您可能会对 Vuex 和 [Redux](https://github.com/reactjs/redux) 间的差异表示关注,Redux 是 React 生态环境中最流行的 Flux 实现。 +``` + +正例显示效果:如果您是来自 React 的开发者,您可能会对 Vuex 和 [Redux](https://github.com/reactjs/redux) 间的差异表示关注,Redux 是 React 生态环境中最流行的 Flux 实现。 + +反例: + +``` +如果您是来自 React 的开发者,您可能会对 Vuex 和[Redux](https://github.com/reactjs/redux)间的差异表示关注,Redux 是 React 生态环境中最流行的 Flux 实现。 +``` + +反例显示效果:如果您是来自 React 的开发者,您可能会对 Vuex 和[Redux](https://github.com/reactjs/redux)间的差异表示关注,Redux 是 React 生态环境中最流行的 Flux 实现。 + +> 参考资料:[https://docs.github.com/en/rest/overview/resources-in-the-rest-api](https://docs.github.com/en/rest/overview/resources-in-the-rest-api) + + + +##### 英文单词和英文单词之间要有一个空格 + +正例: + +``` +Hello World! +``` + +反例: + +``` +HelloWorld! +``` + + + +##### 中文和英文单词之间要有一个空格 + +正例: + +``` +通过 Gmail、Google Pay 和 Google 助理等 Google 应用,Chrome 可帮助您保持工作效率并充分利用您的浏览器。 +``` + +反例: + +``` +通过Gmail、Google Pay和Google助理等Google应用,Chrome可帮助您保持工作效率并充分利用您的浏览器。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/](https://www.google.com/intl/zh-CN/chrome/) + + + +#### 不加空格的情况 + +##### 中文全角标点符号前后都不加空格 + +正例: + +``` +您可为标签页分组,以便将彼此相关的网页保存在同一个工作区内。如需创建标签页组,只需右键点击任一标签页并选择“向新组添加标签页”即可。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/tips/](https://www.google.com/intl/zh-CN/chrome/tips/) + + + +##### 数字与度之间不加空格 + +正例: + +``` +角度为 90° 的角,就是直角。 +``` + +> 说明:90° 是一个整体,不应该用空格分开。 + +反例: + +``` +角度为 90 ° 的角,就是直角。 +``` + + + +##### 数字与百分比之间不加空格 + +正例: + +``` +我现在手机的电量是 100%。 +``` + +反例: + +``` +我现在手机的电量是 100 %。 +``` + + + +##### 全角标点与其他字符之间不加空格 + +正例: + +``` +期末考试通过了,好开心! +``` + +反例: + +``` +期末考试通过了, 好开心! +``` + + + +##### 英文单词和半角句号之间不加空格 + +正例: + +``` +All Roads Lead to Rome. +``` + +反例: + +``` +All Roads Lead to Rome . +``` + + + +##### 超链接中文文本前后都不加空格 + +正例: + +``` +Redux 事实上无法感知视图层,所以它能够轻松的通过一些[简单绑定](https://classic.yarnpkg.com/en/packages?q=redux%20vue&p=1)和 Vue 一起使用。 +``` + +正例显示效果:Redux 事实上无法感知视图层,所以它能够轻松的通过一些[简单绑定](https://classic.yarnpkg.com/en/packages?q=redux%20vue&p=1)和 Vue 一起使用。 + +反例: + +``` +Redux 事实上无法感知视图层,所以它能够轻松的通过一些 [简单绑定](https://classic.yarnpkg.com/en/packages?q=redux%20vue&p=1) 和 Vue 一起使用。 +``` + +反例显示效果:Redux 事实上无法感知视图层,所以它能够轻松的通过一些 [简单绑定](https://classic.yarnpkg.com/en/packages?q=redux%20vue&p=1) 和 Vue 一起使用。 + +> 参考资料:[https://cn.vuejs.org/v2/guide/state-management.html](https://cn.vuejs.org/v2/guide/state-management.html) + + + +### 标点符号的使用 + +#### 中文句子中使用破折号 —— + +正例: + +``` +我们刚才简单介绍了 Vue 核心最基本的功能——本教程的其余部分将更加详细地涵盖这些功能以及其它高级功能,所以请务必读完整个教程! +``` + +> 说明:破折号占两个字的位置,中间不能断开,上下居中。 + +反例: + +``` +我们刚才简单介绍了 Vue 核心最基本的功能—本教程的其余部分将更加详细地涵盖这些功能以及其它高级功能,所以请务必读完整个教程! +``` + +> 参考资料:[https://cn.vuejs.org/v2/guide/](https://cn.vuejs.org/v2/guide/) + + + +#### 不连续使用相同标点符号 + +正例: + +``` +如果您刚开始学习前端开发,将框架作为您的第一步可能不是最好的主意——掌握好基础知识再来吧! +``` + +反例: + +``` +如果您刚开始学习前端开发,将框架作为您的第一步可能不是最好的主意——掌握好基础知识再来吧!!! +``` + +> 说明:有时我们在书写的时候想进行感叹,可能会情不自禁地连续使用了相同的标点符号,但是请克制住自己,不要连续使用相同的标点符号。 + +> 参考资料:[https://cn.vuejs.org/v2/guide/#Vue-js-%E6%98%AF%E4%BB%80%E4%B9%88](https://cn.vuejs.org/v2/guide/#Vue-js-%E6%98%AF%E4%BB%80%E4%B9%88) + + + +#### 中文句子中使用全角标点符号 + +正例 1: + +``` +借助个人资料,您可单独保存自己的所有 Chrome 信息,例如书签、历史记录、密码及其他设置。个人资料最适合用于以下情况:多人共用一台计算机,或者您需要分隔自己的不同帐号(例如工作帐号和个人帐号)。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/browser-features/](https://www.google.com/intl/zh-CN/chrome/browser-features/) + +正例 2: + +``` +您可以根据自己的需求或心情,选择主题和颜色(例如“深色模式”)。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/browser-features/](https://www.google.com/intl/zh-CN/chrome/browser-features/) + + + +#### 英文句子中使用半角标点符号 + +正例: + +``` +You build it, you run it. +``` + +反例: + +``` +You build it,you run it。 +``` + + + +#### 中英文混用时使用全角标点符号 + +正例: + +``` +通过 Gmail、Google Pay 和 Google 助理等 Google 应用,Chrome 可帮助您保持工作效率并充分利用您的浏览器。 +``` + +> 参考资料:[https://www.google.com/intl/zh-CN/chrome/](https://www.google.com/intl/zh-CN/chrome/) + + + +#### 数字使用半角字符 + +正例: + +``` +这个蛋糕价值 1000 元。 +``` + +反例: + +``` +这个蛋糕只卖 1000 元。 +``` + + + +#### 遇到完整的英文整句、特殊名词,其内容使用半角标点 + +正例: + +``` +乔布斯那句话是怎么说的?「Stay hungry, stay foolish.」 +``` + +> 说明:中文句子使用中文全角标点符号,英文句子使用英文半角标点符号。 + + + + + +> 参考文献: +> +> [chinese-copywriting-guidelines](https://github.com/sparanoid/chinese-copywriting-guidelines) +> +> [Basic writing and formatting syntax - GitHub Docs](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) +> +> [写给大家看的中文排版指南](https://zhuanlan.zhihu.com/p/20506092) +> +> [Dubbo 博客文档中文排版指南](https://dubbo.apache.org/zh/blog/2018/01/01/dubbo-%E5%8D%9A%E5%AE%A2%E6%96%87%E6%A1%A3%E4%B8%AD%E6%96%87%E6%8E%92%E7%89%88%E6%8C%87%E5%8D%97/) +> +> [中文文本排版指南](https://zhiqiang.org/it/chinese-copywriting-guidelines.html) +> +> [Vue.js 内部文档规范](https://github.com/vuejs/cn.vuejs.org/wiki) + + + diff --git a/web-vue/.editorconfig b/web-vue/.editorconfig index 5f75a18c1b..1ee7c2812d 100644 --- a/web-vue/.editorconfig +++ b/web-vue/.editorconfig @@ -1,3 +1,13 @@ +# +# Copyright (c) 2019 Of Him Code Technology Studio +# Jpom is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# + # http://editorconfig.org # 和父级配置一致,避免使用新的工作空间打开当前项目,无法获取到配置问题 @@ -12,7 +22,7 @@ trim_trailing_whitespace = true insert_final_newline = true max_line_length = 200 -[*.{json, yml}] +[*.{json,yml}] indent_style = space indent_size = 2 @@ -20,6 +30,10 @@ indent_size = 2 insert_final_newline = false trim_trailing_whitespace = false -[*.vue] +[*.{vue,js,ts}] indent_style = space indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/web-vue/.env b/web-vue/.env new file mode 100644 index 0000000000..f0c1b34f1c --- /dev/null +++ b/web-vue/.env @@ -0,0 +1,13 @@ +JPOM_APP_TITLE=项目运维系统 +JPOM_BASE_URL=./ +JPOM_BASE_API_URL=/api/ +JPOM_PORT=3000 + +# 百度翻译配置 +BAIDU_FY_ID='xxx' +BAIDU_FY_SECRET='xxx' +BAIDU_FY_GRANT_TYPE='client_credentials' +# i18n 日志 VERBOSE +CLI_VERBOSE=1 +# i18n 日志 debug +CLI_DEBUG=1 diff --git a/web-vue/.env.dev b/web-vue/.env.dev index ae4f221b2f..21a29ea6c3 100644 --- a/web-vue/.env.dev +++ b/web-vue/.env.dev @@ -1 +1 @@ -NODE_ENV = 'dev' \ No newline at end of file +JPOM_PROXY_HOST="127.0.0.1:2122" diff --git a/web-vue/.env.loc b/web-vue/.env.loc new file mode 100644 index 0000000000..21a29ea6c3 --- /dev/null +++ b/web-vue/.env.loc @@ -0,0 +1 @@ +JPOM_PROXY_HOST="127.0.0.1:2122" diff --git a/web-vue/.env.pro b/web-vue/.env.pro deleted file mode 100644 index 5dfe29576e..0000000000 --- a/web-vue/.env.pro +++ /dev/null @@ -1 +0,0 @@ -NODE_ENV = 'pro' \ No newline at end of file diff --git a/web-vue/.env.production b/web-vue/.env.production new file mode 100644 index 0000000000..5a8235b4c7 --- /dev/null +++ b/web-vue/.env.production @@ -0,0 +1 @@ +JPOM_BASE_API_URL=/ diff --git a/web-vue/.eslintignore b/web-vue/.eslintignore new file mode 100644 index 0000000000..494ec955d0 --- /dev/null +++ b/web-vue/.eslintignore @@ -0,0 +1,3 @@ +# 忽略 eslint 检查 +dist +node_modules diff --git a/web-vue/.eslintrc-auto-import.json b/web-vue/.eslintrc-auto-import.json new file mode 100644 index 0000000000..74a9e1d9ac --- /dev/null +++ b/web-vue/.eslintrc-auto-import.json @@ -0,0 +1,84 @@ +{ + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "EffectScope": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "InjectionKey": true, + "PropType": true, + "Ref": true, + "VNode": true, + "WritableComputedRef": true, + "acceptHMRUpdate": true, + "computed": true, + "createApp": true, + "createPinia": true, + "customRef": true, + "defineAsyncComponent": true, + "defineComponent": true, + "defineStore": true, + "effectScope": true, + "getActivePinia": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "inject": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "mapActions": true, + "mapGetters": true, + "mapState": true, + "mapStores": true, + "mapWritableState": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeRouteLeave": true, + "onBeforeRouteUpdate": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onUnmounted": true, + "onUpdated": true, + "provide": true, + "reactive": true, + "readonly": true, + "ref": true, + "resolveComponent": true, + "setActivePinia": true, + "setMapStoreSuffix": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "storeToRefs": true, + "toRaw": true, + "toRef": true, + "toRefs": true, + "toValue": true, + "triggerRef": true, + "unref": true, + "useAttrs": true, + "useCssModule": true, + "useCssVars": true, + "useLink": true, + "useRoute": true, + "useRouter": true, + "useSlots": true, + "watch": true, + "watchEffect": true, + "watchPostEffect": true, + "watchSyncEffect": true + } +} diff --git a/web-vue/.eslintrc-global-import.json b/web-vue/.eslintrc-global-import.json new file mode 100644 index 0000000000..5b8f6f23b7 --- /dev/null +++ b/web-vue/.eslintrc-global-import.json @@ -0,0 +1,17 @@ +{ + "globals": { + "$confirm": true, + "$error": true, + "$info": true, + "$message": true, + "$notification": true, + "$success": true, + "$warning": true, + "appStore": true, + "guideStore": true, + "jpomWindow": true, + "route": true, + "router": true, + "userStore": true + } +} diff --git a/web-vue/.eslintrc.json b/web-vue/.eslintrc.json new file mode 100644 index 0000000000..1274e3f198 --- /dev/null +++ b/web-vue/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "root": true, + "env": { + "node": true, + "es2021": true, + "browser": true + }, + "parser": "vue-eslint-parser", + "parserOptions": { + "ecmaVersion": "latest", + "parser": "@typescript-eslint/parser", + "ecmaFeatures": { + "jsx": true // 启用 jsx + } + }, + "plugins": ["prettier", "@typescript-eslint"], + "extends": [ + "eslint:recommended", // 内置规则 + "plugin:vue/vue3-recommended", // 支持 vue sfc + "prettier", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:prettier/recommended", + "eslint-config-prettier", + "./.eslintrc-auto-import.json", + "./.eslintrc-global-import.json" + ], + "rules": { + // 禁止使用 var,而应该用 let 或 const + "no-var": "error", + "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], + "vue/multi-word-component-names": "off", + // 处理换行 + "endOfLine": 0 + } +} diff --git a/web-vue/.gitignore b/web-vue/.gitignore index 403adbc1e5..b5dece5b8d 100644 --- a/web-vue/.gitignore +++ b/web-vue/.gitignore @@ -1,23 +1,29 @@ -.DS_Store -node_modules -/dist - - -# local env files -.env.local -.env.*.local - -# Log files +# Logs +.catch +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? + +# vite +.vite +stats.html diff --git a/web-vue/.prettierignore b/web-vue/.prettierignore index d17efb442a..de4d1f007d 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-vue/.prettierrc.json b/web-vue/.prettierrc.json new file mode 100644 index 0000000000..33875d9cf8 --- /dev/null +++ b/web-vue/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "endOfLine": "auto", + "proseWrap": "never", + "printWidth": 120, + "trailingComma": "none" +} diff --git a/web-vue/.vscode/extensions.json b/web-vue/.vscode/extensions.json new file mode 100644 index 0000000000..5c3b0b8b2c --- /dev/null +++ b/web-vue/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "Vue.volar", + "Vue.vscode-typescript-vue-plugin", + "mikestead.dotenv", + "steoates.autoimport", + "formulahendry.auto-rename-tag", + "EditorConfig.EditorConfig", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "eamodio.gitlens" + ] +} diff --git a/web-vue/.vscode/settings.json b/web-vue/.vscode/settings.json new file mode 100644 index 0000000000..90f770945b --- /dev/null +++ b/web-vue/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "editor.tabSize": 2, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.enable": true, + "eslint.alwaysShowStatus": true, + "prettier.enable": true, + "stylelint.enable": 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 23b577cef8..d4196ee4f2 100644 --- a/web-vue/README.md +++ b/web-vue/README.md @@ -1,91 +1,82 @@ -# 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 ` +
+
+
+ +
+
<%- title %>
+
+
+ +
+ + diff --git a/web-vue/jsconfig.json b/web-vue/jsconfig.json deleted file mode 100644 index f87334d482..0000000000 --- a/web-vue/jsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - } -} diff --git a/web-vue/package.json b/web-vue/package.json index d00dacec62..a718a0cff7 100644 --- a/web-vue/package.json +++ b/web-vue/package.json @@ -1,66 +1,63 @@ { - "name": "web-vue", - "version": "1.0.0", + "name": "jpom-vue3", "private": true, + "version": "2.11.6", + "type": "module", "scripts": { - "serve": "vue-cli-service serve --mode dev", - "build": "vue-cli-service build --mode pro", - "lint": "vue-cli-service lint" + "dev": "vite --mode dev", + "local": "vite --mode loc", + "build": "vite build", + "lint": "eslint --ext .ts,.js,.jsx,.vue .", + "lint:fix": "eslint --fix --ext .ts,.js,.jsx,.vue .", + "preview": "vite preview", + "i18n": "node ./bin/i18n.cjs" }, "dependencies": { - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "ant-design-vue": "^1.7.6", - "axios": "^0.21.1", - "core-js": "^3.16.0", - "echarts": "^4.9.0", - "element-ui": "^2.15.7", - "intro.js": "^3.4.0", - "jshint": "^2.13.1", - "markdown-it-vue": "^1.1.6", - "qs": "^6.9.4", - "sha1": "^1.1.1", - "vue": "^2.6.14", - "vue-codemirror": "^4.0.6", - "vue-json-viewer": "^2.2.20", - "vue-router": "^3.5.2", - "vuex": "^3.5.1", - "xterm": "^4.13.0", - "xterm-addon-attach": "^0.6.0", - "xterm-addon-fit": "^0.5.0" + "@ant-design/icons-vue": "^7.0.1", + "@codemirror/lang-javascript": "^6.2.2", + "ant-design-vue": "^4.1.2", + "axios": "^1.6.8", + "base64-js": "^1.5.1", + "codemirror": "^5.65.16", + "codemirror-editor-vue3": "^2.4.1", + "dayjs": "^1.11.10", + "echarts": "^5.5.0", + "js-sha1": "^0.7.0", + "markdown-it": "^14.1.0", + "pinia": "^2.1.7", + "prismjs": "^1.29.0", + "qs": "^6.12.1", + "spark-md5": "^3.0.2", + "vue": "^3.4.21", + "vue-i18n": "^9.12.1", + "vue-router": "^4.3.0", + "vue-virtual-scroller": "^2.0.0-beta.8", + "vue3-smooth-dnd": "^0.0.6", + "xterm": "^5.3.0", + "xterm-addon-attach": "^0.9.0", + "xterm-addon-fit": "^0.8.0" }, "devDependencies": { - "@vue/cli-plugin-babel": "~4.5.13", - "@vue/cli-plugin-eslint": "~4.5.13", - "@vue/cli-service": "~4.5.13", - "@vue/eslint-config-prettier": "^6.0.0", - "babel-eslint": "^10.1.0", - "babel-plugin-component": "^1.1.1", - "babel-plugin-import": "^1.13.3", - "eslint": "^6.8.0", - "eslint-plugin-prettier": "^3.4.0", - "eslint-plugin-vue": "^7.15.1", - "prettier": "^2.3.2", - "stylus": "^0.54.8", - "stylus-loader": "^3.0.2", - "vue-template-compiler": "^2.6.14" - }, - "eslintConfig": { - "root": true, - "env": { - "node": true - }, - "extends": [ - "plugin:vue/essential", - "eslint:recommended" - ], - "parserOptions": { - "parser": "babel-eslint" - }, - "rules": {} - }, - "browserslist": [ - "> 1%", - "last 2 versions", - "not dead" - ] + "@types/node": "^20.10.8", + "@types/vue-i18n": "^7.0.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", + "@vitejs/plugin-vue": "^5.0.4", + "@vitejs/plugin-vue-jsx": "^3.1.0", + "dotenv": "^16.4.5", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-vue": "^9.25.0", + "https": "^1.0.0", + "jpom-i18n": "^1.0.17", + "less": "^4.2.0", + "prettier": "^3.2.5", + "rollup-plugin-visualizer": "^5.12.0", + "typescript": "^5.4.5", + "unplugin-auto-import": "^0.17.5", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.2.8", + "vite-plugin-html": "^3.2.2" + } } diff --git a/web-vue/prettier.js b/web-vue/prettier.js deleted file mode 100644 index a9988ce1cf..0000000000 --- a/web-vue/prettier.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - singleQuote: true, - trailingComma: 'all', - printWidth: 200, - proseWrap: 'never', - endOfLine: 'lf', - overrides: [ - { - files: '.prettierrc', - options: { - parser: 'json', - }, - } - ], -}; diff --git a/web-vue/public/favicon.ico b/web-vue/public/favicon.ico index 4adb8faba5..cd5c496350 100644 Binary files a/web-vue/public/favicon.ico and b/web-vue/public/favicon.ico differ diff --git a/web-vue/public/index.html b/web-vue/public/index.html deleted file mode 100644 index 9dd11a467c..0000000000 --- a/web-vue/public/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - - -
- -
- - diff --git a/web-vue/src/App.vue b/web-vue/src/App.vue index e771e3b489..dd8e42aef0 100644 --- a/web-vue/src/App.vue +++ b/web-vue/src/App.vue @@ -1,65 +1,198 @@ + +// const { useToken } = theme +// const { token } = useToken() +// console.log(token.value) +//console.log(theme) + +// 监听系统主题模式 +const onMatchMediaChange = (e: MediaQueryListEvent) => { + useGuideStore.setSystemIsDark(e.matches) +} +const changeI18n = (lang: string) => { + changeLang(lang).then((antdLoadLang) => { + antdLang.value = antdLoadLang.default + }) +} +//console.log('app', new Date().getTime()) +onMounted(() => { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', onMatchMediaChange) + changeI18n(nowLang.value) +}) + +onUnmounted(() => { + window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', onMatchMediaChange) +}) + +// https://www.antdv.com/docs/vue/customize-theme-cn +// theme.defaultAlgorithm +// theme.darkAlgorithm +// theme.compactAlgorithm +const themeAlgorithm = computed(() => { + const algorithm: any = [] + if (getGuideCache.compactView) { + algorithm.push(theme.compactAlgorithm) + } + const themeDiy = useGuideStore.getThemeView() + if (themeDiy === 'light') { + if (getGuideCache.compactView) { + return algorithm + } + algorithm.push(theme.defaultAlgorithm) + } else if (themeDiy === 'dark') { + algorithm.push(theme.darkAlgorithm) + } + + return algorithm +}) + +const pageloading = ref(true) +const pageLoadingTimeout = ref() + +const useAppStore = appStore() - + diff --git a/web-vue/src/api/about.ts b/web-vue/src/api/about.ts new file mode 100644 index 0000000000..91ae6c1ad5 --- /dev/null +++ b/web-vue/src/api/about.ts @@ -0,0 +1,34 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +// Jpom 为开源软件,请基于开源协议用于商业用途 + +// 开源不等同于免费,如果您基于 Jpom 二次开发修改了 logo、名称、版权等,请联系我们授权,否则会有法律风险。 +// 我们有权利追诉破坏开源并因此获利的团队个人的全部违法所得,也欢迎给我们提供侵权线索。 + +// 二次修改不可删除或者修改版权,否则可能承担法律责任 + +// 擅自修改或者删除版权信息有法律风险,请尊重开源协议,不要擅自修改版本信息,否则可能承担法律责任。 + +import axios from './config' + +export function getLicense() { + return axios({ + url: '/about/license', + method: 'get' + }) +} + +export function getThankDependency() { + return axios({ + url: '/about/thank-dependency', + method: 'get' + }) +} diff --git a/web-vue/src/api/backup-info.js b/web-vue/src/api/backup-info.js deleted file mode 100644 index dbd36632f4..0000000000 --- a/web-vue/src/api/backup-info.js +++ /dev/null @@ -1,122 +0,0 @@ -import axios from "./config"; - -/** - * 备份列表 - * @param { - * name: 备份名称 - * backupType: 备份类型{0: 全量, 1: 部分} - * } params - */ -export function getBackupList(params) { - return axios({ - url: "/system/backup/list", - method: "post", - data: params, - }); -} - -/** - * 获取数据库表名列表 - */ -export function getTableNameList() { - return axios({ - url: "/system/backup/table-name-list", - method: "post", - }); -} - -/** - * 创建备份信息 - * @param tableNameList 需要备份的表名称列表,没有默认表示全量备份 - */ -export function createBackup(tableNameList) { - const data = { - tableNameList, - }; - return axios({ - url: "/system/backup/create", - method: "post", - headers: { - "Content-Type": "application/json", - }, - data, - }); -} - -/** - * 删除备份信息 - * @param {*} id - */ -export function deleteBackup(id) { - return axios({ - url: "/system/backup/delete", - method: "post", - data: { id }, - }); -} - -/** - * 还原备份信息 - * @param {*} id - * @returns - */ -export function restoreBackup(id) { - return axios({ - url: "/system/backup/restore", - method: "post", - timeout: 0, - data: { id }, - }); -} - -/** - * 下载备份文件 - * @param {*} id - * @returns - */ -export function downloadBackupFile(id) { - return `/system/backup/download?id=${id}`; -} - -/** - * 上传 SQL 备份文件 - * @param { - * file: 文件 multipart/form-data - * bakcupType: 0 全量备份 1 部分备份 - * } formData - */ -export function uploadBackupFile(formData) { - return axios({ - url: "/system/backup/upload", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - method: "post", - // 0 表示无超时时间 - timeout: 0, - data: formData, - }); -} - -export const backupTypeArray = [ - { key: 0, value: "全量备份", disabled: false }, - { key: 1, value: "部分备份", disabled: false }, - { key: 2, value: "导入备份", disabled: true }, - { key: 3, value: "自动备份", disabled: true }, -]; - -export const arrayToMap = (arra) => { - let obj = {}; - arra.forEach((value) => { - obj[value.key] = value.value; - }); - return obj; -}; - -export const backupTypeMap = arrayToMap(backupTypeArray); - -export const backupStatusMap = { - 0: "备份中", - 1: "备份成功", - 2: "备份失败", -}; diff --git a/web-vue/src/api/backup-info.ts b/web-vue/src/api/backup-info.ts new file mode 100644 index 0000000000..81963625ab --- /dev/null +++ b/web-vue/src/api/backup-info.ts @@ -0,0 +1,135 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' +import { loadRouterBase } from './config' + +/** + * 备份列表 + * @param { + * name: 备份名称 + * backupType: 备份类型{0: 全量, 1: 部分} + * } params + */ +export function getBackupList(params) { + return axios({ + url: '/system/backup/list', + method: 'post', + data: params + }) +} + +/** + * 获取数据库表名列表 + */ +export function getTableNameList() { + return axios({ + url: '/system/backup/table-name-list', + method: 'post' + }) +} + +/** + * 创建备份信息 + * @param tableNameList 需要备份的表名称列表,没有默认表示api.backup-info.945bddc + */ +export function createBackup(tableNameList) { + const data = { + tableNameList + } + return axios({ + url: '/system/backup/create', + method: 'post', + headers: { + 'Content-Type': 'application/json' + }, + data + }) +} + +/** + * 删除备份信息 + * @param {*} id + */ +export function deleteBackup(id) { + return axios({ + url: '/system/backup/delete', + method: 'post', + data: { id } + }) +} + +/** + * 还原备份信息 + * @param {*} id + * @returns + */ +export function restoreBackup(id) { + return axios({ + url: '/system/backup/restore', + method: 'post', + timeout: 0, + data: { id } + }) +} + +/** + * 下载备份文件 + * @param {*} id + * @returns + */ +export function downloadBackupFile(id) { + return loadRouterBase('/system/backup/download', { + id: id + }) +} + +/** + * 上传 SQL 备份文件 + * @param { + * file: 文件 multipart/form-data + * bakcupType: 0 全量备份 1 部分备份 + * } formData + */ +export function uploadBackupFile(formData) { + return axios({ + url: '/system/backup/upload', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} + +export const backupTypeArray = [ + { key: 0, value: t('i18n_6d68bd5458'), disabled: false }, + { key: 1, value: t('i18n_67b667bf98'), disabled: false }, + { key: 2, value: t('i18n_90c0458a4c'), disabled: true }, + { key: 3, value: t('i18n_590e5b46a0'), disabled: true } +] + +export const arrayToMap = (arra) => { + const obj = {} + arra.forEach((value) => { + obj[value.key] = value.value + }) + return obj +} + +export const backupTypeMap = arrayToMap(backupTypeArray) + +export const backupStatusMap = { + 0: t('i18n_5d459d550a'), + 1: t('i18n_3ba621d736'), + 2: t('i18n_1012e09849') +} diff --git a/web-vue/src/api/build-info.js b/web-vue/src/api/build-info.js deleted file mode 100644 index 147e975965..0000000000 --- a/web-vue/src/api/build-info.js +++ /dev/null @@ -1,254 +0,0 @@ -import axios from "./config"; - -/** - * 构建列表 - * @param { - * group: 分组名称 - * } params - */ -export function getBuildList(params) { - return axios({ - url: "/build/list", - method: "post", - data: params, - }); -} - -/** - * 构建列表 - * @param { - * group: 分组名称 - * } params - */ -export function getBuildListAll() { - return axios({ - url: "/build/list_all", - method: "get", - }); -} - -/** - * 获取仓库分支信息 - * @param { - * repositoryId: 仓库id - * } params - */ -export function getBranchList(params) { - return axios({ - url: "/build/branch-list", - method: "post", - timeout: 0, - data: params, - }); -} - -/** - * 编辑构建信息 - * @param { - * id: 构建 ID - * name: 构建名称 - * group: 分组名称 - * branchName: 分支名称 - * branchTagName: 标签 - * script: 构建命令 - * resultDirFile: 构建产物目录 - * releaseMethod: 发布方法 - * extraData: 额外信息 JSON 字符串 - * repostitoryId: 仓库信息 - * } params - */ -export function editBuild(params) { - const data = { - id: params.id, - name: params.name, - repositoryId: params.repositoryId, - resultDirFile: params.resultDirFile, - script: params.script, - releaseMethod: params.releaseMethod, - branchName: params.branchName, - branchTagName: params.branchTagName, - group: params.group, - repoType: params.repoType, - // 其他参数 - extraData: params.extraData, - webhook: params.webhook, - }; - return axios({ - url: "/build/edit", - method: "post", - data, - }); -} - -/** - * 删除构建信息 - * @param {*} id - */ -export function deleteBuild(id) { - return axios({ - url: "/build/delete", - method: "post", - data: { id }, - }); -} - -/** - * 获取触发器地址 - * @param {*} id - */ -export function getTriggerUrl(id) { - return axios({ - url: "/build/trigger/url", - method: "post", - data: { id }, - }); -} - -/** - * 重置触发器 - * @param {*} id - */ -export function resetTrigger(id) { - return axios({ - url: "/build/trigger/rest", - method: "post", - data: { id }, - }); -} - -/** - * 清理构建 - * @param {*} id - */ -export function clearBuid(id) { - return axios({ - url: "/build/clean-source", - method: "post", - data: { id }, - }); -} - -/** - * 查看构建日志 - * @param {JSON} params { - * id: 构建 ID - * buildId: 构建任务 ID - * line: 需要获取的行号 1 开始 - * } - */ -export function loadBuildLog(params) { - return axios({ - url: "/build/manage/get-now-log", - method: "post", - data: params, - headers: { - tip: "no", - loading: "no", - }, - }); -} - -/** - * 开始构建 - * @param {*} id - */ -export function startBuild(id) { - return axios({ - url: "/build/manage/start", - method: "post", - data: { id }, - }); -} - -/** - * 停止构建 - * @param {*} id - */ -export function stopBuild(id) { - return axios({ - url: "/build/manage/cancel", - method: "post", - data: { id }, - }); -} - -/** - * 构建历史 - * @param { - * buildDataId: 构建任务 ID - * status: 状态 - * } params - */ -export function geteBuildHistory(params) { - return axios({ - url: "/build/history/history_list.json", - method: "post", - data: params, - }); -} - -/** - * 下载构建日志 - * @param {*} logId - */ -export function downloadBuildLog(logId) { - return `/build/history/download_log.html?logId=${logId}`; -} - -/** - * 下载构建产物 - * @param {*} logId - */ -export function downloadBuildFile(logId) { - return `/build/history/download_file.html?logId=${logId}`; -} - -/** - * 回滚(重新发布) - * @param {*} logId - * @returns - */ -export function rollback(logId) { - return axios({ - url: "/build/manage/reRelease", - method: "post", - data: { logId }, - }); -} - -/** - * 删除构建历史记录 - * @param {*} logId - */ -export function deleteBuildHistory(logId) { - return axios({ - url: "/build/history/delete_log.json", - method: "post", - data: { logId }, - }); -} - -export const statusMap = { - 1: "构建中", - 2: "构建完成", - 3: "构建失败", - 4: "发布中", - 5: "发布成功", - 6: "发布失败", - 7: "取消构建", -}; - -export const releaseMethodMap = { - 0: "不发布", - 1: "节点分发", - 2: "项目", - 3: "SSH", - 4: "本地命令", -}; - -export const releaseMethodArray = Object.keys(releaseMethodMap).map((item) => { - return { - value: Number(item), - name: releaseMethodMap[item], - }; -}); diff --git a/web-vue/src/api/build-info.ts b/web-vue/src/api/build-info.ts new file mode 100644 index 0000000000..76ba8d95ff --- /dev/null +++ b/web-vue/src/api/build-info.ts @@ -0,0 +1,337 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' +import { loadRouterBase } from './config' + +/** + * 构建列表 + * @param { + * group: 分组名称 + * } params + */ +export function getBuildList(params, loading) { + return axios({ + url: '/build/list', + method: 'post', + data: params, + headers: { + loading: loading === false ? 'no' : '' + } + }) +} + +/** + * 构建详情 + * @param { + * + * } params + */ +export function getBuildGet(params) { + return axios({ + url: '/build/get', + method: 'get', + params + }) +} + +/** + * 构建分组 + */ +export function getBuildGroupAll() { + return axios({ + url: '/build/list_group_all', + method: 'get' + }) +} + +/** + * 获取仓库分支信息 + * @param { + * repositoryId: 仓库id + * } params + */ +export function getBranchList(params) { + return axios({ + url: '/build/branch-list', + method: 'post', + timeout: 0, + data: params, + headers: {} + }) +} + +/** + * 编辑构建信息 + * @param { + * id: 构建 ID + * name: 构建名称 + * group: 分组名称 + * branchName: 分支名称 + * branchTagName: 标签 + * script: 构建命令 + * resultDirFile: 构建产物目录 + * releaseMethod: 发布方法 + * extraData: 额外信息 JSON 字符串 + * repostitoryId: 仓库信息 + * } params + */ +export function editBuild(params) { + const data = { + id: params.id, + name: params.name, + repositoryId: params.repositoryId, + resultDirFile: params.resultDirFile, + script: params.script, + releaseMethod: params.releaseMethod, + branchName: params.branchName, + branchTagName: params.branchTagName, + group: params.group, + repoType: params.repoType, + // 其他参数 + extraData: params.extraData, + webhook: params.webhook, + autoBuildCron: params.autoBuildCron, + buildMode: params.buildMode, + aliasCode: params.aliasCode, + resultKeepDay: params.resultKeepDay, + buildEnvParameter: params.buildEnvParameter + } + return axios({ + url: '/build/edit', + method: 'post', + data + }) +} + +/** + * 删除构建信息 + * @param {*} id + */ +export function deleteBuild(id) { + return axios({ + url: '/build/delete', + method: 'post', + data: { id } + }) +} + +export function deleteatchBuild(data) { + return axios({ + url: '/build/batch-delete', + method: 'post', + data: data + }) +} + +/** + * 获取触发器地址 + * @param {*} id + */ +export function getTriggerUrl(data) { + return axios({ + url: '/build/trigger/url', + method: 'post', + data: data + }) +} + +// /** +// * 重置触发器 +// * @param {*} id +// */ +// export function resetTrigger(id) { +// return axios({ +// url: "/build/trigger/rest", +// method: "post", +// data: { id }, +// }); +// } + +/** + * 清理构建 + * @param {*} id + */ +export function clearBuid(id) { + return axios({ + url: '/build/clean-source', + method: 'post', + data: { id } + }) +} + +/** + * 查看构建日志 + * @param {JSON} params { + * id: 构建 ID + * buildId: 构建任务 ID + * line: 需要获取的行号 1 开始 + * } + */ +export function loadBuildLog(params) { + return axios({ + url: '/build/manage/get-now-log', + method: 'post', + data: params, + headers: { + tip: 'no', + loading: 'no' + } + }) +} + +/** + * 开始构建 + * @param {*} id + */ +export function startBuild(data) { + return axios({ + url: '/build/manage/start', + method: 'post', + data: data + }) +} + +/** + * 停止构建 + * @param {*} id + */ +export function stopBuild(id) { + return axios({ + url: '/build/manage/cancel', + method: 'post', + data: { id } + }) +} + +/** + * 构建历史 + * @param { + * buildDataId: 构建任务 ID + * status: 状态 + * } params + */ +export function geteBuildHistory(params) { + return axios({ + url: '/build/history/history_list.json', + method: 'post', + data: params + }) +} + +/** + * 下载构建日志 + * @param {*} logId + */ +export function downloadBuildLog(logId) { + return loadRouterBase('/build/history/download_log', { + logId: logId + }) +} + +/** + * 下载构建产物 + * @param {*} logId + */ +export function downloadBuildFile(logId) { + return loadRouterBase('/build/history/download_file', { + logId: logId + }) +} + +/** + * 下载构建产物 + * @param {*} logId + */ +export function downloadBuildFileByBuild(id, numberId) { + return loadRouterBase('/build/history/download_file_by_build', { + buildId: id, + buildNumberId: numberId + }) +} + +/** + * 回滚(重新发布) + * @param {*} logId + * @returns + */ +export function rollback(logId) { + return axios({ + url: '/build/manage/reRelease', + method: 'post', + data: { logId } + }) +} + +/** + * 删除构建历史记录 + * @param {*} logId + */ +export function deleteBuildHistory(logId) { + return axios({ + url: '/build/history/delete_log.json', + method: 'post', + data: { logId } + }) +} + +export function sortItem(params) { + return axios({ + url: '/build/sort-item', + method: 'get', + params: params + }) +} + +export const statusMap = { + 1: t('i18n_32493aeef9'), + 2: t('i18n_641796b655'), + 3: t('i18n_41298f56a3'), + 4: t('i18n_0baa0e3fc4'), + 5: t('i18n_2fff079bc7'), + 6: t('i18n_250688d7c9'), + 7: t('i18n_b4fc1ac02c'), + 8: t('i18n_979b7d10b0'), + 9: t('i18n_81afd9e713'), + 10: t('i18n_8160b4be4e') +} +export const statusColor = { + 1: 'orange', + 2: 'green', + 3: 'red', + 4: 'orange', + 5: 'green', + 6: 'red', + 7: '', + 8: 'blue', + 9: 'orange', + 10: 'red' +} + +export const releaseMethodMap = { + 0: t('i18n_a189314b9e'), + 1: t('i18n_ae6838c0e6'), + 2: t('i18n_31ecc0e65b'), + 3: 'SSH', + 4: t('i18n_b71a7e6aab'), + 5: t('i18n_9136e1859a') +} + +export const triggerBuildTypeMap = { + 0: t('i18n_2a3e7f5c38'), + 1: t('i18n_4696724ed3'), + 2: t('i18n_72ebfe28b0'), + 3: t('i18n_31070fd376') +} + +export const buildModeMap = { + 0: t('i18n_69c3b873c1'), + 1: t('i18n_685e5de706') +} diff --git a/web-vue/src/api/build.js b/web-vue/src/api/build.js deleted file mode 100644 index ccc0c7913d..0000000000 --- a/web-vue/src/api/build.js +++ /dev/null @@ -1,245 +0,0 @@ -// import axios from './config'; - -// /** -// * 这个文件里面的 api 已废弃 -// */ - -// // 分组列表 -// export function getBuildGroupList() { -// return axios({ -// url: '/build/group-list', -// method: 'get' -// }) -// } - -// /** -// * 构建列表 -// * @param { -// * group: 分组名称 -// * } params -// */ -// export function getBuildList(params) { -// return axios({ -// url: '/build/list_data.json', -// method: 'post', -// data: params -// }) -// } - -// /** -// * 获取仓库分支信息 -// * @param { -// * url: 仓库地址 -// * userName: 用户名 -// * userPwd: 密码 -// * } params -// */ -// export function getBranchList(params) { -// return axios({ -// url: '/build/branchList.json', -// method: 'post', -// timeout: 0, -// data: params -// }) -// } - -// /** -// * 编辑构建信息 -// * @param { -// * id: 构建 ID -// * name: 构建名称 -// * gitUrl: 仓库地址 -// * userName: 登录用户 -// * password: 登录密码 -// * resultDirFile: 构建产物目录 -// * script: 构建命令 -// * releaseMethod: 发布方法 -// * branchName: 分支名称 -// * group: 分组名称 -// * repoType: 仓库类型 0: GIT | 1: SVN -// * } params -// */ -// export function editBuild(params) { -// const data = { -// id: params.id, -// name: params.name, -// gitUrl: params.gitUrl, -// userName: params.userName, -// password: params.password, -// resultDirFile: params.resultDirFile, -// script: params.script, -// releaseMethod: params.releaseMethod, -// branchName: params.branchName, -// group: params.group, -// repoType: params.repoType, -// // 其他参数 -// releaseMethodDataId_1: params.releaseMethodDataId_1, -// releaseMethodDataId_2_node: params.releaseMethodDataId_2_node, -// releaseMethodDataId_2_project: params.releaseMethodDataId_2_project, -// afterOpt: params.afterOpt, -// releaseMethodDataId_3: params.releaseMethodDataId_3, -// releasePath: params.releasePath, -// releaseCommand: params.releaseCommand, -// clearOld: params.clearOld, -// } -// return axios({ -// url: '/build/updateBuild', -// method: 'post', -// data -// }) -// } - -// /** -// * 删除构建信息 -// * @param {*} id -// */ -// export function deleteBuild(id) { -// return axios({ -// url: '/build/delete.json', -// method: 'post', -// data: {id} -// }) -// } - -// /** -// * 获取触发器地址 -// * @param {*} id -// */ -// export function getTriggerUrl(id) { -// return axios({ -// url: '/build/trigger-url', -// method: 'post', -// data: {id} -// }) -// } - -// /** -// * 重置触发器 -// * @param {*} id -// */ -// export function resetTrigger(id) { -// return axios({ -// url: '/build/trigger_rest.json', -// method: 'post', -// data: {id} -// }) -// } - -// /** -// * 清理构建 -// * @param {*} id -// */ -// export function clearBuid(id) { -// return axios({ -// url: '/build/cleanSource.json', -// method: 'post', -// data: {id} -// }) -// } - -// /** -// * 查看构建日志 -// * @param {JSON} params { -// * id: 构建 ID -// * buildId: 构建任务 ID -// * line: 需要获取的行号 1 开始 -// * } -// */ -// export function loadBuildLog(params) { -// return axios({ -// url: '/build/getNowLog.json', -// method: 'post', -// data: params, -// headers: { -// tip: 'no', -// loading: 'no' -// }, -// }) -// } - -// /** -// * 开始构建 -// * @param {*} id -// */ -// export function startBuild(id) { -// return axios({ -// url: '/build/start.json', -// method: 'post', -// data: {id} -// }) -// } - -// /** -// * 停止构建 -// * @param {*} id -// */ -// export function stopBuild(id) { -// return axios({ -// url: '/build/cancel.json', -// method: 'post', -// data: {id} -// }) -// } - -// /** -// * 构建历史 -// * @param { -// * buildDataId: 构建任务 ID -// * status: 状态 -// * } params -// */ -// export function geteBuildHistory(params) { -// return axios({ -// url: '/build/history_list.json', -// method: 'post', -// data: params -// }) -// } - -// /** -// * 下载构建日志 -// * @param {*} logId -// */ -// export function downloadBuildLog(logId) { -// return `/build/download_log.html?logId=${logId}` -// } - -// /** -// * 下载构建产物 -// * @param {*} logId -// */ -// export function downloadBuildFile(logId) { -// return `/build/download_file.html?logId=${logId}` -// } - -// /** -// * 回滚(重新发布) -// * @param {*} logId -// * @returns -// */ -// export function rollback(logId) { -// return axios({ -// url: '/build/reRelease.json', -// method: 'post', -// data: {logId} -// }) -// } - -// /** -// * 删除构建历史记录 -// * @param {*} logId -// */ -// export function deleteBuildHistory(logId) { -// return axios({ -// url: '/build/delete_log.json', -// method: 'post', -// data: {logId} -// }) -// } - -// export const releaseMethodMap = { -// 0: '不发布', -// 1: '节点分发', -// 2: '项目', -// 3: 'SSH' -// } \ No newline at end of file diff --git a/web-vue/src/api/command.ts b/web-vue/src/api/command.ts new file mode 100644 index 0000000000..b8bd29547f --- /dev/null +++ b/web-vue/src/api/command.ts @@ -0,0 +1,132 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios, { loadRouterBase } from './config' + +// 命令列表 +export function getCommandList(params) { + return axios({ + url: '/node/ssh_command/list', + method: 'post', + data: params + }) +} + +// 编辑命令 +export function editCommand(params) { + return axios({ + url: '/node/ssh_command/edit', + method: 'post', + headers: { + 'Content-Type': 'application/json' + }, + data: params + }) +} + +// 删除命令 +export function deleteCommand(id) { + return axios({ + url: '/node/ssh_command/del', + method: 'post', + data: { id } + }) +} + +// 删除命令 +export function executeBatch(param) { + return axios({ + url: '/node/ssh_command/batch', + method: 'post', + data: param + }) +} + +// 命令日志列表 +export function getCommandLogList(params) { + return axios({ + url: '/node/ssh_command_log/list', + method: 'post', + data: params + }) +} + +// 命令日志批次列表 +export function getCommandLogBarchList(params) { + return axios({ + url: '/node/ssh_command_log/batch_list', + method: 'get', + params: params + }) +} + +// 删除命令执行记录 +export function deleteCommandLog(id) { + return axios({ + url: '/node/ssh_command_log/del', + method: 'post', + data: { id } + }) +} + +// 命令日志信息 +export function getCommandLogInfo(params) { + return axios({ + url: '/node/ssh_command_log/log', + method: 'post', + data: params, + headers: { + loading: 'no' + } + }) +} + +/** + * 下载日志 + * @param {*} logId + */ +export function downloadLog(logId) { + return loadRouterBase('/node/ssh_command_log/download_log', { + logId: logId + }) +} + +export function syncToWorkspace(params) { + return axios({ + url: '/node/ssh_command/sync-to-workspace', + method: 'get', + params: params + }) +} + +/** + * 获取触发器地址 + * @param {*} id + */ +export function getTriggerUrl(data) { + return axios({ + url: '/node/ssh_command/trigger-url', + method: 'post', + data: data + }) +} + +export const statusMap = { + 0: t('i18n_46e3867956'), + 1: t('i18n_ec219f99ee'), + 2: t('i18n_05f6e923af'), + 3: t('i18n_e2f942759e') +} + +export const triggerExecTypeMap = { + 0: t('i18n_2a3e7f5c38'), + 1: t('i18n_3aed2c11e9'), + 2: t('i18n_4696724ed3') +} diff --git a/web-vue/src/api/common.ts b/web-vue/src/api/common.ts new file mode 100644 index 0000000000..350a2b1b09 --- /dev/null +++ b/web-vue/src/api/common.ts @@ -0,0 +1,22 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +/** + * 生成分片上传 id + */ +export function generateShardingId() { + return axios({ + url: '/generate-sharding-id', + method: 'get', + data: {} + }) +} diff --git a/web-vue/src/api/config.js b/web-vue/src/api/config.js deleted file mode 100644 index 8963ff077b..0000000000 --- a/web-vue/src/api/config.js +++ /dev/null @@ -1,197 +0,0 @@ -import Vue from "vue"; -import axios from "axios"; -import Qs from "qs"; -import store from "../store"; -import router from "../router"; -import { NO_NOTIFY_KEY, NO_LOADING_KEY, TOKEN_HEADER_KEY, CACHE_WORKSPACE_ID } from "@/utils/const"; -import { refreshToken } from "./user"; - -import { notification } from "ant-design-vue"; - -// axios.defaults.baseURL = 'http://localhost:2122' -let $global_loading; -let startTime; -// -const delTimeout = 20 * 1000; -// -const apiTimeout = window.apiTimeout === "" ? delTimeout : window.apiTimeout; - -const request = axios.create({ - timeout: apiTimeout || delTimeout, - headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", - }, - responseType: "json", -}); - -let pro = process.env.NODE_ENV === "pro"; - -// 请求拦截器 -request.interceptors.request.use( - (config) => { - // 如果 headers 里面配置了 loading: no 就不用 loading - if (!config.headers[NO_LOADING_KEY]) { - $global_loading = Vue.prototype.$loading.service({ - lock: true, - text: "加载数据中,请稍候...", - spinner: "el-icon-loading", - background: "rgba(0, 0, 0, 0.7)", - }); - startTime = new Date().getTime(); - } - // 处理数据 - if (window.routerBase) { - // 防止 url 出现 // - config.url = (window.routerBase + config.url).replace(new RegExp("//", "gm"), "/"); - } - if (config.headers["Content-Type"].indexOf("application/x-www-form-urlencoded") !== -1) { - config.data = Qs.stringify(config.data); - } - let wid = router.app.$route.query.wid; - if (!wid) { - wid = getHashVars().wid; - } - config.headers[TOKEN_HEADER_KEY] = store.getters.getToken; - config.headers[CACHE_WORKSPACE_ID] = wid ? wid : store.getters.getWorkspaceId; - return config; - }, - (error) => { - return Promise.reject(error); - } -); - -function getHashVars() { - var vars = {}; - location.hash.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) { - vars[key] = value; - }); - return vars; -} - -// 响应拦截器 -request.interceptors.response.use( - (response) => { - // 如果 headers 里面配置了 loading: no 就不用 loading - if (!response.config?.headers[NO_LOADING_KEY]) { - const endTime = new Date().getTime(); - if (endTime - startTime < 1000) { - setTimeout(() => { - $global_loading.close(); - }, 300); - } else { - $global_loading.close(); - } - } - // 如果 responseType 是 blob 表示是下载文件 - if (response.request.responseType === "blob") { - return response.data; - } - // 判断返回值,权限等... - const res = response.data; - - // 先判断 jwt token 状态 - if (res.code === 800 || res.code === 801) { - return checkJWTToken(res, response); - } - - // 禁止访问 - if (res.code === 999) { - notification.error({ - message: "禁止访问", - description: "禁止访问,当前IP限制访问", - }); - router.push("/system/ipAccess"); - return false; - } - - // 其他情况 - if (res.code !== 200) { - // 如果 headers 里面配置了 tip: no 就不用弹出提示信息 - if (!response.config.headers[NO_NOTIFY_KEY]) { - notification.error({ - message: "提示信息 " + (pro ? "" : response.config.url), - description: res.msg, - }); - console.error(response.config.url, res); - } - } - - return res; - }, - (error) => { - if (!error.response) { - // 网络异常 - $global_loading.close(); - notification.error({ - message: "Network Error", - description: "网络开了小差!请重试...:" + error, - }); - return Promise.reject(error); - } - // 如果 headers 里面配置了 loading: no 就不用 loading - if (!error.response.config.headers[NO_LOADING_KEY]) { - $global_loading.close(); - } - // 如果 headers 里面配置了 tip: no 就不用弹出提示信息 - if (!error.response.config.headers[NO_NOTIFY_KEY]) { - const { status, statusText, data } = error.response; - if (!status) { - notification.error({ - message: "Network Error", - description: "网络开了小差!请重试...:" + error, - }); - } else { - notification.error({ - message: "状态码错误 " + status, - description: (statusText || "") + (data || ""), - }); - } - } - return Promise.reject(error); - } -); - -// 判断 jwt token 状态 -function checkJWTToken(res, response) { - // 如果是登录信息失效 - if (res.code === 800) { - notification.warn({ - message: "提示信息 " + (pro ? "" : response.config.url), - description: res.msg, - }); - console.error(response.config.url, res); - store.dispatch("logOut").then(() => { - router.push("/login"); - location.reload(); - }); - return false; - } - // 如果 jwt token 还可以续签 - if (res.code === 801) { - notification.close(); - notification.info({ - message: "登录信息过期,尝试自动续签...", - description: "如果不需要自动续签,请修改配置文件。该续签将不会影响页面。", - }); - // 续签且重试请求 - return redoRequest(response.config); - } -} - -// 刷新 jwt token 并且重试上次请求 -function redoRequest(config) { - return new Promise((resolve) => { - Promise.resolve(refreshToken()).then((result) => { - if (result.code === 200) { - // 调用 store action 存储当前登录的用户名和 token - store.dispatch("login", result.data); - resolve(); - } - }); - }).then(() => { - // 重试原来的请求 - return request(config); - }); -} - -export default request; diff --git a/web-vue/src/api/config.ts b/web-vue/src/api/config.ts new file mode 100644 index 0000000000..54aea827ac --- /dev/null +++ b/web-vue/src/api/config.ts @@ -0,0 +1,353 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import { IResponse } from '@/interface/request' +import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios' +import { NO_NOTIFY_KEY, TOKEN_HEADER_KEY, CACHE_WORKSPACE_ID } from '@/utils/const' +import { refreshToken } from './user/user' +import { useAppStore } from '@/stores/app' +import { useUserStore } from '@/stores/user' +import { GlobalWindow } from '@/interface/common' +import Qs from 'qs' +import router from '../router' +import { base64Encode } from '@/utils/check-type' + +const delTimeout: number = 20 * 1000 +const jpomWindow_ = window as unknown as GlobalWindow +const apiTimeout: number = Number(jpomWindow_.apiTimeout === '' ? delTimeout : jpomWindow_.apiTimeout) +// debug routerBase +const routerBase: string = jpomWindow_.routerBase === '' ? '' : jpomWindow_.routerBase + +const pro: boolean = process.env.NODE_ENV === 'production' + +const baseURL = import.meta.env.JPOM_BASE_API_URL + +const parseTransportEncryption = () => { + if (jpomWindow_.transportEncryption === '') { + return 'NONE' + } + return jpomWindow_.transportEncryption || 'NONE' +} +const transportEncryption = parseTransportEncryption() + +// 创建实例 +const instance: AxiosInstance = axios.create({ + baseURL: baseURL, + + timeout: apiTimeout || delTimeout, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + responseType: 'json' +}) + +// 续签状态 +let isRefreshing = false + +const obj2base64 = (obj: any) => { + if (obj instanceof Object && obj.constructor === Object) { + const keys = Object.keys(obj) + const newData: any = {} + for (const key of keys) { + const item = obj[key] + if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { + newData[base64Encode(String(key))] = base64Encode(String(item)) + } + } + return newData + } else if (obj instanceof FormData) { + const newFormData: any = new FormData() + for (const key of (obj as any).keys()) { + const item = obj.get(key) + if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { + newFormData.append(base64Encode(String(key)), base64Encode(String(item))) + } else { + newFormData.append(base64Encode(String(key)), item) + } + } + return newFormData + } + if (Array.isArray(obj)) { + return obj.map((item: any) => { + if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { + item = base64Encode(String(item)) + } + return item + }) + } + return obj +} + +// 请求拦截 +instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { + const appStore = useAppStore() + const userStore = useUserStore() + + const { headers } = config + headers[TOKEN_HEADER_KEY] = userStore.getToken() + headers[CACHE_WORKSPACE_ID] = appStore.getWorkspaceId() + const useGuideStore = guideStore() + headers['Accept-Language'] = useGuideStore.getLocale() + + if (routerBase) { + // 防止 url 出现 // + config.url = (routerBase + config.url).replace(new RegExp('//', 'gm'), '/') + } + if (transportEncryption == 'BASE64') { + if (headers['Content-Type'] === 'application/json') { + if (config.data) { + config.data = base64Encode(JSON.stringify(config.data)) + } + } else { + if (config.data) { + config.data = obj2base64(config.data) + } + if (config.params) { + config.params = obj2base64(config.params) + } + } + } + + return config +}) + +// 响应拦截 +instance.interceptors.response.use( + function (response) { + // 2xx 范围内的状态码都会触发该函数。 + return response + }, + function (error: AxiosError) { + // 无响应体 + if (!error.response) { + $notification.error({ + key: 'network-error-no-response', + message: 'Network Error No response', + description: t('i18n_9ee0deb3c8') + error + }) + } else if (!error.response.config.headers[NO_NOTIFY_KEY]) { + const { status, statusText, data } = error.response + if (!status) { + $notification.error({ + key: 'network-error-no-response', + message: 'Network Error', + description: t('i18n_9ee0deb3c8') + error + }) + } else { + $notification.error({ + key: 'network-error-status-' + status, + message: t('i18n_cc9a708364') + status, + description: (statusText || '') + (data || '') + }) + } + } + return Promise.reject(error) + } +) + +/** + * 请求封装 + * @param url 接口地址 + * @param config AxiosRequestConfig + * @returns IResponse + */ +async function request(url: string, config?: AxiosRequestConfig): Promise> +// eslint-disable-next-line no-redeclare +async function request(config: AxiosRequestConfig): Promise> +// eslint-disable-next-line no-redeclare +async function request(arg: string | AxiosRequestConfig, config?: AxiosRequestConfig): Promise> { + config = config || {} + const options = + typeof arg === 'string' + ? { + url: arg, + ...config + } + : arg + const response = await instance.request>(options) + const { data } = response + // 登录失效 + if (data.code === 800) { + toLogin(data, response) + return Promise.reject(data) + } + + // 需要续签 + if (data.code === 801) { + const data2 = await handleRefreshTokenAndRetryQueue(response.config) + return data2 as any + } + + // 账号禁用 + if (data.code === 802) { + toLogin(data, response) + return Promise.reject() + } + + // 禁止访问 + if (data.code === 999) { + $notification.error({ + key: 'prohibit-access', + message: t('i18n_bb9ef827bf'), + description: t('i18n_f05e3ec44d') + }) + window.location.href = jpomWindow_.routerBase + '/prohibit-access' + return Promise.reject(data) + } + + // 其他情况 + if (data.code !== 200) { + // 如果 headers 里面配置了 tip: no 就不用弹出提示信息 + if (!response.config.headers[NO_NOTIFY_KEY]) { + $notification.error({ + message: t('i18n_1a8f90122f') + (pro ? '' : response.config.url), + description: data.msg + }) + console.error(response.config.url, data) + } + } + return data +} + +export default request + +// 异步续签并处理队列 +async function handleRefreshTokenAndRetryQueue(config: InternalAxiosRequestConfig) { + if (!isRefreshing) { + isRefreshing = true + return new Promise(async (resolve, reject) => { + try { + $notification.info({ + key: 'login-timeout', + message: t('i18n_72d46ec2cf'), + description: t('i18n_f652d8cca7') + }) + // 执行续签操作 + const result = await refreshToken() + if (result.code === 200) { + // 更新用户登录状态或token + const userStore = useUserStore() + await userStore.login(result.data) + $notification.success({ + key: 'login-timeout', + message: t('i18n_72d46ec2cf'), + description: t('i18n_a7c8eea801') + }) + // 更新token到config中,以便于后续请求使用新token + config.headers[TOKEN_HEADER_KEY] = userStore.getToken() + const result2 = await request(config) + resolve(result2) + isRefreshing = false + } else { + toLoginOnly(t('i18n_72d46ec2cf'), t('i18n_edc1185b8e') + result.msg + t('i18n_d3fb6a7c83')) + return + } + } catch (error) { + //reject('Failed to refresh token:' + error) + toLoginOnly(t('i18n_72d46ec2cf'), t('i18n_edc1185b8e') + error + t('i18n_d3fb6a7c83')) + } finally { + } + }) + } else { + return new Promise(async (resolve) => { + const interval = setInterval(async () => { + if (!isRefreshing) { + clearInterval(interval) + const userStore = useUserStore() + config.headers[TOKEN_HEADER_KEY] = userStore.getToken() + const result2 = await request(config) + resolve(result2) + } else { + // + } + }, 100) // 检查间隔设置为100毫秒 + }) + } +} + +function toLogin(res: IResponse, response: AxiosResponse>) { + return toLogin2(t('i18n_1a8f90122f') + (pro ? '' : response.config.url), res.msg) +} + +function toLogin2(message: any, description: any) { + $notification.warn({ + message: message, + description: description, + 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)) + } + const pageUrl = router.resolve({ + path: '/login', + query: params + }) + + setTimeout(() => { + ;(location.href as any) = pageUrl.href + }, 2000) + }) + return false +} + +function toLoginOnly(message: any, description: any) { + $notification.warn({ + message: message, + description: description, + key: 'to-login' + }) + const userStore = useUserStore() + + userStore.logOutOnly().then(() => { + 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() + let queryStr = '' + Object.keys(paramsObj).forEach((key, i) => { + queryStr += `${i === 0 ? '' : '&'}${key}=${paramsObj[key]}` + }) + return `${((baseURL + routerBase || '') + url).replace(new RegExp('//', 'gm'), '/')}?${queryStr}` +} + +/** + * 获取 socket 地址 + * @param {String} url 二级地址 + * @param {String} parameter 参数 + * @returns + */ +export function getWebSocketUrl(url: string, parameter: any) { + const protocol: string = location.protocol === 'https:' ? 'wss://' : 'ws://' + const fullUrl: string = (baseURL + routerBase + url).replace(new RegExp('//', 'gm'), '/') + const useGuideStore = guideStore() + parameter += `&lang=${useGuideStore.getLocale()}` + return `${protocol}${location.host}${fullUrl}?${parameter}` +} diff --git a/web-vue/src/api/dispatch.js b/web-vue/src/api/dispatch.js deleted file mode 100644 index 8eebe794c8..0000000000 --- a/web-vue/src/api/dispatch.js +++ /dev/null @@ -1,186 +0,0 @@ -import axios from "./config"; - -// 分发列表 -export function getDishPatchList(data) { - return axios({ - url: "/outgiving/dispatch-list", - method: "post", - data: data, - }); -} - -// 分发列表 -export function getDishPatchListAll() { - return axios({ - url: "/outgiving/dispatch-list-all", - method: "get", - }); -} - -// 分发节点项目状态 -export function getDispatchProject(id) { - return axios({ - url: "/outgiving/getItemData.json", - method: "post", - data: { id }, - }); -} - -// reqId -export function getReqId() { - return axios({ - url: "/outgiving/get-reqId", - method: "get", - }); -} - -/** - * 编辑分发 - * @param { - * id: 分发 ID - * name: 分发名称 - * reqId: 请求 ID - * type: 类型 add | edit - * node_xxx: xxx 表示节点 ID - * afterOpt: 发布后操作 - * } params - */ -export function editDispatch(params) { - return axios({ - url: "/outgiving/save", - method: "post", - data: params, - }); -} - -/** - * 编辑分发项目 - * @param { - * id: 分发 ID - * name: 分发名称 - * reqId: 请求 ID - * type: 类型 add | edit - * afterOpt: 发布后操作 - * runMode: 运行方式 - * mainClass: 启动类 - * javaExtDirsCp: 目录地址 - * whitelistDirectory: 白名单地址 - * lib: lib - * add_xxx: xxx 表示添加的节点信息 - * xxx_token: xxx 节点的 webhook 地址 - * xxx_jvm: jvm 参数 - * xxx_args: args 参数 - * xxx_javaCopyIds: xxx 节点副本 ID (如果有副本,还需要加上 xxx_jvm_${副本ID} | xxx_args_${副本ID} 参数) - * } params - */ -export function editDispatchProject(params) { - return axios({ - url: "/outgiving/save_project", - method: "post", - data: params, - }); -} - -/** - * 上传分发文件 - * @param { - * id: 分发 ID - * afterOpt: 发布后操作 - * clearOld: 是否清除 - * file: 文件 multipart/form-data - * } formData - */ -export function uploadDispatchFile(formData) { - return axios({ - url: "/outgiving/upload", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - method: "post", - // 0 表示无超时时间 - timeout: 0, - data: formData, - }); -} - -/** - * 远程下载文件分发 - * @param { - * id: 节点 ID - * clearOld: 是否清空 - * afterOpt: 分发后的操作 - * url: 远程URL - * unzip:是否为压缩包 - * } params - */ -export function remoteDownload(params) { - return axios({ - url: "/outgiving/remote_download", - method: "post", - data: params, - }); -} - -/** - * 删除分发 - * @param {*} id 分发 ID - */ -export function deleteDisPatch(id) { - return axios({ - url: "/outgiving/del.json", - method: "post", - data: { id }, - }); -} - -/** - * 分发日志 - * @param { - * nodeId: 节点 ID - * outGivingId: 分发 ID - * status: 分发状态 - * time: 时间区间 2021-01-04 00:00:00 ~ 2021-01-12 00:00:00 - * } params - */ -export function getDishPatchLogList(params) { - return axios({ - url: "/outgiving/log_list_data.json", - method: "post", - data: params, - }); -} - -// 获取分发白名单数据 -export function getDispatchWhiteList() { - return axios({ - url: "/outgiving/white-list", - method: "post", - }); -} - -/** - * 编辑分发白名单 - * @param {*} params - */ -export function editDispatchWhiteList(params) { - return axios({ - url: "/outgiving/whitelistDirectory_submit", - method: "post", - data: params, - }); -} - -export const afterOptList = [ - { title: "不做任何操作", value: 0 }, - { title: "并发重启", value: 1 }, - { title: "完整顺序重启(有重启失败将结束本次)", value: 2 }, - { title: "顺序重启(有重启失败将继续)", value: 3 }, -]; - -export const dispatchStatusMap = { - 0: "未分发", - 1: "分发中", - 2: "分发成功", - 3: "分发失败", - 4: "取消分发", -}; diff --git a/web-vue/src/api/dispatch.ts b/web-vue/src/api/dispatch.ts new file mode 100644 index 0000000000..9c3daa5875 --- /dev/null +++ b/web-vue/src/api/dispatch.ts @@ -0,0 +1,315 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' + +// 分发列表 +export function getDishPatchList(data, loading) { + return axios({ + url: '/outgiving/dispatch-list', + method: 'post', + data: data, + headers: { + loading: loading === false ? 'no' : '' + } + }) +} + +// 分发列表 +export function getDishPatchListAll() { + return axios({ + url: '/outgiving/dispatch-list-all', + method: 'get' + }) +} + +// 分发节点项目状态 +export function getDispatchProject(id, loading) { + return axios({ + url: '/outgiving/getItemData.json', + method: 'post', + data: { id }, + timeout: 0, + headers: { + loading: loading === false ? 'no' : '' + } + }) +} + +// reqId +export function getReqId() { + return axios({ + url: '/outgiving/get-reqId', + method: 'get' + }) +} + +/** + * 编辑分发 + * @param { + * id: 分发 ID + * name: 分发名称 + * reqId: 请求 ID + * type: 类型 add | edit + * node_xxx: xxx 表示节点 ID + * afterOpt: 发布后操作 + * } params + */ +export function editDispatch(params) { + return axios({ + url: '/outgiving/save', + method: 'post', + data: params + }) +} + +/** + * 编辑分发项目 + * @param { + * id: 分发 ID + * name: 分发名称 + * reqId: 请求 ID + * type: 类型 add | edit + * afterOpt: 发布后操作 + * runMode: 运行方式 + * mainClass: 启动类 + * javaExtDirsCp: 目录地址 + * whitelistDirectory: 授权地址 + * lib: lib + * add_xxx: xxx 表示新增的节点信息 + + * } params + */ +export function editDispatchProject(params) { + return axios({ + url: '/outgiving/save_project', + method: 'post', + data: params + }) +} + +/** + * 上传分发文件 + * @param { + * id: 分发 ID + * afterOpt: 发布后操作 + * clearOld: 是否清除 + * file: 文件 multipart/form-data + * } formData + */ +export function uploadDispatchFile(formData) { + return axios({ + url: '/outgiving/upload-sharding', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8', + loading: 'no' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} + +export function uploadDispatchFileMerge(params) { + return axios({ + url: '/outgiving/upload-sharding-merge', + method: 'post', + data: params, + // 0 表示无超时时间 + timeout: 0 + }) +} + +/** + * 远程下载文件分发 + * @param { + * id: 节点 ID + * clearOld: 是否清空 + * afterOpt: 分发后的操作 + * url: 远程URL + * unzip:是否为压缩包 + * } params + */ +export function remoteDownload(params) { + return axios({ + url: '/outgiving/remote_download', + method: 'post', + data: 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 + */ +export function releaseDelDisPatch(id) { + return axios({ + url: '/outgiving/release_del.json', + method: 'post', + data: { id } + }) +} + +/** + * 删除分发 + * @param {*} id 分发 ID + */ +export function delDisPatchProject(data) { + return axios({ + url: '/outgiving/delete_project', + method: 'post', + data: data + }) +} + +/** + * 解绑分发 + * @param {*} id 分发 ID + */ +export function unbindOutgiving(id) { + return axios({ + url: '/outgiving/unbind.json', + method: 'get', + params: { id } + }) +} + +/** + * 分发日志 + * @param { + * nodeId: 节点 ID + * outGivingId: 分发 ID + * status: 分发状态 + * time: 时间区间 2021-01-04 00:00:00 ~ 2021-01-12 00:00:00 + * } params + */ +export function getDishPatchLogList(params) { + return axios({ + url: '/outgiving/log_list_data.json', + method: 'post', + data: params + }) +} + +// 获取分发授权数据 +export function getDispatchWhiteList(params) { + return axios({ + url: '/outgiving/white-list', + method: 'post', + data: params + }) +} + +/** + * 编辑分发授权 + * @param {*} params + */ +export function editDispatchWhiteList(params) { + return axios({ + url: '/outgiving/whitelistDirectory_submit', + method: 'post', + data: params + }) +} + +/** + * 取消分发 + * @param {*} id 分发 ID + */ +export function cancelOutgiving(data) { + return axios({ + url: '/outgiving/cancel', + method: 'post', + data + }) +} + +export function removeProject(params) { + return axios({ + url: '/outgiving/remove-project', + method: 'get', + params: params + }) +} + +export function saveDispatchProjectConfig(data) { + return axios({ + url: '/outgiving/config-project', + method: 'post', + data, + headers: { + 'Content-Type': 'application/json' + } + }) +} + +export const afterOptList = [ + { title: t('i18n_a2ebd000e4'), value: 0 }, + { title: t('i18n_82915930eb'), value: 1 }, + { title: t('i18n_0e1ecdae4a'), value: 2 }, + { title: t('i18n_8887e94cb7'), value: 3 } +] + +export const afterOptListSimple = [ + { title: t('i18n_a2ebd000e4'), value: 0 }, + { title: t('i18n_913ef5d129'), value: 1 } +] + +export const dispatchStatusMap = { + 0: t('i18n_29efa328e5'), + 1: t('i18n_5b3ffc2910'), + 2: t('i18n_7e300e89b1'), + 3: t('i18n_2a049f4f5b'), + 4: t('i18n_036c0dc2aa'), + 5: t('i18n_339097ba2e'), + 6: t('i18n_7bf62f7284') +} + +export const statusMap = { + 0: t('i18n_29efa328e5'), + 1: t('i18n_5b3ffc2910'), + 2: t('i18n_3ea6c5e8ec'), + 3: t('i18n_30e855a053'), + 4: t('i18n_2a049f4f5b') +} + +export const dispatchMode = { + upload: t('i18n_bd7c8c96bc'), + download: t('i18n_bd7043cae3'), + 'build-trigger': t('i18n_74d5f61b9f'), + 'use-build': t('i18n_c1af35d001'), + 'static-file-storage': t('i18n_28f6e7a67b'), + 'file-storage': t('i18n_26183c99bf') +} diff --git a/web-vue/src/api/docker-api.ts b/web-vue/src/api/docker-api.ts new file mode 100644 index 0000000000..8b196445fe --- /dev/null +++ b/web-vue/src/api/docker-api.ts @@ -0,0 +1,398 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' +import { loadRouterBase } from './config' + +/** + * 容器列表 + * @param {JSON} params + */ +export function dockerList(params) { + return axios({ + url: '/docker/list', + method: 'post', + data: params + }) +} + +/** + * 获取支持的所有 api 版本 + * @returns json + */ +export function apiVersions() { + return axios({ + url: '/docker/api-versions', + method: 'get', + data: {} + }) +} + +export function editDocker(data) { + return axios({ + url: '/docker/edit', + method: 'post', + data: data + }) +} + +/** + * 删除 docker + * @param { + * id: docker ID + * } params + */ +export function deleteDcoker(params) { + return axios({ + url: '/docker/del', + method: 'get', + params + }) +} + +/** + * 容器中的列表 + * @param {JSON} params + */ +export function dockerContainerList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/list', + method: 'post', + data: params, + headers: { + loading: 'no' + } + }) +} + +/** + * 容器中的列表 + * @param {JSON} params + */ +export function dockerContainerListCompose(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/list-compose', + method: 'post', + data: params, + headers: { + loading: 'no' + } + }) +} + +/** + * 查看 docker info + * @param {JSON} params + */ +export function dockerInfo(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/info', + method: 'get', + params: params + }) +} + +/** + * 修剪 docker + * @param {JSON} params + */ +export function dockerPrune(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/prune', + method: 'post', + data: params, + timeout: 0 + }) +} + +/** + * 删除容器 + * @param {JSON} params + */ +export function dockerContainerRemove(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/remove', + method: 'get', + params: params + }) +} + +/** + * 重启容器 + * @param {JSON} params + */ +export function dockerContainerRestart(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/restart', + method: 'get', + params: params + }) +} + +/** + * 启动容器 + * @param {JSON} params + */ +export function dockerContainerStart(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/start', + method: 'get', + params: params + }) +} + +/** + * 停止容器 + * @param {JSON} params + */ +export function dockerContainerStop(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/stop', + method: 'get', + params: params + }) +} + +/** + * 获取容器统计信息 + * @param {JSON} params + */ +export function dockerContainerStats(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/stats', + method: 'get', + params: params, + headers: { + // tip: "no", + loading: 'no' + } + }) +} + +/** + * 获取容器信息 + * @param {JSON} params + */ +export function dockerInspectContainer(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/inspect-container', + method: 'get', + params: params + }) +} + +/** + * 更新容器 + * @param {JSON} params + * @returns + */ +export function dockerUpdateContainer(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/update-container', + method: 'post', + headers: { + 'Content-Type': 'application/json' + }, + data: params + }) +} + +export function dockerContainerDownloaLog(urlPrefix, id) { + return loadRouterBase(urlPrefix + '/container/download-log', { + id: id + }) +} + +/** + * 容器中的镜像列表 + * @param {JSON} params + */ +export function dockerImagesList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/list', + method: 'post', + data: params + }) +} + +/** + * 删除镜像 + * @param {JSON} params + */ +export function dockerImageRemove(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/remove', + method: 'get', + params: params + }) +} + +/** + * 批量删除镜像 + * @param {JSON} params + */ +export function dockerImageBatchRemove(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/batchRemove', + method: 'get', + params: params + }) +} + +/** + * inspect 镜像 + * @param {JSON} params + */ +export function dockerImageInspect(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/inspect', + method: 'get', + params: params + }) +} + +/** + * 镜像 创建容器 + * @param {JSON} params + */ +export function dockerImageCreateContainer(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/create-container', + method: 'post', + headers: { + 'Content-Type': 'application/json' + }, + data: params + }) +} + +/** + * 拉取镜像 + * @param {JSON} params + */ +export function dockerImagePullImage(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/pull-image', + method: 'get', + params: 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 + */ +export function dockerImagePullImageLog(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/pull-image-log', + method: 'get', + params: params, + headers: { + // tip: "no", + loading: 'no' + } + }) +} + +/** + * 卷 + * @param {JSON} params + */ +export function dockerVolumesList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/volumes/list', + method: 'post', + data: params + }) +} + +/** + * 删除卷 + * @param {JSON} params + */ +export function dockerVolumesRemove(urlPrefix, params) { + return axios({ + url: urlPrefix + '/volumes/remove', + method: 'get', + params: params + }) +} + +/** + * 网络 + * @param {JSON} params + */ +export function dockerNetworksList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/networks/list', + method: 'post', + data: params + }) +} + +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.ts b/web-vue/src/api/docker-swarm.ts new file mode 100644 index 0000000000..39e5663001 --- /dev/null +++ b/web-vue/src/api/docker-swarm.ts @@ -0,0 +1,221 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' +import { loadRouterBase } from './config' +/** + * 容器列表 + * @param {JSON} params + */ +export function dockerSwarmList(params) { + return axios({ + url: '/docker/swarm/list', + method: 'post', + data: params + }) +} + +export function dockerSwarmListAll(params) { + return axios({ + url: '/docker/swarm/list-all', + method: 'get', + params: params + }) +} + +export function editDockerSwarm(data) { + return axios({ + url: '/docker/swarm/edit', + method: 'post', + data: data + }) +} + +/** + * 删除 集群 + * @param { + * id: docker ID + * } params + */ +export function delSwarm(params) { + return axios({ + url: '/docker/swarm/del', + method: 'get', + params + }) +} + +/** + * 容器集群节点列表 + * @param {JSON} params + */ +export function dockerSwarmNodeList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm/node-list', + method: 'post', + data: params, + headers: { + loading: 'no' + } + }) +} + +/** + * 容器集群节点修改 + * @param {JSON} params + */ +export function dockerSwarmNodeUpdate(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm/update', + method: 'post', + data: params + }) +} + +/** + * 容器集群服务列表 + * @param {JSON} params + */ +export function dockerSwarmServicesList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm-service/list', + method: 'post', + data: params, + headers: { + loading: 'no' + } + }) +} + +/** + * 容器集群服务任务列表 + * @param {JSON} params + */ +export function dockerSwarmServicesTaskList(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm-service/task-list', + method: 'post', + data: params, + headers: { + loading: 'no' + } + }) +} + +/** + * 容器集群节点 删除服务 + * @param {JSON} params + */ +export function dockerSwarmServicesDel(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm-service/del', + method: 'get', + params: params + }) +} + +/** + * 容器集群节点 删除服务 + * @param {JSON} params + */ +export function dockerSwarmServicesEdit(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm-service/edit', + method: 'post', + data: params, + headers: { + 'Content-Type': 'application/json' + } + }) +} + +/** + * 开始拉取服务日志 + * @param {JSON} params + */ +export function dockerSwarmServicesStartLog(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm-service/start-log', + method: 'get', + params: params + }) +} + +/** + * 拉取服务日志 + * @param {JSON} params + */ +export function dockerSwarmServicesPullLog(urlPrefix, params) { + return axios({ + url: urlPrefix + '/docker/swarm-service/pull-log', + method: 'get', + params: params, + headers: { + loading: 'no' + } + }) +} + +export function dockerSwarmServicesDownloaLog(urlPrefix, id) { + return loadRouterBase(urlPrefix + '/docker/swarm-service/download-log', { + id: id + }) +} + +/** + * + + + + 新建状态 + 已分配 + + 待处理 + + 已分配 + + 处理中 + + 准备中 + 准备 + + 开始执行任务 + + 执行任务中 + + 执行成功 + + 停止 + + 执行失败 + + 拒绝 + + 移除 + + 已失联 + */ +export const TASK_STATE = { + NEW: t('i18n_40da3fb58b'), + // ALLOCATED: "已分配", + PENDING: t('i18n_047109def4'), + ASSIGNED: t('i18n_fbfa6c18bf'), + ACCEPTED: t('i18n_5d459d550a'), + PREPARING: t('i18n_f76540a92e'), + READY: t('i18n_424a2ad8f7'), + STARTING: t('i18n_a34c24719b'), + RUNNING: t('i18n_e9e9373c6f'), + COMPLETE: t('i18n_f56c1d014e'), + SHUTDOWN: t('i18n_095e938e2a'), + FAILED: t('i18n_1c83d79715'), + REJECTED: t('i18n_7173f80900'), + REMOVE: t('i18n_86048b4fea'), + ORPHANED: t('i18n_788a3afc90') +} diff --git a/web-vue/src/api/ext-config.ts b/web-vue/src/api/ext-config.ts new file mode 100644 index 0000000000..6728a203ce --- /dev/null +++ b/web-vue/src/api/ext-config.ts @@ -0,0 +1,54 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +/** + * 获取基础运行镜像列表 + * @returns {*} + */ +export function listExtConf() { + return axios({ + url: '/system/ext-conf/list', + method: 'get' + }) +} + +export function getItem(params) { + return axios({ + url: '/system/ext-conf/get-item', + method: 'get', + params: params + }) +} + +export function getDefaultItem(params) { + return axios({ + url: '/system/ext-conf/get-default-item', + method: 'get', + params: params + }) +} + +export function saveItem(params) { + return axios({ + url: '/system/ext-conf/save-item', + method: 'post', + data: params + }) +} + +export function addItem(params) { + return axios({ + url: '/system/ext-conf/add-item', + method: 'get', + params: params + }) +} diff --git a/web-vue/src/api/external.ts b/web-vue/src/api/external.ts new file mode 100644 index 0000000000..d1bbc03ee4 --- /dev/null +++ b/web-vue/src/api/external.ts @@ -0,0 +1,34 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from 'axios' + +const external = axios.create({ + timeout: 5 * 1000, + headers: {} +}) + +// 响应拦截器 +external.interceptors.response.use( + async (response) => { + return response.data + }, + (error) => { + return Promise.reject(error) + } +) + +export function executionRequest(url: any, param: any) { + return external({ + url: url, + method: 'get', + params: param + }) +} diff --git a/web-vue/src/api/file-manager/file-storage.ts b/web-vue/src/api/file-manager/file-storage.ts new file mode 100644 index 0000000000..6176bbf875 --- /dev/null +++ b/web-vue/src/api/file-manager/file-storage.ts @@ -0,0 +1,101 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from '@/api/config' + +// 文件列表 +export function fileStorageList(params) { + return axios({ + url: '/file-storage/list', + method: 'post', + data: params + }) +} + +export function uploadFile(formData) { + return axios({ + url: '/file-storage/upload-sharding', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8', + loading: 'no' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} + +export function uploadFileMerge(params) { + return axios({ + url: '/file-storage/upload-sharding-merge', + method: 'post', + data: params, + // 0 表示无超时时间 + timeout: 0 + }) +} + +// 修改文件 +export function fileEdit(params) { + return axios({ + url: '/file-storage/edit', + method: 'post', + data: params + }) +} + +// 下载远程文件 +export function remoteDownload(params) { + return axios({ + url: '/file-storage/remote-download', + method: 'post', + data: params + }) +} + +// 判断文件是否存在 +export function hasFile(params) { + return axios({ + url: '/file-storage/has-file', + method: 'get', + params: params + }) +} + +export function delFile(params) { + return axios({ + url: '/file-storage/del', + method: 'get', + params: params + }) +} + +// 下载 url +export function triggerUrl(params) { + return axios({ + url: '/file-storage/trigger-url', + method: 'get', + params: params + }) +} + +export const sourceMap = { + 0: t('i18n_d5a73b0c7f'), + 1: t('i18n_fcba60e773'), + 2: t('i18n_f26ef91424'), + 3: t('i18n_d40b511510') +} + +export const statusMap = { + 0: t('i18n_2d455ce5cd'), + 1: t('i18n_50940ed76f'), + 2: t('i18n_af924a1a14') +} diff --git a/web-vue/src/api/file-manager/release-task-log.ts b/web-vue/src/api/file-manager/release-task-log.ts new file mode 100644 index 0000000000..442d69452d --- /dev/null +++ b/web-vue/src/api/file-manager/release-task-log.ts @@ -0,0 +1,89 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from '@/api/config' + +// 任务列表 +export function fileReleaseTaskLog(params) { + return axios({ + url: '/file-storage/release-task/list', + method: 'post', + data: params + }) +} + +// 新增发布任务 +export function addReleaseTask(params) { + return axios({ + url: '/file-storage/release-task/add-task', + method: 'post', + data: params + }) +} + +// 重新发布任务 +export function reReleaseTask(params) { + return axios({ + url: '/file-storage/release-task/re-task', + method: 'post', + data: params + }) +} + +// 取消任务 +export function cancelReleaseTask(params) { + return axios({ + url: '/file-storage/release-task/cancel-task', + method: 'get', + params: params + }) +} + +// 删除任务 +export function deleteReleaseTask(params) { + return axios({ + url: '/file-storage/release-task/delete', + method: 'get', + params: params + }) +} + +// 任务详情 +export function taskDetails(params) { + return axios({ + url: '/file-storage/release-task/details', + method: 'get', + params: params + }) +} + +export function taskLogInfoList(params) { + return axios({ + url: '/file-storage/release-task/log-list', + method: 'get', + params: params, + headers: { + loading: 'no' + } + }) +} + +export const statusMap = { + 0: t('i18n_a87818b04f'), + 1: t('i18n_fb852fc6cc'), + 2: t('i18n_5ab90c17a3'), + 3: t('i18n_250688d7c9'), + 4: t('i18n_d926e2f58e') +} + +export const taskTypeMap = { + 0: 'SSH', + 1: t('i18n_3bf3c0a8d6') +} 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 0000000000..69292ddf76 --- /dev/null +++ b/web-vue/src/api/file-manager/static-storage.ts @@ -0,0 +1,61 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +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.js deleted file mode 100644 index 1da7c4b0c0..0000000000 --- a/web-vue/src/api/install.js +++ /dev/null @@ -1,27 +0,0 @@ -import axios from './config'; - -// 检查是否需要初始化系统 -export function checkSystem() { - return axios({ - url: '/check-system', - method: 'post', - headers: { - loading: 'no' - } - }) -} - -/** - * 初始化系统 - * @param { - * userName: 登录名 - * userPwd: 初始密码 - * } params - */ -export function initInstall(params) { - return axios({ - url: '/install_submit.json', - method: 'post', - data: params - }) -} \ No newline at end of file diff --git a/web-vue/src/api/install.ts b/web-vue/src/api/install.ts new file mode 100644 index 0000000000..bf878aeb07 --- /dev/null +++ b/web-vue/src/api/install.ts @@ -0,0 +1,47 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +// 检查是否需要初始化系统 +export function checkSystem() { + return axios({ + url: '/check-system', + method: 'post', + headers: { + loading: 'no' + } + }) +} + +/** + * 初始化系统 + * @param { + * userName: 登录名 + * userPwd: 初始密码 + * } params + */ +export function initInstall(params) { + return axios({ + url: '/install_submit.json', + method: 'post', + data: params + }) +} + +export function loadingLogo() { + return axios({ + url: '/logo-image', + method: 'get', + headers: { + loading: 'no' + } + }) +} diff --git a/web-vue/src/api/log-read.ts b/web-vue/src/api/log-read.ts new file mode 100644 index 0000000000..5399190aab --- /dev/null +++ b/web-vue/src/api/log-read.ts @@ -0,0 +1,63 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +// 日志搜索列表 +export function getLogReadList(params) { + return axios({ + url: '/log-read/list', + method: 'post', + data: params + }) +} + +/** + * 编辑日志搜索 + * @param { + * id: 监控 ID + * name: 监控名称 + * nodeProject: { nodeId:'',projectId:''} + * + * } params + */ +export function editLogRead(params) { + return axios({ + url: '/log-read/save.json', + method: 'post', + data: params, + headers: { + 'Content-Type': 'application/json' + } + }) +} + +export function updateCache(params) { + return axios({ + url: '/log-read/update-cache.json', + method: 'post', + data: params, + headers: { + 'Content-Type': 'application/json' + } + }) +} + +/** + * 删除日志搜索 + * @param {*} id + */ +export function deleteLogRead(id) { + return axios({ + url: '/log-read/del.json', + method: 'post', + data: { id } + }) +} diff --git a/web-vue/src/api/menu.js b/web-vue/src/api/menu.js deleted file mode 100644 index ca8e95f1a6..0000000000 --- a/web-vue/src/api/menu.js +++ /dev/null @@ -1,21 +0,0 @@ -import axios from './config'; - -// 获取系统菜单列表 -export function getSystemMenu() { - return axios({ - url: '/menus_data.json', - method: 'post' - }) -} - -/** - * 节点菜单 - * @param {String} nodeId - */ -export function getNodeMenu(nodeId) { - return axios({ - url: '/menus_data.json', - method: 'post', - data: {nodeId} - }) -} diff --git a/web-vue/src/api/menu.ts b/web-vue/src/api/menu.ts new file mode 100644 index 0000000000..55f00d41da --- /dev/null +++ b/web-vue/src/api/menu.ts @@ -0,0 +1,39 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +// 获取系统菜单列表 +export function getMenu() { + return axios({ + url: '/menus_data.json', + method: 'post' + }) +} + +// 获取系统菜单列表 +export function getSystemMenu() { + return axios({ + url: '/system_menus_data.json', + method: 'post' + }) +} + +/** + * 节点菜单 + * @param {String} nodeId + */ +export function getNodeMenu(nodeId) { + return axios({ + url: '/menus_data.json', + method: 'post', + data: { nodeId } + }) +} diff --git a/web-vue/src/api/monitor.js b/web-vue/src/api/monitor.js deleted file mode 100644 index 9f81ba60c4..0000000000 --- a/web-vue/src/api/monitor.js +++ /dev/null @@ -1,129 +0,0 @@ -import axios from './config'; - -// 监控列表 -export function getMonitorList(params) { - return axios({ - url: '/monitor/getMonitorList', - method: 'post', - data: params - }) -} - -/** - * 编辑监控 - * @param { - * id: 监控 ID - * name: 监控名称 - * status: 状态 - * autoRestart: 是否自动重启 - * cycle: 监控周期 - * projects: 监控项目 - * notifyUser: 报警联系人 - * } params - */ -export function editMonitor(params) { - return axios({ - url: '/monitor/updateMonitor', - method: 'post', - data: params - }) -} - -/** - * 修改监控状态 - * @param { - * id: 监控 ID - * status: 状态 true | false - * type: 状态类型 status | restart - * } params - */ -export function changeMonitorStatus(params) { - return axios({ - url: '/monitor/changeStatus', - method: 'post', - data: params - }) -} - -/** - * 删除监控 - * @param {*} id - */ -export function deleteMonitor(id) { - return axios({ - url: '/monitor/deleteMonitor', - method: 'post', - data: {id} - }) -} - -/** - * 监控日志 - * @param { - * page: 页码 - * limit: 每页条数 - * nodeId: 节点 ID - * notifyStatus: 通知状态 - * } params - */ -export function getMonitorLogList(params) { - return axios({ - url: '/monitor/list_data.json', - method: 'post', - data: params - }) -} - -/** - * 操作监控日志列表 - */ -export function getMonitorOperateLogList() { - return axios({ - url: '/monitor_user_opt/list_data', - method: 'post' - }) -} - -/** - * 操作类型列表 - * @returns - */ -export function getMonitorOperateTypeList() { - return axios({ - url: '/monitor_user_opt/type_data', - method: 'post' - }) -} - -/** - * 编辑操作监控 - * @param { - * id: ID - * name: 名称 - * status: 状态 => on 表示开启 - * notifyUser: 通知用户 json 字符串 - * monitorUser: 监控用户 json 字符串 - * monitorOpt: 监控操作 json 字符串 - * } params - * @returns - */ -export function editMonitorOperate(params) { - return axios({ - url: '/monitor_user_opt/update', - method: 'post', - data: params - }) -} - -/** - * 删除操作监控 - * @param {*} id - * @returns - */ -export function deleteMonitorOperate(id) { - return axios({ - url: '/monitor_user_opt/delete', - method: 'post', - data: {id} - }) -} \ No newline at end of file diff --git a/web-vue/src/api/monitor.ts b/web-vue/src/api/monitor.ts new file mode 100644 index 0000000000..131bc1ba1d --- /dev/null +++ b/web-vue/src/api/monitor.ts @@ -0,0 +1,146 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' + +// 监控列表 +export function getMonitorList(params) { + return axios({ + url: '/monitor/getMonitorList', + method: 'post', + data: params + }) +} + +/** + * 编辑监控 + * @param { + * id: 监控 ID + * name: 监控名称 + * status: 状态 + * autoRestart: 是否自动重启 + * cycle: 监控周期 + * projects: 监控项目 + * notifyUser: 报警联系人 + * } params + */ +export function editMonitor(params) { + return axios({ + url: '/monitor/updateMonitor', + method: 'post', + data: params + }) +} + +/** + * 修改监控状态 + * @param { + * id: 监控 ID + * status: 状态 true | false + * type: 状态类型 status | restart + * } params + */ +export function changeMonitorStatus(params) { + return axios({ + url: '/monitor/changeStatus', + method: 'post', + data: params + }) +} + +/** + * 删除监控 + * @param {*} id + */ +export function deleteMonitor(id) { + return axios({ + url: '/monitor/deleteMonitor', + method: 'post', + data: { id } + }) +} + +/** + * 监控日志 + * @param { + * page: 页码 + * limit: 每页条数 + * nodeId: 节点 ID + * notifyStatus: 通知状态 + * } params + */ +export function getMonitorLogList(params) { + return axios({ + url: '/monitor/list_data.json', + method: 'post', + data: params + }) +} + +/** + * 操作监控日志列表 + */ +export function getMonitorOperateLogList() { + return axios({ + url: '/monitor_user_opt/list_data', + method: 'post' + }) +} + +/** + * 操作类型列表 + * @returns + */ +export function getMonitorOperateTypeList() { + return axios({ + url: '/monitor_user_opt/type_data', + method: 'post' + }) +} + +/** + * 编辑操作监控 + * @param { + * id: ID + * name: 名称 + * status: 状态 => on 表示开启 + * notifyUser: 通知用户 json 字符串 + * monitorUser: 监控用户 json 字符串 + * monitorOpt: 监控操作 json 字符串 + * } params + * @returns + */ +export function editMonitorOperate(params) { + return axios({ + url: '/monitor_user_opt/update', + method: 'post', + data: params + }) +} + +/** + * 删除操作监控 + * @param {*} id + * @returns + */ +export function deleteMonitorOperate(id) { + return axios({ + url: '/monitor_user_opt/delete', + method: 'post', + data: { id } + }) +} + +export const notifyStyle = { + 0: t('i18n_4a0e9142e7'), + 1: t('i18n_3bc5e602b2'), + 2: t('i18n_ff17b9f9cd'), + 3: 'webhook' +} diff --git a/web-vue/src/api/node-nginx.js b/web-vue/src/api/node-nginx.js deleted file mode 100644 index a963d31016..0000000000 --- a/web-vue/src/api/node-nginx.js +++ /dev/null @@ -1,221 +0,0 @@ -import axios from './config'; - -/** - * Nginx 目录列表 - * @param { - * nodeId: 节点 ID - * } params - */ -export function getNginxDirectoryList(params) { - return axios({ - url: '/node/system/nginx/tree.json', - method: 'post', - data: params - }) -} - -/** - * Nginx 配置文件数据 - * @param { - * nodeId: 节点 ID - * whitePath: nginx 白名单目录 - * name: 子级目录 - * } params - */ -export function getNginxFileList(params) { - return axios({ - url: '/node/system/nginx/list_data.json', - method: 'post', - data: params - }) -} - -/** - * 编辑 Nginx 配置文件 - * @param { - * nodeId: 节点 ID - * genre: 操作类型 - * whitePath: 白名单目录 - * name: 文件名称 - * context: 内容 - * } params - */ -export function editNginxConfig(params) { - return axios({ - url: '/node/system/nginx/updateNgx', - method: 'post', - data: params - }) -} - -/** - * 删除 - * @param { - * nodeId: 节点 ID - * path: 路径 - * name: 文件名称 - * } params - */ -export function deleteNginxConfig(params) { - return axios({ - url: '/node/system/nginx/delete', - method: 'post', - data: params - }) -} - -/** - * 加载 Nginx 白名单列表 - * @param { - * nodeId: 节点 ID - * } params - */ -export function loadNginxWhiteList(params) { - return axios({ - url: '/node/system/nginx/white-list', - method: 'post', - data: params - }) -} - -/** - * 加载 Nginx 配置内容 - * @param { - * nodeId: 节点 ID - * path: 白名单目录 - * name: 文件名称 - * } params - */ -export function loadNginxConfig(params) { - return axios({ - url: '/node/system/nginx/load-config', - method: 'post', - data: params - }) -} - -/** - * 加载 Nginx 状态 - * @param { - * nodeId: 节点 ID - * } params - */ -export function loadNginxData(params) { - return axios({ - url: '/node/system/nginx/status', - method: 'post', - data: params - }) -} - -/** - * 管理 Nginx 服务 - * @param { - * nodeId: 节点 ID - * command: 命令 {open || reload || stop} - * } params - */ -export function doNginxCommand(params) { - return axios({ - url: `/node/system/nginx/${params.command}`, - method: 'post', - data: params - }) -} - -/** - * 编辑 Nginx 服务名称 - * @param { - * nodeId: 节点 ID - * name: Nginx 服务名称 - * } params - */ -export function editNginxServerName(params) { - return axios({ - url: '/node/system/nginx/updateConf', - method: 'post', - data: params - }) -} - -/***************************** */ - -/** - * cert 白名单列表 - * @param { - * nodeId: 节点 ID - * } params - */ -export function getCertWhiteList(params) { - return axios({ - url: '/node/system/certificate/white-list', - method: 'post', - data: params - }) -} - -/** - * cert 列表 - * @param { - * nodeId: 节点 ID - * } params - */ -export function getCertList(params) { - return axios({ - url: '/node/system/certificate/getCertList', - method: 'post', - data: params - }) -} - -/** - * 编辑 cert - * @param { - * file: 文件 multipart/form-data - * nodeId: 节点 ID - * data: 证书相关数据 - * } formData - */ -export function editCert(formData) { - return axios({ - url: '/node/system/certificate/saveCertificate', - headers: { - 'Content-Type': 'multipart/form-data;charset=UTF-8' - }, - method: 'post', - timeout: 0, - data: formData - }) -} - -/** - * 删除 cert - * @param { - * nodeId: 节点 ID - * id: 证书 ID - * } params - */ -export function deleteCert(params) { - return axios({ - url: '/node/system/certificate/delete', - method: 'post', - data: params - }) -} - -/** - * 导出 cert - * @param { - * nodeId: 节点 ID - * id: 证书 ID - * } params - */ -export function downloadCert(params) { - return axios({ - url: '/node/system/certificate/export', - method: 'get', - responseType: 'blob', - timeout: 0, - params - }) -} \ No newline at end of file diff --git a/web-vue/src/api/node-other.js b/web-vue/src/api/node-other.js deleted file mode 100644 index 02335972cb..0000000000 --- a/web-vue/src/api/node-other.js +++ /dev/null @@ -1,307 +0,0 @@ -import axios from './config'; - -/** - * tomcat 列表 - * @param {String} nodeId 节点 ID - */ -export function getTomcatList(nodeId) { - return axios({ - url: '/node/tomcat/list', - method: 'post', - data: {nodeId} - }) -} - -/** - * Tomcat 编辑 - * @param {nodeId, id, name, path, port, appBase} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - * params.name 名称 - * params.path jdk 路径 - * params.port 端口 - * params.appBase appBase 路径 - */ -export function editTomcat(params) { - return axios({ - url: '/node/tomcat/save', - method: 'post', - data: params - }) -} - -/** - * 删除 Tomcat - * @param {nodeId, id} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - */ -export function deleteTomcat(params) { - return axios({ - url: '/node/tomcat/delete', - method: 'post', - data: params - }) -} - -/** - * 上传 Tomcat WAR 项目文件 - * @param { - * file: 文件 multipart/form-data - * nodeId: 节点 ID - * id: Tomcat ID - * } formData - */ - export function uploadTomcatWarFile(formData) { - return axios({ - url: '/node/tomcat/uploadWar', - headers: { - 'Content-Type': 'multipart/form-data;charset=UTF-8' - }, - method: 'post', - // 0 表示无超时时间 - timeout: 0, - data: formData - }) - } - -/** - * 查询项目列表 - * @param {nodeId, id} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - */ -export function getTomcatProjectList(params) { - return axios({ - url: '/node/tomcat/getTomcatProject', - method: 'post', - data: params - }) -} - -/** - * 查询 tomcat 状态 - * @param {nodeId, id} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - */ -export function getTomcatStatus(params) { - return axios({ - url: '/node/tomcat/getTomcatStatus', - method: 'post', - data: params - }) -} - -/** - * tomcat 日志列表 - * @param {nodeId, id} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - */ -export function getTomcatLogList(params) { - return axios({ - url: '/node/tomcat/getLogList', - method: 'post', - data: params - }) -} - -/** - * 删除 Tomcat 日志 - * @param {nodeId, path, filename, id} params - * params.nodeId 节点 ID - * params.path tomcat 日志目录 - * params.filename 日志名称 - * params.id 编辑修改时判断 ID - */ -export function deleteTomcatFile(params) { - return axios({ - url: '/node/tomcat/deleteFile', - method: 'post', - data: params - }) -} - -/** - * 下载文件的返回是 blob 类型,把 blob 用浏览器下载下来 - * @param {nodeId, path, filename, id} params - * params.nodeId 节点 ID - * params.path tomcat 日志目录 - * params.filename 日志名称 - * params.id 编辑修改时判断 ID - */ -export function downloadTomcatFile(params) { - return axios({ - url: '/node/tomcat/download', - method: 'get', - responseType: 'blob', - params - }) -} - -/** - * 启动 Tomcat - * @param { - * nodeId: 节点 ID - * id: Tomcat ID - * } params - */ -export function startTomcat(params) { - return axios({ - url: '/node/tomcat/start', - method: 'post', - data: params - }) -} - -/** - * 停止 Tomcat - * @param { - * nodeId: 节点 ID - * id: Tomcat ID - * } params -*/ -export function stopTomcat(params) { - return axios({ - url: '/node/tomcat/stop', - method: 'post', - data: params - }) -} - -/** - * 重启 Tomcat - * @param { - * nodeId: 节点 ID - * id: Tomcat ID - * } params -*/ -export function restartTomcat(params) { - return axios({ - url: '/node/tomcat/restart', - method: 'post', - data: params - }) -} - -/** - * Tomcat 项目命令操作 - * @param { - * nodeId: 节点 ID - * id: Tomcat ID - * path: 项目目录 - * op: 操作符 - * } params - */ -export function doTomcatProjectCommand(params) { - return axios({ - url: '/node/tomcat/tomcatProjectManage', - method: 'post', - data: params - }) -} - -/** - * Tomcat 项目文件列表 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * path: Tomcat 项目目录 - * except: dir 固定值 - * } params - */ - export function getTomcatFileList(params) { - return axios({ - url: '/node/tomcat/getFileList', - method: 'post', - data: params - }) - } - - /** - * 上传 Tomcat 项目文件 - * @param { - * file: 文件 multipart/form-data - * nodeId: 节点 ID - * id: 项目 ID - * path: 目录地址 - * } formData - */ - export function uploadTomcatProjectFile(formData) { - return axios({ - url: '/node/tomcat/upload', - headers: { - 'Content-Type': 'multipart/form-data;charset=UTF-8' - }, - method: 'post', - // 0 表示无超时时间 - timeout: 0, - data: formData - }) - } - - /************************** */ - -/** - * script 列表 - * @param {String} nodeId 节点 ID - */ -export function getScriptList(nodeId) { - return axios({ - url: '/node/script/list', - method: 'post', - data: {nodeId} - }) -} - -/** - * Script 编辑 - * @param {nodeId, id, name, path, port, appBase} params - * params.type: add 表示添加 - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - * params.name 名称 - * params.context 内容 - */ -export function editScript(params) { - return axios({ - url: '/node/script/save.json', - method: 'post', - data: params - }) -} - -/** - * 删除 Script - * @param {nodeId, id} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - */ -export function deleteScript(params) { - return axios({ - url: '/node/script/del.json', - method: 'post', - data: params - }) -} - -/** - * 上传 Script 文件 - * @param { - * file: 文件 multipart/form-data - * nodeId: 节点 ID - * } formData -*/ -export function uploadScriptFile(formData) { - return axios({ - url: '/node/script/upload', - headers: { - 'Content-Type': 'multipart/form-data;charset=UTF-8' - }, - method: 'post', - // 0 表示无超时时间 - timeout: 0, - data: formData - }) -} diff --git a/web-vue/src/api/node-other.ts b/web-vue/src/api/node-other.ts new file mode 100644 index 0000000000..40aeb2ac4c --- /dev/null +++ b/web-vue/src/api/node-other.ts @@ -0,0 +1,132 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' + +/************************** */ + +/** + * script 服务端中的所有列表 + */ +export function getScriptListAll(params) { + return axios({ + url: '/node/script/list_all', + method: 'post', + data: params + }) +} + +// 脚本模版日志列表 +export function getScriptLogList(params) { + return axios({ + url: '/node/script_log/list', + method: 'post', + data: params + }) +} + +// 删除执行记录 +export function scriptDel(params) { + return axios({ + url: '/node/script_log/del', + method: 'post', + data: params + }) +} + +//执行记录 详情 +export function scriptLog(params) { + return axios({ + url: '/node/script_log/log', + method: 'post', + data: params, + headers: { + tip: 'no' + } + }) +} + +/** + * Script 编辑 + * @param {nodeId, id, name, path, port, appBase} params + * params.type: add 表示新增 + * params.nodeId 节点 ID + * params.id 编辑修改时判断 ID + * params.name 名称 + * params.context 内容 + */ +export function editScript(params) { + return axios({ + url: '/node/script/save.json', + method: 'post', + data: params + }) +} + +export function itemScript(params) { + return axios({ + url: '/node/script/item.json', + method: 'get', + params: params + }) +} + +export function syncScript(params) { + return axios({ + url: '/node/script/sync', + method: 'get', + params: params + }) +} +export const triggerExecTypeMap = { + 0: t('i18n_2a3e7f5c38'), + 1: t('i18n_3aed2c11e9'), + 2: t('i18n_4696724ed3') +} + +/** + * 获取触发器地址 + * @param {*} id + */ +export function getTriggerUrl(data) { + return axios({ + url: '/node/script/trigger-url', + method: 'post', + data: data + }) +} + +/** + * 删除 Script + * @param {nodeId, id} params + * params.nodeId 节点 ID + * params.id 编辑修改时判断 ID + */ +export function deleteScript(params) { + return axios({ + url: '/node/script/del.json', + method: 'post', + data: params + }) +} + +/** + * 解绑 Script + * @param {id} params + + * params.id 编辑修改时判断 ID + */ +export function unbindScript(params) { + return axios({ + url: '/node/script/unbind.json', + method: 'get', + params: params + }) +} diff --git a/web-vue/src/api/node-project-backup.ts b/web-vue/src/api/node-project-backup.ts new file mode 100644 index 0000000000..f3a56ec93e --- /dev/null +++ b/web-vue/src/api/node-project-backup.ts @@ -0,0 +1,87 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' +import { loadRouterBase } from './config' + +/** + * 项目列表 + * @param {JSON} params { + * nodeId: 节点 ID, + * id: 项目ID + * } + */ +export function listBackup(params) { + return axios({ + url: '/node/manage/file/list-backup', + method: 'post', + data: params + }) +} + +export function backupFileList(params) { + return axios({ + url: '/node/manage/file/backup-item-files', + method: 'post', + headers: { + loading: 'no' + }, + data: params + }) +} + +/** + * 下载项目文件 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 文件 levelName + * filename: 文件名称 + * } params + */ +export function backupDownloadProjectFile(params) { + return loadRouterBase('/node/manage/file/backup-download', params) +} + +/** + * 删除文件 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 文件 levelName + * filename: 文件名称 + * + * } params + */ +export function backupDeleteProjectFile(params) { + return axios({ + url: '/node/manage/file/backup-delete', + method: 'post', + data: params + }) +} + +/** + * 还原文件 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 文件 levelName + * filename: 文件名称 + * + * } params + */ +export function backupRecoverProjectFile(params) { + return axios({ + url: '/node/manage/file/backup-recover', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/node-project.js b/web-vue/src/api/node-project.js deleted file mode 100644 index 77bfb48c36..0000000000 --- a/web-vue/src/api/node-project.js +++ /dev/null @@ -1,538 +0,0 @@ -/** - * 节点管理 api - */ -import axios from "./config"; - -/** - * jdk 列表 - * @param {String} nodeId 节点 ID - */ -export function getJdkList(nodeId) { - return axios({ - url: "/node/manage/jdk/list", - method: "post", - data: { nodeId }, - }); -} - -/** - * jdk 编辑 - * @param {nodeId, id, name, path} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - * params.name 名称 - * params.path jdk 路径 - */ -export function editJdk(params) { - return axios({ - url: "/node/manage/jdk/update", - method: "post", - data: params, - }); -} - -/** - * 删除 JDK - * @param {nodeId, id} params - * params.nodeId 节点 ID - * params.id 编辑修改时判断 ID - */ -export function deleteJdk(params) { - return axios({ - url: "/node/manage/jdk/delete", - method: "post", - data: params, - }); -} - -/** - * 项目列表 - * @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) { - return axios({ - url: "/node/manage/getProjectPort", - method: "post", - data: params, - timeout: 0, - headers: { - loading: "no", - }, - }); -} - -/** - * 获取单个项目信息 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * } params - */ -export function getProjectData(params) { - return axios({ - url: "/node/manage/getProjectData.json", - method: "post", - data: params, - }); -} - -/** - * 项目白名单列表 - * @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: 分组名称 - * jdkId: JDK - * ... - * } - * @param {JSON} replicaParams { - * javaCopyIds: 副本 xx1,xx2 - * jvm_xxn: 副本 n JVM 参数 - * args_xxn: 副本 n args 参数 - * } - */ -export function editProject(params, replicaParams) { - const data = { - nodeId: params.nodeId, - id: params.id, - name: params.name, - group: params.group, - jdkId: params.jdkId, - 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, - ...replicaParams, - }; - return axios({ - url: "/node/manage/saveProject", - method: "post", - data, - }); -} - -/** - * 删除项目 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * copyId: copyId - * } params - */ -export function deleteProject(params) { - return axios({ - url: "/node/manage/deleteProject", - 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 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", - 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", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - 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 {String} nodeId 节点 ID - */ -export function getRecoverList(nodeId) { - return axios({ - url: "/node/manage/recover/recover-list", - method: "post", - data: { nodeId }, - }); -} - -/** - * 获取回收项目信息 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * } params - */ -export function getRecoverData(params) { - return axios({ - url: "/node/manage/recover/data.json", - method: "post", - data: params, - }); -} - -/** - * 获取项目日志文件大小 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * copyId: copyId - * } params - */ -export function getProjectLogSize(params) { - return axios({ - url: "/node/manage/log/logSize", - method: "post", - data: params, - headers: { - loading: "no", - }, - }); -} - -/** - * 下载项目日志文件 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * copyId: copyId - * } params - */ -export function downloadProjectLogFile(params) { - return axios({ - url: "/node/manage/log/export.html", - method: "get", - responseType: "blob", - timeout: 0, - 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 - * copyId: copyId - * key: 文件名 - * } params - */ -export function downloadProjectLogBackFile(params) { - return axios({ - url: "/node/manage/log/logBack_download", - method: "get", - responseType: "blob", - timeout: 0, - params, - }); -} - -/** - * 项目日志备份文件删除 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * copyId: copyId - * name: 文件名 - * } params - */ -export function deleteProjectLogBackFile(params) { - return axios({ - url: "/node/manage/log/logBack_delete", - method: "post", - data: params, - }); -} - -/** - * 获取内存信息接口 - * @param { - * nodeId: 节点 ID - * tag: 项目 ID - * copyId: copyId - * } 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 - * copyId: copyId - * } params - */ -export function exportStack(params) { - return axios({ - url: "/node/manage/stack", - method: "get", - responseType: "blob", - timeout: 0, - params, - }); -} - -/** - * 导出内存信息 - * @param { - * nodeId: 节点 ID - * tag: 项目 ID - * copyId: copyId - * } params - */ -export function exportRam(params) { - return axios({ - url: "/node/manage/ram", - method: "get", - responseType: "blob", - timeout: 0, - params, - }); -} - -/** - * 加载副本集 - * @param { - * nodeId: 节点 ID - * id: 项目 ID - * } params - */ -export function getProjectReplicaList(params) { - return axios({ - url: "/node/manage/project_copy_list", - method: "post", - data: 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 - * copyId: 副本id - * } params - */ -export function restartProject(params) { - return axios({ - url: "/node/manage/restart", - method: "post", - data: params, - }); -} - -/** - * 启动项目 - * @param { - * nodeId: 节点 ID, - * id: 项目id - * copyId: 副本id - * } params - */ -export function startProject(params) { - return axios({ - url: "/node/manage/start", - method: "post", - data: params, - }); -} - -/** - * 关闭项目 - * @param { - * nodeId: 节点 ID, - * id: 项目id - * copyId: 副本id - * } params - */ -export function stopProject(params) { - return axios({ - url: "/node/manage/stop", - method: "post", - data: params, - }); -} - -export const runModeList = ["ClassPath", "Jar", "JarWar", "JavaExtDirsCp", "File"]; diff --git a/web-vue/src/api/node-project.ts b/web-vue/src/api/node-project.ts new file mode 100644 index 0000000000..9e3ea98bfa --- /dev/null +++ b/web-vue/src/api/node-project.ts @@ -0,0 +1,596 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +/** + * 节点管理 api + */ +import { t } from '@/i18n' +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, + disableScanDir: params.disableScanDir + } + 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 + }) +} + +/** + * 复制文件 + * @param params + * @returns {id, levelName, filename,newname} params x + */ +export function copyFileFolder(params) { + return axios({ + url: '/node/manage/file/copy', + method: 'post', + data: params + }) +} + +/** + * 压缩文件 + * @param params + * @returns {id, levelName, filename,newname} params x + */ +export function compressFileFolder(params) { + return axios({ + url: '/node/manage/file/compress', + method: 'post', + data: 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: t('i18n_386edb98a5') }, + { name: 'ClassPath', desc: t('i18n_f9c9f95929') }, + { name: 'Jar', desc: t('i18n_be24e5ffbe') }, + { name: 'JavaExtDirsCp', desc: t('i18n_eef4dfe786') }, + { name: 'File', desc: t('i18n_f282058f75') }, + { + name: 'Link', + desc: t('i18n_c538b1db4a'), + // 仅有节点有此项目(节点分发不支持) + onlyNode: true + }, + { name: 'JarWar', desc: t('i18n_d6eab4107a') } +] + +/** + * 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-stat.ts b/web-vue/src/api/node-stat.ts new file mode 100644 index 0000000000..003e189ea8 --- /dev/null +++ b/web-vue/src/api/node-stat.ts @@ -0,0 +1,376 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' +import { parseTime, formatPercent2, renderSize, formatDuration } from '@/utils/const' +import * as echarts from 'echarts/core' +import { GridComponent, TitleComponent, LegendComponent, TooltipComponent, DataZoomComponent } from 'echarts/components' +import { LineChart } from 'echarts/charts' +import { UniversalTransition } from 'echarts/features' +import { CanvasRenderer } from 'echarts/renderers' +echarts.use([ + GridComponent, + LineChart, + CanvasRenderer, + UniversalTransition, + TitleComponent, + LegendComponent, + TooltipComponent, + DataZoomComponent +]) + +// 获取机器信息 +export function machineInfo(params) { + return axios({ + url: '/node/machine-info', + method: 'get', + params: params, + headers: { + loading: 'no' + } + }) +} + +// 机器文件系统 +export function machineDiskInfo(params) { + return axios({ + url: '/node/disk-info', + method: 'get', + params, + headers: { + loading: 'no' + } + }) +} + +// 机器硬件硬盘 +export function machineHwDiskInfo(params) { + return axios({ + url: '/node/hw-disk-info', + method: 'get', + params, + headers: { + loading: 'no' + } + }) +} + +const defaultData = { + title: { + // text: "系统 Top 监控", + }, + tooltip: { + trigger: 'axis' + }, + legend: { + // data: legends, + }, + color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], + grid: { + left: '1%', + right: '2%', + bottom: '1%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false + // data: scales, + }, + calculable: true, + // yAxis: { + // type: "value", + // axisLabel: { + // // 设置y轴数值为% + // formatter: "{value} %", + // }, + // max: 100, + // }, + dataZoom: [{ type: 'inside' }, { type: 'slider' }] + // series: series, +} + +/** + * 节点系统统计 + * @param { JSON } data + * @returns + */ +export function generateNodeTopChart(data) { + const cpuItem = { + name: t('i18n_7f5bcd975b'), + type: 'line', + data: [], + showSymbol: false, + // 设置折线为曲线 + smooth: true + } + const diskItem = { + name: t('i18n_f5d14ee3f8'), + type: 'line', + data: [], + showSymbol: false, + smooth: true + } + const memoryItem = { + name: t('i18n_883848dd37'), + type: 'line', + data: [], + showSymbol: false, + smooth: true + } + const virtualMemory = { + name: t('i18n_07a03567aa'), + type: 'line', + data: [], + showSymbol: false, + smooth: true + } + const swapMemory = { + name: t('i18n_0895c740a6'), + type: 'line', + data: [], + showSymbol: false, + smooth: true + } + const scales = [] + for (let i = data.length - 1; i >= 0; i--) { + const item = data[i] + cpuItem.data.push(parseFloat(item.occupyCpu)) + diskItem.data.push(parseFloat(item.occupyDisk)) + memoryItem.data.push(parseFloat(item.occupyMemory)) + swapMemory.data.push(parseFloat(item.occupySwapMemory || -0.1)) + virtualMemory.data.push(parseFloat(item.occupyVirtualMemory || -0.1)) + + scales.push(parseTime(item.monitorTime)) + } + //swapMemory, virtualMemory + const series = [cpuItem, memoryItem, diskItem] + if ( + swapMemory.data.filter((item) => { + return item !== -0.1 + }).length + ) { + series.push(swapMemory) + } + if ( + virtualMemory.data.filter((item) => { + return item !== -0.1 + }).length + ) { + series.push(virtualMemory) + } + let maxVlaue = 0 + const legends = series.map((data) => { + const itemMax = Math.max(...data.data) + maxVlaue = Math.max(itemMax, maxVlaue) + return data.name + }) + + // 指定图表的配置项和数据 + return Object.assign({}, defaultData, { + legend: { + data: legends + }, + xAxis: { + data: scales + }, + yAxis: { + type: 'value', + axisLabel: { + // 设置y轴数值为% + formatter: '{value} %' + }, + max: maxVlaue + }, + tooltip: { + trigger: 'axis', + show: true, + formatter: function (params) { + let html = params[0].name + '
' + for (let i = 0; i < params.length; i++) { + html += params[i].marker + params[i].seriesName + ':' + formatPercent2(params[i].value) + '
' + } + return html + } + }, + series: series + }) +} + +/** + * 节点网络统计 + * @param { JSON } data + * @returns + */ +export function generateNodeNetChart(data) { + const rxItem = { + name: t('i18n_15e9238b79'), + type: 'line', + data: [], + showSymbol: false, + // 设置折线为曲线 + smooth: true + } + const txItem = { + name: t('i18n_1535fcfa4c'), + type: 'line', + data: [], + showSymbol: false, + smooth: true + } + const scales = [] + for (let i = data.length - 1; i >= 0; i--) { + const item = data[i] + txItem.data.push(item.netTxBytes) + rxItem.data.push(item.netRxBytes) + + scales.push(parseTime(item.monitorTime)) + } + + const series = [rxItem, txItem] + + const legends = series.map((data) => { + return data.name + }) + + // 指定图表的配置项和数据 + return Object.assign({}, defaultData, { + legend: { + data: legends + }, + xAxis: { + data: scales + }, + yAxis: { + type: 'value', + axisLabel: { + // 设置y轴数值为 bit/s + // formatter: "{value} bit/s", + formatter: (value) => { + return renderSize(value) + } + } + }, + tooltip: { + trigger: 'axis', + show: true, + formatter: function (params) { + let html = params[0].name + '
' + for (let i = 0; i < params.length; i++) { + html += params[i].marker + params[i].seriesName + ':' + renderSize(params[i].value) + '/s
' + } + return html + } + }, + series: series + }) +} + +/** + * 节点网络延迟 + * @param { JSON } data + * @returns + */ +export function generateNodeNetworkTimeChart(data) { + const dataArray = { + name: t('i18n_204222d167'), + type: 'line', + data: [], + showSymbol: false, + // 设置折线为曲线 + smooth: true + } + const scales = [] + for (let i = data.length - 1; i >= 0; i--) { + const item = data[i] + dataArray.data.push(parseFloat(item.networkDelay)) + scales.push(parseTime(item.monitorTime)) + } + + const series = [dataArray] + + const legends = series.map((data) => { + return data.name + }) + // 指定图表的配置项和数据 + return Object.assign({}, defaultData, { + legend: { + data: legends + }, + xAxis: { + data: scales + }, + yAxis: { + type: 'value', + axisLabel: { + // formatter: "{value} ms", + formatter: (value) => { + return formatDuration(value) + } + } + }, + tooltip: { + trigger: 'axis', + show: true, + formatter: function (params) { + let html = params[0].name + '
' + for (let i = 0; i < params.length; i++) { + html += params[i].marker + params[i].seriesName + ':' + formatDuration(params[i].value) + '
' + } + return html + } + }, + series: series + }) +} + +/** + * + * @param {*} data + * @param {String} domId + * @returns + */ +export function drawChart(data, domId, parseFn, theme) { + const historyChartDom = document.getElementById(domId) + if (!historyChartDom) { + // console.error('dom 节点不存在', domId) + return + } + const option = parseFn(data) + const myChart = echarts.getInstanceByDom(historyChartDom) + if (myChart) { + myChart.setOption(option) + return myChart + } + // 绘制图表 + const historyChart = echarts.init(historyChartDom, theme) + historyChart.setOption(option) + return historyChart +} + +// export const status = { +// 1: "无法连接", +// 0: "正常", +// 2: "授权信息错误", +// 3: "状态码错误", +// 4: "关闭中", +// }; + +// 机器网络 +export function machineNetworkInterfaces(params) { + return axios({ + url: '/node/network-interfaces', + method: 'get', + params, + headers: { + loading: 'no' + } + }) +} diff --git a/web-vue/src/api/node-system.js b/web-vue/src/api/node-system.js deleted file mode 100644 index d5e816018b..0000000000 --- a/web-vue/src/api/node-system.js +++ /dev/null @@ -1,30 +0,0 @@ -import axios from './config'; - -/** - * white list data - * @param {nodeId} nodeId - */ -export function getWhiteList(nodeId) { - return axios({ - url: '/node/system/white-list', - method: 'post', - data: {nodeId} - }) -} - -/** - * edit white list data - * @param { - * nodeId: 节点 ID, - * project: 项目目录, - * certificate: 证书目录, - * nginx: Nginx 目录 - * } params - */ -export function editWhiteList(params) { - return axios({ - url: '/node/system/whitelistDirectory_submit', - method: 'post', - data: params - }) -} \ No newline at end of file diff --git a/web-vue/src/api/node-system.ts b/web-vue/src/api/node-system.ts new file mode 100644 index 0000000000..e9a18461dd --- /dev/null +++ b/web-vue/src/api/node-system.ts @@ -0,0 +1,40 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +/** + * white list data + * @param {nodeId} nodeId + */ +export function getWhiteList(data) { + return axios({ + url: '/node/system/white-list', + method: 'post', + data: data + }) +} + +/** + * edit white list data + * @param { + * nodeId: 节点 ID, + * project: 项目目录, + + + * } params + */ +export function editWhiteList(params) { + return axios({ + url: '/node/system/whitelistDirectory_submit', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/node.js b/web-vue/src/api/node.js deleted file mode 100644 index 37bf6bbead..0000000000 --- a/web-vue/src/api/node.js +++ /dev/null @@ -1,233 +0,0 @@ -import axios from "./config"; - -// node 列表 -export function getNodeList(params) { - return axios({ - url: "/node/list_data.json", - method: "post", - params: params, - }); -} - -// node 列表 all -export function getNodeListAll() { - return axios({ - url: "/node/list_data_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 getNodeProjectList(params) { - return axios({ - url: "/node/node_project_list", - method: "post", - params: params, - }); -} - -// 节点 + 项目列表 -export function getProjectList(params) { - return axios({ - url: "/node/project_list", - method: "post", - params: params, - }); -} - -// 节点 + 项目列表 -export function getProjectListAll() { - 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 delProjectCache(nodeId) { - return axios({ - url: "/node/del_project_cache", - method: "get", - params: { nodeId: nodeId }, - }); -} - -// 删除节点项目缓存 -export function delAllProjectCache() { - return axios({ - url: "/node/clear_all_project", - method: "get", - 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, - }; - return axios({ - url: "/node/save.json", - method: "post", - data, - }); -} - -// 删除 node -export function deleteNode(id) { - return axios({ - url: "/node/del.json", - method: "post", - data: { id }, - }); -} - -// 节点 top 命令 -export function getNodeTop(nodeId) { - return axios({ - url: "/node/getTop", - method: "post", - data: { nodeId }, - headers: { - loading: "no", - }, - }); -} - -// 获取进程列表 -export function getProcessList(nodeId) { - return axios({ - url: "/node/processList", - method: "post", - data: { nodeId }, - headers: { - loading: "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) { - return axios({ - url: "/node/nodeMonitor_data.json", - method: "post", - data: params, - }); -} - -/** - * 上传升级文件 - * @param { - * file: 文件 multipart/form-data, - * nodeId: 节点 ID - * } formData - */ -export function uploadAgentFile(formData) { - return axios({ - url: "/node/upload_agent", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - method: "post", - // 0 表示无超时时间 - timeout: 0, - data: formData, - }); -} - -/** - * 检查远程最新 - * @returns json - */ -export function checkVersion() { - return axios({ - url: "/node/check_version.json", - method: "get", - data: {}, - }); -} - -/** - * 下载远程文件 - * @returns json - */ -export function downloadRemote() { - return axios({ - url: "/node/download_remote.json", - method: "get", - data: {}, - }); -} diff --git a/web-vue/src/api/node.ts b/web-vue/src/api/node.ts new file mode 100644 index 0000000000..e3909839ce --- /dev/null +++ b/web-vue/src/api/node.ts @@ -0,0 +1,331 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +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.js deleted file mode 100644 index 3c18d35b28..0000000000 --- a/web-vue/src/api/operation-log.js +++ /dev/null @@ -1,10 +0,0 @@ -import axios from './config'; - -// 操作日志列表 -export function getOperationLogList(params) { - return axios({ - url: '/user/log/list_data.json', - method: 'post', - data: params - }) -} \ No newline at end of file diff --git a/web-vue/src/api/operation-log.ts b/web-vue/src/api/operation-log.ts new file mode 100644 index 0000000000..5e8275e4ea --- /dev/null +++ b/web-vue/src/api/operation-log.ts @@ -0,0 +1,20 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +// 操作日志列表 +export function getOperationLogList(params) { + return axios({ + url: '/user/log/list_data.json', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/repository.js b/web-vue/src/api/repository.js deleted file mode 100644 index dfff838533..0000000000 --- a/web-vue/src/api/repository.js +++ /dev/null @@ -1,89 +0,0 @@ -import axios from "./config"; - -/** - * 仓库列表 - * @param { - * name: 仓库名称 - * } params - */ -export function getRepositoryList(params) { - return axios({ - url: "/build/repository/list", - method: "post", - data: params, - }); -} - -/** - * 仓库列表 all - * - */ -export function getRepositoryListAll() { - return axios({ - url: "/build/repository/list_all", - method: "get", - }); -} - -/** - * 编辑仓库信息,新增或者删除 - * @param { - * id: id - * name: 仓库名称 - * gitUrl: 仓库地址 - * repoType: 仓库类型 {0: GIT, 1: SVN} - * protocol: 协议 {0: HTTP(S), 1: SSH} - * userName: 用户名 - * password: 密码 - * rsaPub: 公钥信息 - * } params - * @returns - */ -export function editRepository(params) { - return axios({ - url: "/build/repository/edit", - method: "post", - data: params, - }); -} - -/** - * delete by id -@param { - * id: id - * isRealDel: 是否真删 - * } params - * @returns - */ -export function deleteRepository(params) { - return axios({ - url: "/build/repository/delete", - method: "post", - data: params, - }); -} -/** - * delete by id - * @param {String} id - * @returns - */ -export function recoveryRepository(id) { - return axios({ - url: "/build/repository/recovery", - method: "post", - data: { id }, - }); -} - -/** - * restHideField by id - * @param {String} id - * @returns - */ -export function restHideField(id) { - return axios({ - url: "/build/repository/rest_hide_field", - method: "post", - data: { id }, - }); -} diff --git a/web-vue/src/api/repository.ts b/web-vue/src/api/repository.ts new file mode 100644 index 0000000000..974ffbe574 --- /dev/null +++ b/web-vue/src/api/repository.ts @@ -0,0 +1,160 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios, { loadRouterBase } from './config' + +/** + * 分页获取仓库列表 + * + * @param {Object} params 分页和查询参数 + * @param {Number} params.limit 每页显示条数,默认 10 条 + * @param {Number} params.page 获取第几页的数据,默认第 1 页 + * @param {Number} params.total 总条数,默认 0 + * @param {String} params.order 排序方式[ascend(升序,从小到大), descend(降序,从大到小)] + * @param {String} params.order_field 需要排序的字段名称 + * @param {Number} params.repoType 查询的仓库类型[0(GIT), 1(SVN)] + * @param {String} params.name 仓库名称 + * @param {String} params.gitUrl 仓库地址 + * @return {axios} 请求结果 axios 对象 + */ +export function getRepositoryList(params) { + return axios({ + url: '/build/repository/list', + method: 'post', + data: params + }) +} + +/* + * 下载导入模板 + * + */ +export function importTemplate(data) { + return loadRouterBase('/build/repository/import-template', data) +} +/** + * 导出CSV + * @param data + * @returns {string} + */ +export function exportData(data) { + return loadRouterBase('/build/repository/export', data) +} +// 导入数据 +export function importData(formData) { + return axios({ + url: '/build/repository/import-data', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} +/** + * 获取仓库信息 + * + * @return {axios} 请求结果 axios 对象 + */ +export function getRepositoryInfo(params) { + return axios({ + url: '/build/repository/get', + method: 'get', + params + }) +} + +/** + * 获取仓库信息 + * + * @return {axios} 请求结果 axios 对象 + */ +export function listRepositoryGroup(params) { + return axios({ + url: '/build/repository/list-group', + method: 'get', + params + }) +} + +/** + * 编辑仓库信息,新增或者更新 + * + * @param {Object} params 请求参数 + * @param {String} params.id 仓库id + * @param {String} params.name 仓库名称 + * @param {String} params.gitUrl 仓库地址 + * @param {Number} params.repoType 仓库类型[0(GIT), 1(SVN)] + * @param {Number} params.protocol 协议[0(HTTP(S)), 1(SSH)] + * @param {String} params.userName 用户名 + * @param {String} params.password 密码 + * @param {String} params.rsaPub 公钥信息 + * @return {axios} 请求结果 axios 对象 + */ +export function editRepository(params) { + return axios({ + url: '/build/repository/edit', + method: 'post', + data: params + }) +} + +/** + * 根据 仓库id 删除仓库 + * + * @param {Object} params 请求参数 + * @param {String} params.id 仓库id + * @param {Boolean} params.isRealDel 是否真正删除 + * @return {axios} 请求结果 axios 对象 + */ +export function deleteRepository(params) { + return axios({ + url: '/build/repository/delete', + method: 'post', + data: params + }) +} +/** + * restHideField by id + * @param {String} id + * @returns + */ +export function restHideField(id) { + return axios({ + url: '/build/repository/rest_hide_field', + method: 'post', + data: { id } + }) +} + +export function authorizeRepos(param) { + return axios({ + url: '/build/repository/authorize_repos', + method: 'get', + params: param + }) +} + +export function providerInfo() { + return axios({ + url: '/build/repository/provider_info', + method: 'get' + }) +} + +export function sortItem(params) { + return axios({ + url: '/build/repository/sort-item', + method: 'get', + params: params + }) +} diff --git a/web-vue/src/api/role.js b/web-vue/src/api/role.js deleted file mode 100644 index 47b19f7fc5..0000000000 --- a/web-vue/src/api/role.js +++ /dev/null @@ -1,76 +0,0 @@ -// import axios from './config'; - -// // 角色列表 -// export function getRoleList() { -// return axios({ -// url: '/user/role/list_data.json', -// method: 'post' -// }) -// } - -// // 角色权限 -// export function getRoleFeature(id) { -// return axios({ -// url: '/user/role/getFeature.json', -// method: 'post', -// data: {id} -// }) -// } - -// // 编辑角色 -// export function editRole(params) { -// return axios({ -// url: '/user/role/save.json', -// method: 'post', -// data: params -// }) -// } - -// // 删除角色 -// export function deleteRole(id) { -// return axios({ -// url: '/user/role/del.json', -// method: 'post', -// data: {id} -// }) -// } - -// // 角色动态 -// export function getDynamicList() { -// return axios({ -// url: '/user/role/dynamic-list', -// method: 'get' -// }) -// } - -// /** -// * getDynamic -// * @param { -// * id: 角色 ID -// * dynamic: dynamic -// * } params -// */ -// export function getRoleDynamicList(params) { -// return axios({ -// url: '/user/role/getDynamic.json', -// method: 'post', -// timeout: 0, -// data: params -// }) -// } - -// /** -// * editRoleDynamic -// * @param { -// * id: 角色 ID -// * dynamic: dynamic -// * } params -// */ -// export function editRoleDynamic(params) { -// return axios({ -// url: '/user/role/saveDynamic.json', -// method: 'post', -// timeout: 0, -// data: params -// }) -// } \ No newline at end of file diff --git a/web-vue/src/api/server-script.ts b/web-vue/src/api/server-script.ts new file mode 100644 index 0000000000..5b08dd6f91 --- /dev/null +++ b/web-vue/src/api/server-script.ts @@ -0,0 +1,128 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from './config' + +/** + * script 服务端中的列表 + */ +export function getScriptList(params) { + return axios({ + url: '/script/list', + method: 'post', + data: params + }) +} + +/** + * 保存脚本 + * @param {Json} params + * @returns + */ +export function editScript(params) { + return axios({ + url: '/script/save.json', + method: 'post', + data: params + }) +} + +/** + * 删除 Script + * @param {id} params + + * params.id 编辑修改时判断 ID + */ +export function deleteScript(params) { + return axios({ + url: '/script/del.json', + method: 'post', + data: params + }) +} + +/** + * 解绑 Script + * @param {id} params + + * params.id 编辑修改时判断 ID + */ +export function unbindScript(params) { + return axios({ + url: '/script/unbind.json', + method: 'get', + params: params + }) +} + +// 脚本模版日志列表 +export function getScriptLogList(params) { + return axios({ + url: '/script_log/list', + method: 'post', + data: params + }) +} + +// 删除执行记录 +export function scriptDel(params) { + return axios({ + url: '/script_log/del_log', + method: 'post', + data: params + }) +} + +//执行记录 详情 +export function scriptLog(params) { + return axios({ + url: '/script_log/log', + method: 'post', + data: params, + headers: { + tip: 'no' + } + }) +} + +export function syncToWorkspace(params) { + return axios({ + url: '/script/sync-to-workspace', + method: 'get', + params: params + }) +} + +export function getScriptItem(params) { + return axios({ + url: '/script/get', + method: 'get', + params: params + }) +} + +/** + * 获取触发器地址 + * @param {*} id + */ +export function getTriggerUrl(data) { + return axios({ + url: '/script/trigger-url', + method: 'post', + data: data + }) +} + +export const triggerExecTypeMap = { + 0: t('i18n_2a3e7f5c38'), + 1: t('i18n_3aed2c11e9'), + 2: t('i18n_4696724ed3'), + 3: t('i18n_dba16b1b92') +} diff --git a/web-vue/src/api/ssh-file.ts b/web-vue/src/api/ssh-file.ts new file mode 100644 index 0000000000..6e39124b89 --- /dev/null +++ b/web-vue/src/api/ssh-file.ts @@ -0,0 +1,214 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' +import { loadRouterBase } from './config' + +/** + * 上传文件到 SSH 节点 + * @param { + * file: 文件 multipart/form-data, + * id: ssh ID, + * name: 当前目录, + * path: 父级目录 + * } formData + */ +export function uploadFile(baseUrl, formData) { + return axios({ + url: baseUrl + 'upload', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} + +/** + * 授权目录列表 + * @param {String} id + */ +export function getRootFileList(baseUrl, id) { + return axios({ + url: baseUrl + 'root_file_data.json', + method: 'post', + data: { id } + }) +} + +/** + * 文件列表 + * @param {id, path, children} params + */ +export function getFileList(baseUrl, params) { + return axios({ + url: baseUrl + 'list_file_data.json', + method: 'post', + data: params + }) +} + +/** + * 下载文件 + * 下载文件的返回是 blob 类型,把 blob 用浏览器下载下来 + * @param {id, path, name} params + */ +export function downloadFile(baseUrl, params) { + return loadRouterBase(baseUrl + 'download', params) +} + +/** + * 删除文件 + * @param {id, path, name} params x + */ +export function deleteFile(baseUrl, params) { + return axios({ + url: baseUrl + 'delete.json', + method: 'post', + data: params + }) +} + +/** + * 读取文件 + * @param {id, path, name} params x + */ +export function readFile(baseUrl, params) { + return axios({ + url: baseUrl + 'read_file_data.json', + method: 'post', + data: params + }) +} + +/** + * 保存文件 + * @param {id, path, name,content} params x + */ +export function updateFileData(baseUrl, params) { + return axios({ + url: baseUrl + 'update_file_data.json', + method: 'post', + data: params + }) +} + +/** + * 新增目录 或文件 + * @param params + * @returns {id, path, name,unFolder} params x + */ +export function newFileFolder(baseUrl, params) { + return axios({ + url: baseUrl + 'new_file_folder.json', + method: 'post', + data: params + }) +} + +/** + * 修改目录或文件名称 + * @param params + * @returns {id, levelName, filename,newname} params x + */ +export function renameFileFolder(baseUrl, params) { + return axios({ + url: baseUrl + 'rename.json', + method: 'post', + data: params + }) +} + +/** + * 修改文件权限 + * @param {*} baseUrl + * @param { + * String id, + * String allowPathParent, + * String nextPath, + * String fileName, + * String permissionValue + * } params + * @returns + */ +export function changeFilePermission(baseUrl, params) { + return axios({ + url: baseUrl + 'change_file_permission.json', + method: 'post', + data: params + }) +} + +/** + * 权限字符串转权限对象 + * @param {String} str "lrwxr-xr-x" + * @returns + */ +export function parsePermissions(str) { + const permissions = { owner: {}, group: {}, others: {} } + + const 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.others.read = chars[7] === 'r' + permissions.others.write = chars[8] === 'w' + permissions.others.execute = chars[9] === 'x' + + return permissions +} + +/** + * 文件权限字符串转权限值 + * @param { + * owner: { read: false, write: false, execute: false, }, + * group: { read: false, write: false, execute: false, }, + * others: { read: false, write: false, execute: false, }, + * } permissions + * @returns + */ +export function calcFilePermissionValue(permissions) { + let value = 0 + if (permissions.owner.read) { + value += 400 + } + if (permissions.owner.write) { + value += 200 + } + if (permissions.owner.execute) { + value += 100 + } + if (permissions.group.read) { + value += 40 + } + if (permissions.group.write) { + value += 20 + } + if (permissions.group.execute) { + value += 10 + } + if (permissions.others.read) { + value += 4 + } + if (permissions.others.write) { + value += 2 + } + if (permissions.others.execute) { + value += 1 + } + return value +} diff --git a/web-vue/src/api/ssh.js b/web-vue/src/api/ssh.js deleted file mode 100644 index 32e36e92f1..0000000000 --- a/web-vue/src/api/ssh.js +++ /dev/null @@ -1,198 +0,0 @@ -import axios from "./config"; - -// ssh 列表 -export function getSshList(params) { - return axios({ - url: "/node/ssh/list_data.json", - method: "post", - data: params, - }); -} - -// 检查 ssh 是否安装 插件端 -export function getSshCheckAgent(params) { - return axios({ - url: "/node/ssh/check_agent.json", - method: "get", - params: params, - timeout: 0, - headers: { - loading: "no", - }, - }); -} - -// 根据 nodeId 查询列表 -export function getSshListAll() { - return axios({ - url: "/node/ssh/list_data_all.json", - method: "get", - }); -} - -// ssh 操作日志列表 -export function getSshOperationLogList(params) { - return axios({ - url: "/node/ssh/log_list_data.json", - method: "post", - data: params, - }); -} - -/** - * 编辑 SSH - * @param {*} params - * params.type = {'add': 表示新增, 'edit': 表示修改} - */ -export function editSsh(params) { - const data = { - type: params.type, - id: params.id, - name: params.name, - host: params.host, - port: params.port, - user: params.user, - password: params.password, - connectType: params.connectType, - privateKey: params.privateKey, - charset: params.charset, - fileDirs: params.fileDirs, - notAllowedCommand: params.notAllowedCommand, - allowEditSuffix: params.allowEditSuffix, - }; - return axios({ - url: "/node/ssh/save.json", - method: "post", - // 0 表示无超时时间 - timeout: 0, - data, - }); -} - -// 删除 SSH -export function deleteSsh(id) { - return axios({ - url: "/node/ssh/del.json", - method: "post", - data: { id }, - }); -} - -/** - * 上传安装文件 - * @param { - * file: 文件 multipart/form-data, - * id: ssh ID, - * nodeData: 节点数据 json 字符串 `{"url":"121.42.160.109:2123","protocol":"http","id":"test","name":"tesst","path":"/seestech"}`, - * path: 文件保存的路径 - * } formData - */ -export function installAgentNode(formData) { - return axios({ - url: "/node/ssh/installAgentSubmit.json", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - method: "post", - // 0 表示无超时时间 - timeout: 0, - data: formData, - }); -} - -/** - * 上传文件到 SSH 节点 - * @param { - * file: 文件 multipart/form-data, - * id: ssh ID, - * name: 当前目录, - * path: 父级目录 - * } formData - */ -export function uploadFile(formData) { - return axios({ - url: "/node/ssh/upload", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - method: "post", - // 0 表示无超时时间 - timeout: 0, - data: formData, - }); -} - -/** - * 授权目录列表 - * @param {String} id - */ -export function getRootFileList(id) { - return axios({ - url: "/node/ssh/root_file_data.json", - method: "post", - data: { id }, - }); -} - -/** - * 文件列表 - * @param {id, path, children} params - */ -export function getFileList(params) { - return axios({ - url: "/node/ssh/list_file_data.json", - method: "post", - data: params, - }); -} - -/** - * 下载文件 - * 下载文件的返回是 blob 类型,把 blob 用浏览器下载下来 - * @param {id, path, name} params - */ -export function downloadFile(params) { - return axios({ - url: "/node/ssh/download.html", - method: "get", - responseType: "blob", - timeout: 0, - params, - }); -} - -/** - * 删除文件 - * @param {id, path, name} params x - */ -export function deleteFile(params) { - return axios({ - url: "/node/ssh/delete.json", - method: "post", - data: params, - }); -} - -/** - * 读取文件 - * @param {id, path, name} params x - */ -export function readFile(params) { - return axios({ - url: "/node/ssh/read_file_data.json", - method: "post", - data: params, - }); -} - -/** - * 保存文件 - * @param {id, path, name,content} params x - */ -export function updateFileData(params) { - return axios({ - url: "/node/ssh/update_file_data.json", - method: "post", - data: params, - }); -} diff --git a/web-vue/src/api/ssh.ts b/web-vue/src/api/ssh.ts new file mode 100644 index 0000000000..a4413fb2b7 --- /dev/null +++ b/web-vue/src/api/ssh.ts @@ -0,0 +1,102 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +// ssh 列表 +export function getSshList(params) { + return axios({ + url: '/node/ssh/list_data.json', + method: 'post', + data: params + }) +} + +// ssh group all +export function getSshGroupAll() { + return axios({ + url: '/node/ssh/list-group-all', + method: 'get' + }) +} + +// ssh list tree +export function getSshListTree() { + return axios({ + url: '/node/ssh/list-tree', + method: 'get' + }) +} + +// 查询单个 ssh +export function getItem(params) { + return axios({ + url: '/node/ssh/get-item.json', + method: 'get', + params: params + }) +} + +// 根据 nodeId 查询列表 +export function getSshListAll() { + return axios({ + url: '/node/ssh/list_data_all.json', + method: 'get' + }) +} + +// ssh 操作日志列表 +export function getSshOperationLogList(params) { + return axios({ + url: '/node/ssh/log_list_data.json', + method: 'post', + data: params + }) +} + +/** + * 编辑 SSH + * @param {*} params + * params.type = {'add': 表示新增, 'edit': 表示修改} + */ +export function editSsh(params) { + return axios({ + url: '/node/ssh/save.json', + method: 'post', + + params + }) +} + +// 删除 SSH +export function deleteSsh(id) { + return axios({ + url: '/node/ssh/del.json', + method: 'post', + data: { id } + }) +} + +// 删除 SSH +export function deleteForeSsh(id) { + return axios({ + url: '/node/ssh/del-fore', + method: 'post', + data: { id } + }) +} + +export function syncToWorkspace(params) { + return axios({ + url: '/node/ssh/sync-to-workspace', + method: 'get', + params: params + }) +} diff --git a/web-vue/src/api/system.js b/web-vue/src/api/system.js deleted file mode 100644 index 0b1c5f802a..0000000000 --- a/web-vue/src/api/system.js +++ /dev/null @@ -1,235 +0,0 @@ -import axios 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 axios({ - url: "/system/log_download", - method: "get", - responseType: "blob", - 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(nodeId) { - return axios({ - url: "/system/node_cache.json", - method: "post", - data: { nodeId }, - }); -} - -/** - * 清空缓存 - * @param { - * type: 类型 - * nodeId: 节点 ID - * } params - */ -export function clearCache(params) { - return axios({ - url: "/system/clearCache.json", - method: "post", - data: params, - }); -} - -/** - * 加载配置数据 - * @param {String} nodeId 节点 ID,若为空表示加载 Server 端配置 - */ -export function getConfigData(nodeId) { - return axios({ - url: "/system/config-data", - method: "post", - data: { nodeId }, - }); -} - -/** - * 加载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", - }); -} - -/** - * 编辑配置 - * @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(nodeId) { - return axios({ - url: "/system/info", - method: "post", - headers: { - tip: "no", - loading: "no", - }, - data: { nodeId }, - }); -} - -/** - * 上传升级文件 - * @param { - * file: 文件 multipart/form-data, - * nodeId: 节点 ID - * } formData - */ -export function uploadUpgradeFile(formData) { - return axios({ - url: "/system/uploadJar.json", - headers: { - "Content-Type": "multipart/form-data;charset=UTF-8", - }, - method: "post", - // 0 表示无超时时间 - timeout: 0, - data: formData, - }); -} - -/** - * 获取更新日志 - *@param {String} nodeId 节点 ID - */ -export function changelog(nodeId) { - return axios({ - url: "/system/change_log", - method: "post", - headers: {}, - data: { nodeId }, - }); -} - -/** - * 检查新版本 - *@param {String} nodeId 节点 ID - */ -export function checkVersion(nodeId) { - return axios({ - url: "/system/check_version.json", - method: "post", - headers: {}, - data: { nodeId }, - }); -} - -/** - * 远程升级 - *@param {String} nodeId 节点 ID - */ -export function remoteUpgrade(nodeId) { - return axios({ - url: "/system/remote_upgrade.json", - method: "get", - headers: {}, - data: { nodeId }, - }); -} diff --git a/web-vue/src/api/system.ts b/web-vue/src/api/system.ts new file mode 100644 index 0000000000..eaa4381495 --- /dev/null +++ b/web-vue/src/api/system.ts @@ -0,0 +1,331 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +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 asyncRefreshCache(params) { + return axios({ + url: '/system/async-refresh-cache', + method: 'get', + headers: {}, + 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.ts b/web-vue/src/api/system/assets-docker.ts new file mode 100644 index 0000000000..651378a2a5 --- /dev/null +++ b/web-vue/src/api/system/assets-docker.ts @@ -0,0 +1,150 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from '@/api/config' + +/** + * 容器列表 + * @param {JSON} params + */ +export function dockerList(params) { + return axios({ + url: '/system/assets/docker/list-data', + method: 'post', + data: params + }) +} + +export function dockerImportTls(formData) { + return axios({ + url: '/system/assets/docker/import-tls', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + timeout: 0, + data: formData + }) +} + +export function editDocker(data) { + return axios({ + url: '/system/assets/docker/edit', + method: 'post', + data: data + }) +} + +/** + * 自动探测 docker + * @param { + * + * } params + */ +export function tryLocalDocker(params) { + return axios({ + url: '/system/assets/docker/try-local-docker', + method: 'get', + params + }) +} + +/** + * 删除 docker + * @param { + * id: docker ID + * } params + */ +export function deleteDcoker(params) { + return axios({ + url: '/system/assets/docker/del', + method: 'get', + params + }) +} + +export function dockerListGroup(params) { + return axios({ + url: '/system/assets/docker/list-group', + method: 'get', + params: params + }) +} + +export function initDockerSwarm(data) { + return axios({ + url: '/system/assets/docker/init', + method: 'post', + data: data + }) +} + +export function joinDockerSwarm(data) { + return axios({ + url: '/system/assets/docker/join', + method: 'post', + data: data + }) +} + +export function dockerSwarmListAll(params) { + return axios({ + url: '/system/assets/docker/swarm/list-all', + method: 'get', + params: params + }) +} + +/** + * 强制退出集群 + * @param { + * id: docker ID + * } params + */ +export function dcokerSwarmLeaveForce(params) { + return axios({ + url: '/system/assets/docker/leave-force', + method: 'get', + params + }) +} + +/** + * 容器集群节点剔除 + * @param {JSON} params + */ +export function dockerSwarmNodeLeave(params) { + return axios({ + url: '/system/assets/docker/leave-node', + method: 'get', + params: params + }) +} + +export function machineDockerDistribute(params) { + return axios({ + url: '/system/assets/docker/distribute', + method: 'post', + data: params + }) +} + +export function dockerListWorkspace(params) { + return axios({ + url: '/system/assets/docker/list-workspace-docker', + method: 'get', + params: params + }) +} + +export const statusMap = { + 0: { desc: t('i18n_757a730c9e'), color: 'red' }, + 1: { desc: t('i18n_0f0a5f6107'), color: 'green' } +} diff --git a/web-vue/src/api/system/assets-machine.ts b/web-vue/src/api/system/assets-machine.ts new file mode 100644 index 0000000000..5f4569067d --- /dev/null +++ b/web-vue/src/api/system/assets-machine.ts @@ -0,0 +1,134 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from '@/api/config' + +// 机器 列表 +export function machineListData(params) { + return axios({ + url: '/system/assets/machine/list-data', + method: 'post', + data: params + }) +} + +export function machineListGroup(params) { + return axios({ + url: '/system/assets/machine/list-group', + method: 'get', + params: params + }) +} + +// 编辑机器 +export function machineEdit(params) { + return axios({ + url: '/system/assets/machine/edit', + method: 'post', + data: params + }) +} + +// 删除机器 +export function machineDelete(params) { + return axios({ + url: '/system/assets/machine/delete', + method: 'post', + data: params + }) +} + +// 分配机器 +export function machineDistribute(params) { + return axios({ + url: '/system/assets/machine/distribute', + method: 'post', + data: params + }) +} + +export const statusMap = { + 0: t('i18n_757a730c9e'), + 1: t('i18n_fd6e80f1e0'), + 2: t('i18n_c18455fbe3'), + 3: t('i18n_c5bbaed670'), + 4: t('i18n_a14da34559') +} + +// 查看机器关联节点 +export function machineListNode(params) { + return axios({ + url: '/system/assets/machine/list-node', + method: 'get', + params: params + }) +} + +export function machineListTemplateNode(params) { + return axios({ + url: '/system/assets/machine/list-template-node', + method: 'get', + params: params + }) +} + +/** + * 保存 授权配置 + */ +export function saveWhitelist(data) { + return axios({ + url: '/system/assets/machine/save-whitelist', + method: 'post', + data: data + }) +} + +/** + * 保存 节点系统配置 + */ +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 + }) +} + +export function machineMonitorConfig(data) { + return axios({ + url: '/system/assets/machine/monitor-config', + method: 'get', + params: data + }) +} + +export function machineSearch(data) { + return axios({ + url: '/system/assets/machine/search', + method: 'get', + params: data + }) +} diff --git a/web-vue/src/api/system/assets-ssh.ts b/web-vue/src/api/system/assets-ssh.ts new file mode 100644 index 0000000000..a86dcf8180 --- /dev/null +++ b/web-vue/src/api/system/assets-ssh.ts @@ -0,0 +1,129 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from '@/api/config' +import { loadRouterBase } from '@/api/config' + +// ssh 列表 +export function machineSshListData(params) { + return axios({ + url: '/system/assets/ssh/list-data', + method: 'post', + data: params + }) +} + +export function machineSshListGroup(params) { + return axios({ + url: '/system/assets/ssh/list-group', + method: 'get', + params: params + }) +} + +// 编辑ssh +export function machineSshEdit(params) { + return axios({ + url: '/system/assets/ssh/edit', + method: 'post', + data: params + }) +} + +// 删除 ssh +export function machineSshDelete(params) { + return axios({ + url: '/system/assets/ssh/delete', + method: 'post', + data: params + }) +} + +// 分配 ssh +export function machineSshDistribute(params) { + return axios({ + url: '/system/assets/ssh/distribute', + method: 'post', + data: params + }) +} + +// ssh 操作日志列表 +export function getMachineSshOperationLogList(params) { + return axios({ + url: '/system/assets/ssh/log-list-data', + method: 'post', + data: params + }) +} + +// ssh 关联工作空间的数据 +export function machineListGroupWorkspaceSsh(params) { + return axios({ + url: '/system/assets/ssh/list-workspace-ssh', + method: 'get', + params: params + }) +} + +export function machineSshSaveWorkspaceConfig(params) { + return axios({ + url: '/system/assets/ssh/save-workspace-config', + method: 'post', + data: params + }) +} + +/** + * restHideField by id + * @param {String} id + * @returns + */ +export function restHideField(id) { + return axios({ + url: '/system/assets/ssh/rest-hide-field', + method: 'post', + data: { id } + }) +} +/* + * 下载导入模板 + * + */ +export function importTemplate(data) { + return loadRouterBase('/system/assets/ssh/import-template', data) +} + +/* + * 导出数据 + * + */ +export function exportData(data) { + return loadRouterBase('/system/assets/ssh/export-data', data) +} +// 导入数据 +export function importData(formData) { + return axios({ + url: '/system/assets/ssh/import-data', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} + +export const statusMap = { + 0: { desc: t('i18n_757a730c9e'), color: 'red' }, + 1: { desc: t('i18n_fd6e80f1e0'), color: 'green' }, + 2: { desc: t('i18n_46158d0d6e'), color: 'orange' } +} diff --git a/web-vue/src/api/system/cluster.ts b/web-vue/src/api/system/cluster.ts new file mode 100644 index 0000000000..b07e2e2d33 --- /dev/null +++ b/web-vue/src/api/system/cluster.ts @@ -0,0 +1,66 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from '@/api/config' + +/* + * 集群列表 + * @param {*} + * } params + */ +export function getClusterList(params) { + return axios({ + url: '/cluster/list', + method: 'post', + data: params + }) +} + +/* + * 删除集群 + * @param {String} id + * } params + */ +export function deleteCluster(id) { + return axios({ + url: '/cluster/delete', + method: 'get', + params: { id: id } + }) +} + +/* + * 删除所有可用分组 + * @param {} * + * } params + */ +export function listLinkGroups(params) { + return axios({ + url: '/cluster/list-link-groups', + method: 'get', + params: params + }) +} + +export function listClusterAll(params) { + return axios({ + url: '/cluster/list-all', + method: 'get', + params: params + }) +} + +export function editCluster(params) { + return axios({ + url: '/cluster/edit', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/system/script-library.ts b/web-vue/src/api/system/script-library.ts new file mode 100644 index 0000000000..835fbe947d --- /dev/null +++ b/web-vue/src/api/system/script-library.ts @@ -0,0 +1,25 @@ +import axios from '@/api/config' + +export function getScriptLibraryList(params: any) { + return axios({ + url: '/system/assets/script-library/list-data', + method: 'post', + data: params + }) +} + +export function editScriptLibrary(params: any) { + return axios({ + url: '/system/assets/script-library/edit', + method: 'post', + data: params + }) +} + +export function delScriptLibrary(params: any) { + return axios({ + url: '/system/assets/script-library/del', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/tools.ts b/web-vue/src/api/tools.ts new file mode 100644 index 0000000000..3c48a6ae03 --- /dev/null +++ b/web-vue/src/api/tools.ts @@ -0,0 +1,47 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +/** + * + * @param data + */ +export function cronTools(data) { + return axios({ + url: '/tools/cron', + method: 'get', + params: data + }) +} + +export function ipList(data) { + return axios({ + url: '/tools/ip-list', + method: 'get', + params: data + }) +} + +export function netPing(data) { + return axios({ + url: '/tools/net-ping', + method: 'get', + params: data + }) +} + +export function netTelnet(data) { + return axios({ + url: '/tools/net-telnet', + method: 'get', + params: data + }) +} diff --git a/web-vue/src/api/tools/certificate.ts b/web-vue/src/api/tools/certificate.ts new file mode 100644 index 0000000000..248605dadf --- /dev/null +++ b/web-vue/src/api/tools/certificate.ts @@ -0,0 +1,88 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from '@/api/config' +import { loadRouterBase } from '@/api/config' +// 导入证书 +export function certificateImportFile(formData) { + return axios({ + url: '/certificate/import-file', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + + data: formData + }) +} + +/** + * cert 列表 + */ +export function certList(params) { + return axios({ + url: '/certificate/list', + method: 'post', + data: params + }) +} + +/** + * cert 列表 + */ +export function certListAll(params) { + return axios({ + url: '/certificate/list-all', + method: 'post', + data: params + }) +} + +/** + * 删除 cert + * @param { + * + * } params + */ +export function deleteCert(params) { + return axios({ + url: '/certificate/del', + method: 'get', + params: params + }) +} + +/** + * 导出 cert + * @param { + * + * } params + */ +export function downloadCert(params) { + return loadRouterBase('/certificate/export', params) +} + +// 修改证书 +export function certificateEdit(params) { + return axios({ + url: '/certificate/edit', + method: 'post', + data: params + }) +} + +// 部署证书 +export function certificateDeploy(params) { + return axios({ + url: '/certificate/deploy', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/trigger-token.ts b/web-vue/src/api/trigger-token.ts new file mode 100644 index 0000000000..84f110111f --- /dev/null +++ b/web-vue/src/api/trigger-token.ts @@ -0,0 +1,35 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +export function triggerTokenList(data) { + return axios({ + url: '/system/trigger/list', + method: 'post', + data: data + }) +} + +export function triggerTokenAllType(data) { + return axios({ + url: '/system/trigger/all-type', + method: 'get', + params: data + }) +} + +export function triggerTokenDelete(data) { + return axios({ + url: '/system/trigger/delete', + method: 'get', + params: data + }) +} diff --git a/web-vue/src/api/user.js b/web-vue/src/api/user.js deleted file mode 100644 index c41947e20b..0000000000 --- a/web-vue/src/api/user.js +++ /dev/null @@ -1,158 +0,0 @@ -import axios from "./config"; - -// login -export function login(params) { - return axios({ - url: "/userLogin", - method: "post", - data: params, - }); -} - -// refresh token -export function refreshToken() { - return axios({ - url: "/renewal", - method: "post", - }); -} - -// 获取用户信息 -export function getUserInfo() { - return axios({ - url: "/user/user-basic-info", - method: "post", - }); -} - -// 退出登录 -export function loginOut(params) { - return axios({ - url: "/logout2", - method: "get", - data: params, - }); -} - -// 修改密码 -export function updatePwd(params) { - return axios({ - url: "/user/updatePwd", - method: "post", - data: params, - }); -} - -// 所有管理员列表 -export function getUserListAll() { - return axios({ - url: "/user/get_user_list_all", - method: "post", - }); -} - -// 用户列表 -export function getUserList(params) { - return axios({ - url: "/user/get_user_list", - method: "post", - data: params, - }); -} - -// 编辑 -export function editUser(params) { - return axios({ - url: "/user/edit", - method: "post", - data: params, - }); -} - -// // 修改用户 -// export function updateUser(params) { -// return axios({ -// url: '/user/updateUser', -// method: 'post', -// data: params -// }) -// } - -// 删除用户 -export function deleteUser(id) { - return axios({ - url: "/user/deleteUser", - method: "post", - data: { id }, - }); -} - -/** - * 编辑用户资料 - * @param { - * token: token, - * email: 邮箱地址, - * code: 邮箱验证码 - * dingDing: 钉钉群通知地址, - * workWx: 企业微信群通知地址 - * } params - */ -export function editUserInfo(params) { - return axios({ - url: "/user/save_basicInfo.json", - method: "post", - data: params, - }); -} - -/** - * 发送邮箱验证码 - * @param {String} email 邮箱地址 - */ -export function sendEmailCode(email) { - return axios({ - url: "/user/sendCode.json", - method: "post", - timeout: 0, - data: { email }, - }); -} - -/** - * 解锁管理员 - * @param {String} id 管理员 ID - * @returns - */ -export function unlockUser(id) { - return axios({ - url: "/user/unlock", - method: "post", - data: { id }, - }); -} - -/** - * 用户的工作空间列表 - * @param {String} userId 管理员 ID - * @returns - */ -export function workspaceList(userId) { - return axios({ - url: "/user/workspace_list", - method: "get", - params: { userId: userId }, - }); -} - -/** - * 我的工作空间 - * - * @returns - */ -export function myWorkspace() { - return axios({ - url: "/user/my_workspace", - method: "get", - params: {}, - }); -} diff --git a/web-vue/src/api/user/user-login-log.ts b/web-vue/src/api/user/user-login-log.ts new file mode 100644 index 0000000000..a1f31f6756 --- /dev/null +++ b/web-vue/src/api/user/user-login-log.ts @@ -0,0 +1,29 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import axios from '../config' + +export function userLoginLgin(params) { + return axios({ + url: '/user/login-log/list-data', + method: 'post', + data: params + }) +} + +export const operateCodeMap = { + 0: t('i18n_dd95bf2d45'), + 1: t('i18n_5a5368cf9b'), + 2: t('i18n_18d49918f5'), + 3: t('i18n_a093ae6a6e'), + 4: t('i18n_8b63640eee'), + 5: t('i18n_bb9a581f48'), + 6: 'oauth2' +} diff --git a/web-vue/src/api/user/user-notification.ts b/web-vue/src/api/user/user-notification.ts new file mode 100644 index 0000000000..ec2bc0a394 --- /dev/null +++ b/web-vue/src/api/user/user-notification.ts @@ -0,0 +1,43 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from '../config' + +export type UserNotificationType = { + level?: 'error' | 'success' | 'warning' | 'info' | undefined + closable?: boolean + title?: string + content?: string + enabled?: boolean +} + +// 编辑 +export function saveUserNotification(params: UserNotificationType) { + return axios({ + url: '/user/notification/save', + method: 'post', + data: params + }) +} + +// 获取通知 +export function getUserNotification() { + return axios({ + url: '/user/notification/get', + method: 'get' + }) +} + +export function systemNotification() { + return axios({ + url: '/system-notification', + method: 'get' + }) +} diff --git a/web-vue/src/api/user/user-permission.ts b/web-vue/src/api/user/user-permission.ts new file mode 100644 index 0000000000..833c573d1d --- /dev/null +++ b/web-vue/src/api/user/user-permission.ts @@ -0,0 +1,46 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from '../config' + +// 权限组列表 +export function getList(params) { + return axios({ + url: '/user-permission-group/get-list', + method: 'post', + data: params + }) +} + +// 编辑 +export function editPermissionGroup(params) { + return axios({ + url: '/user-permission-group/edit', + method: 'post', + data: params + }) +} + +// 所有列表 +export function getUserPermissionListAll() { + return axios({ + url: '/user-permission-group/get-list-all', + method: 'get' + }) +} + +// 删除 +export function deletePermissionGroup(id) { + return axios({ + url: '/user-permission-group/delete', + method: 'get', + params: { id } + }) +} diff --git a/web-vue/src/api/user/user.ts b/web-vue/src/api/user/user.ts new file mode 100644 index 0000000000..0aae8c4724 --- /dev/null +++ b/web-vue/src/api/user/user.ts @@ -0,0 +1,349 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from '../config' + +// login +export function login(params) { + return axios({ + url: '/userLogin', + method: 'post', + data: params + }) +} + +// oauth2Login +export function oauth2Login(params) { + return axios({ + url: '/oauth2/login', + method: 'post', + data: params + }) +} + +export function oauth2Url(params) { + return axios({ + url: '/oauth2-url', + method: 'get', + params: params + }) +} + +/** + * 验证输入的验证码 + * @param {JSON} params + * @returns + */ +export function mfaVerify(params) { + return axios({ + url: '/mfa_verify', + method: 'get', + params: params + }) +} + +// refresh token +export function refreshToken() { + return axios({ + url: '/renewal', + method: 'post' + }) +} + +// 关闭 两步验证信息 +export function closeMfa(params) { + return axios({ + url: '/user/close_mfa', + method: 'get', + params + }) +} + +// 生成 两步验证信息 +export function generateMfa() { + return axios({ + url: '/user/generate_mfa', + method: 'get' + }) +} + +/** + * 绑定 mfa + * @param {JSON} params + * @returns + */ +export function bindMfa(params) { + return axios({ + url: '/user/bind_mfa', + method: 'get', + params: params + }) +} + +// 获取用户信息 +export function getUserInfo() { + return axios({ + url: '/user/user-basic-info', + method: 'post' + }) +} + +// 退出登录 +export function loginOut(params) { + return axios({ + url: '/logout2', + method: 'get', + data: params + }) +} + +// 修改密码 +export function updatePwd(params) { + return axios({ + url: '/user/updatePwd', + method: 'post', + data: params + }) +} + +// 所有管理员列表 +export function getUserListAll() { + return axios({ + url: '/user/get_user_list_all', + method: 'post' + }) +} + +// 用户列表 +export function getUserList(params) { + return axios({ + url: '/user/get_user_list', + method: 'post', + data: params + }) +} + +// 编辑 +export function editUser(params) { + return axios({ + url: '/user/edit', + method: 'post', + data: params + }) +} + +// // 修改用户 +// export function updateUser(params) { +// return axios({ +// url: '/user/updateUser', +// method: 'post', +// data: params +// }) +// } + +// 删除用户 +export function deleteUser(id) { + return axios({ + url: '/user/deleteUser', + method: 'post', + data: { id } + }) +} + +/** + * 编辑用户资料 + * @param { + * token: token, + * email: 邮箱地址, + * code: 邮箱验证码 + * dingDing: 钉钉群通知地址, + * workWx: 企业微信群通知地址 + * } params + */ +export function editUserInfo(params) { + return axios({ + url: '/user/save_basicInfo.json', + method: 'post', + data: params + }) +} + +/** + * 发送邮箱验证码 + * @param {String} email 邮箱地址 + */ +export function sendEmailCode(email) { + return axios({ + url: '/user/sendCode.json', + method: 'post', + timeout: 0, + data: { email } + }) +} + +/** + * 解锁管理员 + * @param {String} id 管理员 ID + * @returns + */ +export function unlockUser(id) { + return axios({ + url: '/user/unlock', + method: 'get', + params: { id } + }) +} + +/** + * 关闭用户 mfa 两步验证 + * @param {String} id 管理员 ID + * @returns + */ +export function closeUserMfa(id) { + return axios({ + url: '/user/close_user_mfa', + method: 'get', + params: { id } + }) +} + +/** + * 重置用户密码 + * @param {String} id 管理员 ID + * @returns + */ +export function restUserPwd(id) { + return axios({ + url: '/user/rest-user-pwd', + method: 'get', + params: { id } + }) +} + +/** + * 用户的工作空间列表 + * @param {String} userId 管理员 ID + * @returns + */ +export function workspaceList(userId) { + return axios({ + url: '/user/workspace_list', + method: 'get', + params: { userId: userId } + }) +} + +/** + * 我的工作空间 + * + * @returns + */ +export function myWorkspace() { + return axios({ + url: '/user/my-workspace', + method: 'get', + params: {} + }) +} + +export function statWorkspace() { + return axios({ + url: '/stat/workspace', + method: 'get', + params: {} + }) +} + +export function statSystemOverview() { + return axios({ + url: '/stat/system', + method: 'get', + params: {} + }) +} + +/** + * 我的集群 + * + * @returns + */ +export function clusterList() { + return axios({ + url: '/user/cluster-list', + method: 'get', + params: {} + }) +} + +/** + * 保存我的工作空间 + * + * @returns + */ +export function saveWorkspace(data) { + return axios({ + url: '/user/save-workspace', + method: 'post', + data, + headers: { + 'Content-Type': 'application/json' + } + }) +} + +/** + * 登录页面 信息 + * + * @returns + */ +export function loginConfig() { + return axios({ + url: '/login-config', + method: 'get', + params: {} + }) +} + +/** + * 登录验证码 + * + * @returns + */ +export function loginRandCode(params) { + return axios({ + url: '/rand-code', + method: 'get', + params: params + }) +} + +export function listLoginLog(params) { + return axios({ + url: '/user/list-login-log-data', + method: 'post', + data: params + }) +} + +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.js deleted file mode 100644 index 5101cd3e03..0000000000 --- a/web-vue/src/api/workspace.js +++ /dev/null @@ -1,90 +0,0 @@ -import axios from "./config"; - -/** - * - * @param data - */ -export function editWorkSpace(data) { - return axios({ - url: "/system/workspace/edit", - method: "post", - data: data, - }); -} - -/* - * 工作空间列表 - * @param {*} - * } params - */ -export function getWorkSpaceList(params) { - return axios({ - url: "/system/workspace/list", - method: "post", - data: params, - }); -} - -/* - * 工作空间列表(查询所有) - * @param {*} - * } params - */ -export function getWorkSpaceListAll() { - return axios({ - url: "/system/workspace/list_all", - method: "get", - data: {}, - }); -} - -/* - * 删除工作空间 - * @param {String} id - * } params - */ -export function deleteWorkspace(id) { - return axios({ - url: "/system/workspace/delete", - method: "get", - params: { id: id }, - }); -} - -/* - * 工作空间环境变量列表 - * @param {*} - * } params - */ -export function getWorkspaceEnvList(params) { - return axios({ - url: "/system/workspace_env/list", - method: "post", - data: params, - }); -} - -/** - * - * @param data - */ -export function editWorkspaceEnv(data) { - return axios({ - url: "/system/workspace_env/edit", - method: "post", - data: data, - }); -} - -/* - * 删除工作空间变量 - * @param {String} id - * } params - */ -export function deleteWorkspaceEnv(id) { - return axios({ - url: "/system/workspace_env/delete", - method: "get", - params: { id: id }, - }); -} diff --git a/web-vue/src/api/workspace.ts b/web-vue/src/api/workspace.ts new file mode 100644 index 0000000000..86f8f466e2 --- /dev/null +++ b/web-vue/src/api/workspace.ts @@ -0,0 +1,168 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import axios from './config' + +/** + * + * @param data + */ +export function editWorkSpace(data) { + return axios({ + url: '/system/workspace/edit', + method: 'post', + data: data + }) +} + +/* + * 工作空间列表 + * @param {*} + * } params + */ +export function getWorkSpaceList(params) { + return axios({ + url: '/system/workspace/list', + method: 'post', + data: params + }) +} + +/* + * 工作空间分组列表 + * @param {*} + * } params + */ +export function getWorkSpaceGroupList(params) { + return axios({ + url: '/system/workspace/list-group-all', + method: 'get', + params: params + }) +} + +/* + * 工作空间列表(查询所有) + * @param {*} + * } params + */ +export function getWorkSpaceListAll() { + return axios({ + url: '/system/workspace/list_all', + method: 'get', + data: {} + }) +} + +/* + * 删除工作空间 + * @param {String} id + * } params + */ +export function deleteWorkspace(id) { + return axios({ + url: '/system/workspace/delete', + method: 'get', + params: { id: id } + }) +} + +/* + * 删除工作空间检查 + * @param {String} id + * } params + */ +export function preDeleteWorkspace(id) { + return axios({ + url: '/system/workspace/pre-check-delete', + method: 'get', + params: { id: id } + }) +} + +/* + * 工作空间环境变量列表 + * @param {*} + * } params + */ +export function getWorkspaceEnvList(params) { + return axios({ + url: '/system/workspace_env/list', + method: 'post', + data: params + }) +} +/* + * 工作空间环境变量全部列表 + * @param {*} + * } params + */ +export function getWorkspaceEnvAll(data) { + return axios({ + url: '/system/workspace_env/all', + method: 'post', + data + }) +} + +/** + * + * @param data + */ +export function editWorkspaceEnv(data) { + return axios({ + url: '/system/workspace_env/edit', + method: 'post', + data: data + }) +} + +/* + * 删除工作空间变量 + * @param {String} id + * } params + */ +export function deleteWorkspaceEnv(params) { + return axios({ + url: '/system/workspace_env/delete', + method: 'get', + params: params + }) +} + +export function getTriggerUrlWorkspaceEnv(params) { + return axios({ + url: '/system/workspace_env/trigger-url', + method: 'post', + params: params + }) +} + +/** + * 加载 菜单配置信息 + */ +export function getMenusConfig(data) { + return axios({ + url: '/system/workspace/get_menus_config', + method: 'post', + data + }) +} + +/** + * 保存菜单配置信息 + */ +export function saveMenusConfig(data) { + return axios({ + url: '/system/workspace/save_menus_config.json', + method: 'post', + data: data + }) +} diff --git a/web-vue/src/assets/images/bg.jpeg b/web-vue/src/assets/images/bg.jpeg deleted file mode 100644 index 9097eb8d00..0000000000 Binary files a/web-vue/src/assets/images/bg.jpeg and /dev/null differ diff --git a/web-vue/src/assets/images/dingtalk.svg b/web-vue/src/assets/images/dingtalk.svg new file mode 100644 index 0000000000..ad2ccee5fc --- /dev/null +++ b/web-vue/src/assets/images/dingtalk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-vue/src/assets/images/feishu.svg b/web-vue/src/assets/images/feishu.svg new file mode 100644 index 0000000000..1590ada1c7 --- /dev/null +++ b/web-vue/src/assets/images/feishu.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/web-vue/src/assets/images/gitee.svg b/web-vue/src/assets/images/gitee.svg new file mode 100644 index 0000000000..5bc5c28f39 --- /dev/null +++ b/web-vue/src/assets/images/gitee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-vue/src/assets/images/github.svg b/web-vue/src/assets/images/github.svg new file mode 100644 index 0000000000..e693cd34cd --- /dev/null +++ b/web-vue/src/assets/images/github.svg @@ -0,0 +1 @@ + diff --git a/web-vue/src/assets/images/gitlab.svg b/web-vue/src/assets/images/gitlab.svg new file mode 100644 index 0000000000..5877e1df7f --- /dev/null +++ b/web-vue/src/assets/images/gitlab.svg @@ -0,0 +1,7 @@ + + diff --git a/web-vue/src/assets/images/jpom.svg b/web-vue/src/assets/images/jpom.svg new file mode 100644 index 0000000000..dfb02ab3fb --- /dev/null +++ b/web-vue/src/assets/images/jpom.svg @@ -0,0 +1,25 @@ + + + jpom logo + + + + + + + + + + + + + + diff --git a/web-vue/src/assets/images/maxkey.png b/web-vue/src/assets/images/maxkey.png new file mode 100644 index 0000000000..7134e1ddca Binary files /dev/null and b/web-vue/src/assets/images/maxkey.png differ diff --git a/web-vue/src/assets/images/oauth2.png b/web-vue/src/assets/images/oauth2.png new file mode 100644 index 0000000000..41a8d35aa8 Binary files /dev/null and b/web-vue/src/assets/images/oauth2.png differ diff --git a/web-vue/src/assets/images/qrcode/alipay-praise.jpg b/web-vue/src/assets/images/qrcode/alipay-praise.jpg new file mode 100644 index 0000000000..c78d52e6db Binary files /dev/null and b/web-vue/src/assets/images/qrcode/alipay-praise.jpg differ diff --git a/web-vue/src/assets/images/qrcode/alipay-small.png b/web-vue/src/assets/images/qrcode/alipay-small.png new file mode 100644 index 0000000000..d7013e092f Binary files /dev/null and b/web-vue/src/assets/images/qrcode/alipay-small.png differ diff --git a/web-vue/src/assets/images/qrcode/weix-qrcode-jpom66.jpg b/web-vue/src/assets/images/qrcode/weix-qrcode-jpom66.jpg new file mode 100644 index 0000000000..cbac34b011 Binary files /dev/null and b/web-vue/src/assets/images/qrcode/weix-qrcode-jpom66.jpg differ diff --git a/web-vue/src/assets/images/qrcode/weixin-praise.jpg b/web-vue/src/assets/images/qrcode/weixin-praise.jpg new file mode 100644 index 0000000000..dd6c2ccdeb Binary files /dev/null and b/web-vue/src/assets/images/qrcode/weixin-praise.jpg differ diff --git a/web-vue/src/assets/images/qrcode/weixin-small.png b/web-vue/src/assets/images/qrcode/weixin-small.png new file mode 100644 index 0000000000..726637f26a Binary files /dev/null and b/web-vue/src/assets/images/qrcode/weixin-small.png differ diff --git a/web-vue/src/assets/images/qyweixin.svg b/web-vue/src/assets/images/qyweixin.svg new file mode 100644 index 0000000000..221160388a --- /dev/null +++ b/web-vue/src/assets/images/qyweixin.svg @@ -0,0 +1 @@ + diff --git a/web-vue/src/assets/images/weixin.svg b/web-vue/src/assets/images/weixin.svg new file mode 100644 index 0000000000..36856831e3 --- /dev/null +++ b/web-vue/src/assets/images/weixin.svg @@ -0,0 +1,2 @@ + + 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 c57fbb0f65..0000000000 --- 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.css b/web-vue/src/assets/reset.css deleted file mode 100644 index 307619a7f1..0000000000 --- a/web-vue/src/assets/reset.css +++ /dev/null @@ -1,27 +0,0 @@ -/* 全局样式,重置样式 */ -html, -body { - width: 100vw; - height: 100vh; - margin: 0; - padding: 0; -} - -.ant-drawer-body { - padding: 10px; -} - -.ant-table-wrapper { - overflow-y: auto; - overflow-x: auto; -} - -.ant-table-body { - overflow-x: auto !important; -} - - -.search-input-item { - width: 140px; - margin-right: 10px; -} diff --git a/web-vue/src/assets/reset.less b/web-vue/src/assets/reset.less new file mode 100644 index 0000000000..7126e6d217 --- /dev/null +++ b/web-vue/src/assets/reset.less @@ -0,0 +1,104 @@ +/* 全局样式,重置样式 */ +html, +body { + // width: 100vw; + // height: 100vh; + margin: 0; + padding: 0; +} + +.ant-drawer-body { + padding: 10px; +} + +.ant-table-wrapper { + overflow-y: auto; + overflow-x: auto; +} + +.ant-table-body { + overflow-x: auto !important; +} + +.search-input-item { + width: 140px; + /* margin-right: 10px; */ +} + +.filter { + margin-bottom: 10px; +} +/* +.ant-btn { + margin-right: 10px; +} */ + +.ant-btn-sm { + font-size: 12px; +} + +pre { + margin: 0; +} + +.ant-modal-confirm-body .ant-modal-confirm-content { + font-size: 16px; +} + +.ant-modal-confirm-body .ant-modal-confirm-title { + font-size: 18px; + font-weight: bold; +} + +.hide-scrollbar *::-webkit-scrollbar { + width: 0 !important; + display: none; +} + +.hide-scrollbar * { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.hide-scrollbar pre::-webkit-scrollbar { + width: 0 !important; + display: none; +} + +.hide-scrollbar pre { + -ms-overflow-style: none; + scrollbar-width: none; +} + +@color-border-last: rgba(140, 140, 140, 0.3); +@color-neutral-last: rgba(140, 140, 140, 0); +@scrollbar-size: 4px; + +@-moz-document url-prefix() { + /* Firefox 专属样式 */ + // 兼容火狐 + * { + scrollbar-width: thin; + scrollbar-color: @color-border-last @color-neutral-last; + } +} + +// 滚动条样式 +*::-webkit-scrollbar { + width: @scrollbar-size; + height: @scrollbar-size; + border-radius: 2px; + background-color: transparent; +} +// 滚动条-活动按钮 +*::-webkit-scrollbar-thumb { + background: @color-border-last; + border-radius: 2px; + box-shadow: inset 0 0 6px @color-border-last; +} +// 滚动条背景 +*::-webkit-scrollbar-track { + background-color: @color-neutral-last; + border-radius: 2px; + box-shadow: inset 0 0 6px @color-neutral-last; +} diff --git a/web-vue/src/assets/style.less b/web-vue/src/assets/style.less new file mode 100644 index 0000000000..075cd520f9 --- /dev/null +++ b/web-vue/src/assets/style.less @@ -0,0 +1,16 @@ +.search-box { + margin-bottom: 0 !important; + box-sizing: border-box; + padding: 8px 0; +} + +.text-overflow-hidden { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// 国际化时表单换行 +.ant-form-item .ant-form-item-label > label { + white-space: normal; +} diff --git a/web-vue/src/components/Icon.ts b/web-vue/src/components/Icon.ts new file mode 100644 index 0000000000..4b617ab373 --- /dev/null +++ b/web-vue/src/components/Icon.ts @@ -0,0 +1,58 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import { createVNode } from 'vue' +import { + FileOutlined, + SettingOutlined, + FileTextOutlined, + CloudServerOutlined, + UserOutlined, + DesktopOutlined, + ToolOutlined, + MonitorOutlined, + CodeOutlined, + BuildOutlined, + SaveOutlined, + HddOutlined, + ApartmentOutlined, + DashboardOutlined, + ProjectOutlined, + GatewayOutlined, + LaptopOutlined +} from '@ant-design/icons-vue' + +const iconObj = { + file: FileOutlined, + desktop: DesktopOutlined, + setting: SettingOutlined, + hdd: HddOutlined, + save: SaveOutlined, + user: UserOutlined, + apartment: ApartmentOutlined, + build: BuildOutlined, + code: CodeOutlined, + 'file-text': FileTextOutlined, + 'cloud-server': CloudServerOutlined, + monitor: MonitorOutlined, + tool: ToolOutlined, + dashboard: DashboardOutlined, + project: ProjectOutlined, + gateway: GatewayOutlined, + laptop: LaptopOutlined +} + +const Icon = (props: { type: string }) => { + const { type } = props + // @ts-ignore + return createVNode(iconObj[type]) +} + +export default Icon diff --git a/web-vue/src/components/codeEditor/codemirror.js b/web-vue/src/components/codeEditor/codemirror.js deleted file mode 100644 index 9a7e53d426..0000000000 --- a/web-vue/src/components/codeEditor/codemirror.js +++ /dev/null @@ -1,50 +0,0 @@ -import Vue from "vue"; -import { codemirror } from "vue-codemirror"; -import "codemirror/lib/codemirror.css"; - -Vue.use(codemirror); -import "codemirror/theme/blackboard.css"; -import "codemirror/theme/eclipse.css"; -import "codemirror/mode/javascript/javascript.js"; -import "codemirror/mode/xml/xml.js"; -import "codemirror/mode/htmlmixed/htmlmixed.js"; -import "codemirror/mode/css/css.js"; -import "codemirror/mode/yaml/yaml.js"; -import "codemirror/mode/sql/sql.js"; -import "codemirror/mode/python/python.js"; -import "codemirror/mode/markdown/markdown.js"; -import "codemirror/addon/hint/show-hint.css"; -import "codemirror/addon/hint/show-hint.js"; -import "codemirror/addon/hint/javascript-hint.js"; -import "codemirror/addon/hint/xml-hint.js"; -import "codemirror/addon/hint/css-hint.js"; -import "codemirror/addon/hint/html-hint.js"; -import "codemirror/addon/hint/sql-hint.js"; -import "codemirror/addon/hint/anyword-hint.js"; -import "codemirror/addon/lint/lint.css"; -import "codemirror/addon/lint/lint.js"; -import "codemirror/addon/lint/json-lint"; -require("script-loader!jsonlint"); -// import "codemirror/addon/lint/css-lint.js"; -import "codemirror/addon/lint/javascript-lint.js"; -import "codemirror/addon/fold/foldcode.js"; -import "codemirror/addon/fold/foldgutter.js"; -import "codemirror/addon/fold/foldgutter.css"; -import "codemirror/addon/fold/brace-fold.js"; -import "codemirror/addon/fold/xml-fold.js"; -import "codemirror/addon/fold/comment-fold.js"; -import "codemirror/addon/fold/markdown-fold.js"; -import "codemirror/addon/fold/indent-fold.js"; -import "codemirror/addon/edit/closebrackets.js"; -import "codemirror/addon/edit/closetag.js"; -import "codemirror/addon/edit/matchtags.js"; -import "codemirror/addon/edit/matchbrackets.js"; -import "codemirror/addon/selection/active-line.js"; -import "codemirror/addon/search/jump-to-line.js"; -import "codemirror/addon/dialog/dialog.js"; -import "codemirror/addon/dialog/dialog.css"; -import "codemirror/addon/search/searchcursor.js"; -import "codemirror/addon/search/search.js"; -import "codemirror/addon/display/autorefresh.js"; -import "codemirror/addon/selection/mark-selection.js"; -import "codemirror/addon/search/match-highlighter.js"; diff --git a/web-vue/src/components/codeEditor/index.vue b/web-vue/src/components/codeEditor/index.vue index fdcefd2407..a1bb8566a6 100644 --- a/web-vue/src/components/codeEditor/index.vue +++ b/web-vue/src/components/codeEditor/index.vue @@ -1,181 +1,352 @@ - diff --git a/web-vue/src/components/compositionTransfer/composition-transfer.vue b/web-vue/src/components/compositionTransfer/composition-transfer.vue new file mode 100644 index 0000000000..f9e8b5bc4f --- /dev/null +++ b/web-vue/src/components/compositionTransfer/composition-transfer.vue @@ -0,0 +1,171 @@ + + diff --git a/web-vue/src/components/compositionTransfer/utils.ts b/web-vue/src/components/compositionTransfer/utils.ts new file mode 100644 index 0000000000..334b747164 --- /dev/null +++ b/web-vue/src/components/compositionTransfer/utils.ts @@ -0,0 +1,170 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +/** + * 深拷贝 + * @param data + */ +export function cloneDeep(data: any) { + return JSON.parse(JSON.stringify(data)) +} + +/** + * 树转数组 + * @param tree + * @param hasChildren + */ +export function treeToList(tree: any[], hasChildren = false) { + let queen: any[] = [] + const out = [] + queen = queen.concat(JSON.parse(JSON.stringify(tree))) + while (queen.length) { + const first = queen.shift() + if (first?.children) { + queen = queen.concat(first.children) + if (!hasChildren) delete first.children + } + out.push(first) + } + return out +} + +/** + * 数组转树 + * @param list + * @param tree + * @param parentId + * @param key + */ +export function listToTree(list: any, tree: any, parentId = 0, key = 'parentId') { + list.forEach((item: any) => { + if (item[key] === parentId) { + const child = { + ...item, + children: [] + } + listToTree(list, child.children, item.key, key) + if (!child.children?.length) delete child.children + tree.push(child) + } + }) + return tree +} + +/** + * 获取树节点 key 列表 + * @param treeData + */ +export function getTreeKeys(treeData: any) { + const list = treeToList(treeData) + return list.map((item) => item.key) +} + +// /** +// * 循环遍历出最深层子节点,存放在一个数组中 +// * @param deepList +// * @param treeData +// */ +// export function getDeepList(deepList, treeData) { +// treeData?.forEach((item) => { +// if (item?.children?.length) { +// getDeepList(deepList, item.children); +// } else { +// deepList.push(item.key); +// } +// }); +// return deepList; +// } + +// /** +// * 将后台返回的含有父节点的数组和第一步骤遍历的数组做比较,如果有相同值,将相同值取出来,push到一个新数组中 +// * @param uniqueArr +// * @param arr +// */ +// export function uniqueTree(uniqueArr, arr) { +// const uniqueChild = []; +// for (const i in arr) { +// for (const k in uniqueArr) { +// if (uniqueArr[k] === arr[i]) { +// uniqueChild.push(uniqueArr[k]); +// } +// } +// } +// return uniqueChild; +// } + +/** + * 是否选中 + * @param selectedKeys + * @param eventKey + */ +export function isChecked(selectedKeys: any[], eventKey: any) { + return selectedKeys.indexOf(eventKey) !== -1 +} + +/** + * 处理左侧树数据 + * @param data + * @param targetKeys + * @param direction + */ +export function handleLeftTreeData(data: any, targetKeys: any, direction = 'right') { + data.forEach((item: any) => { + if (direction === 'right') { + item.disabled = targetKeys.includes(item.key) + } else if (direction === 'left') { + if (item.disabled && targetKeys.includes(item.key)) item.disabled = false + } + if (item.children) handleLeftTreeData(item.children, targetKeys, direction) + }) + return data +} + +/** + * 处理右侧树数据 + * @param data + * @param targetKeys + * @param direction + */ +export function handleRightTreeData(data: any, targetKeys: any, direction = 'right') { + const list = treeToList(data) + const arr: any[] = [] + const tree: any[] = [] + list.forEach((item) => { + if (direction === 'right') { + if (targetKeys.includes(item.key)) { + const content = { ...item } + if (content.children) delete content.children + arr.push({ ...content }) + } + } else if (direction === 'left') { + if (!targetKeys.includes(item.key)) { + const content = { ...item } + if (content.children) delete content.children + arr.push({ ...content }) + } + } + }) + listToTree(arr, tree, 0) + return tree +} + +/** + * 树数据展平 + * @param list + * @param dataSource + */ +export function flatten(list: any, dataSource: any) { + list.forEach((item: any) => { + dataSource.push(item) + if (item.children) flatten(item.children, dataSource) + }) + return dataSource +} diff --git a/web-vue/src/components/customDrawer/index.vue b/web-vue/src/components/customDrawer/index.vue new file mode 100644 index 0000000000..9d3f1480eb --- /dev/null +++ b/web-vue/src/components/customDrawer/index.vue @@ -0,0 +1,39 @@ + + diff --git a/web-vue/src/components/customInput/index.vue b/web-vue/src/components/customInput/index.vue new file mode 100644 index 0000000000..20a80bd1c0 --- /dev/null +++ b/web-vue/src/components/customInput/index.vue @@ -0,0 +1,123 @@ + + diff --git a/web-vue/src/components/customModal/index.vue b/web-vue/src/components/customModal/index.vue new file mode 100644 index 0000000000..15f4220596 --- /dev/null +++ b/web-vue/src/components/customModal/index.vue @@ -0,0 +1,62 @@ + + + diff --git a/web-vue/src/components/customSelect/index.vue b/web-vue/src/components/customSelect/index.vue index 16cdb3dfd9..9e0260af15 100644 --- a/web-vue/src/components/customSelect/index.vue +++ b/web-vue/src/components/customSelect/index.vue @@ -1,127 +1,125 @@ - diff --git a/web-vue/src/components/customTable/dict/index.ts b/web-vue/src/components/customTable/dict/index.ts new file mode 100644 index 0000000000..272a228d64 --- /dev/null +++ b/web-vue/src/components/customTable/dict/index.ts @@ -0,0 +1,35 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +export const tableSizeList = [ + { + value: 'large', + label: t('i18n_949934d97c') + }, + { + value: 'middle', + label: t('i18n_eadd05ba6a') + }, + { + value: 'small', + label: t('i18n_03e59bb33c') + } +] + +export const tableLayoutList = [ + { + value: 'table', + label: t('i18n_b339aa8710') + }, + { + value: 'card', + label: t('i18n_d87f215d9a') + } +] diff --git a/web-vue/src/components/customTable/index.vue b/web-vue/src/components/customTable/index.vue new file mode 100644 index 0000000000..9167b69889 --- /dev/null +++ b/web-vue/src/components/customTable/index.vue @@ -0,0 +1,511 @@ + + + + diff --git a/web-vue/src/components/customTable/props.ts b/web-vue/src/components/customTable/props.ts new file mode 100644 index 0000000000..8cbd72010f --- /dev/null +++ b/web-vue/src/components/customTable/props.ts @@ -0,0 +1,77 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// +import { t } from '@/i18n' +import { initDefaultProps } from 'ant-design-vue/es/_util/props-util' +import { tableProps } from 'ant-design-vue/es/table' +import { CustomColumnType } from './types' + +export const customTableProps = initDefaultProps( + { + ...tableProps(), + columns: { + type: Array, + default: () => [] + }, + /** 是否显示工具栏 */ + isShowTools: Boolean, + /** 是否隐藏刷新按钮 */ + isHideRefresh: Boolean, + /** tableName 全局唯一值,存储需要 * */ + tableName: { + type: String, + required: true + }, + /** 是否隐藏自动刷新 */ + isHideAutoRefresh: { + type: Boolean, + default: false + }, + /** 默认自动刷新 */ + defaultAutoRefresh: { + type: Boolean, + default: false + }, + /** 自动刷新时间 s 秒,不建议小于 10 秒 */ + autoRefreshTime: { + type: Number, + default: 10 + }, + /** + * 页面布局方式 + */ + layout: { + type: String, + default: null + }, + /** + * 当前页面是否激活 + * + * 自动刷新需要配合使用 + */ + activePage: { + type: Boolean, + default: false + }, + // 空数据时现在内容 + emptyDescription: { + type: String, + deafult: t('i18n_807ed6f5a6') + } + }, + { + defaultAutoRefresh: false, + isHideAutoRefresh: false, + isShowTools: false, + isHideRefresh: false, + autoRefreshTime: 10, + activePage: false, + emptyDescription: t('i18n_807ed6f5a6') + } +) diff --git a/web-vue/src/components/customTable/types/index.ts b/web-vue/src/components/customTable/types/index.ts new file mode 100644 index 0000000000..7ab4e32a4d --- /dev/null +++ b/web-vue/src/components/customTable/types/index.ts @@ -0,0 +1,53 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import { ColumnType } from 'ant-design-vue/es/table' +import { RenderExpandIconProps } from 'ant-design-vue/es/vc-table/interface' +import { CustomSlotsType } from 'ant-design-vue/es/_util/type' +export type CustomColumnType = ColumnType & { + checked?: boolean +} +export type CatchStorageType = { + key: string + checked: boolean +} + +export type TableLayoutType = 'table' | 'card' | undefined + +export type CustomTableType = { + columns: CustomColumnType[] + storageKey: string +} + +export type CustomTableSlotsType = CustomSlotsType<{ + emptyText?: any + expandIcon?: RenderExpandIconProps + title?: any + footer?: any + summary?: any + tableHelp?: any + toolPrefix?: any + tableBodyCell?: (props: { + text: any + value: any + record: Record + index: number + column: ColumnType + }) => void + cardBodyCell?: any + expandedRowRender?: any + expandColumnTitle?: any + emptyDescription: string + // bodyCell?: (props: { text: any; value: any; record: Record; index: number; column: ColumnType }) => void + headerCell?: (props: { title: any; column: ColumnType }) => void + customFilterIcon?: any + customFilterDropdown?: any + default: any +}> diff --git a/web-vue/src/components/customTable/utils/StorageService.ts b/web-vue/src/components/customTable/utils/StorageService.ts new file mode 100644 index 0000000000..72167f86a6 --- /dev/null +++ b/web-vue/src/components/customTable/utils/StorageService.ts @@ -0,0 +1,188 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +export type ProvideType = 'sessionStorage' | 'localStorage' + +import { SizeType } from 'ant-design-vue/es/config-provider' +import { CustomColumnType, TableLayoutType } from '../types' + +export interface StorageObjectType { + /** 布局大小设置 */ + tableSize: SizeType + /** 列设置 */ + column: CustomColumnType[] + /** 布局配置 */ + layout: TableLayoutType + /** 刷新配置 */ + refresh: { + isAutoRefresh: number + autoRefreshTime: number + } +} + +export const getDefaultConfig: () => StorageObjectType = () => ({ + tableSize: undefined, + column: [], + layout: undefined, + refresh: { + isAutoRefresh: -1, + autoRefreshTime: -1 + } +}) + +export type StorageServiceOptions = { + provide: ProvideType + prefix: string + beforeStorage?: (storageObject: StorageObjectType, defaultConfig: StorageObjectType) => StorageObjectType +} + +export class StorageService { + name: string | undefined + provide: ProvideType + prefix: string + beforeStorage?: StorageServiceOptions['beforeStorage'] + constructor(name: string | undefined, options: StorageServiceOptions) { + this.name = name + this.provide = options.provide || 'localStorage' + this.prefix = options.prefix || 'catch__' + if (options.beforeStorage) { + this.beforeStorage = options.beforeStorage + } + } + exitOpenStorage() { + return !!this.getStorageKey() + } + /** + * 获取存储key + */ + getStorageKey() { + return this.name ? `${this.prefix}__${this.name}` : '' + } + /** + * 获取嵌套属性 + * @param path 属性路径 + * @param storageObject 存储对象 + * @param emptyValue 默认值 + */ + getNestedProperty(path: string, storageObject: StorageObjectType, emptyValue: T) { + const val = path.split('.').reduce((returnVal: any, key: string) => { + if (typeof returnVal !== 'object' || returnVal === null || !(key in returnVal)) { + return null + } + return returnVal[key] + }, storageObject) as T + return val || emptyValue + } + /** + * 获取存储值 + * @param path 属性路径 + * @param emptyValue 默认值 + */ + getStorage(path: string, emptyValue: T) { + const key = this.getStorageKey() + let storageObject: StorageObjectType = getDefaultConfig() + if (!key) { + return emptyValue || ({} as T) + } + try { + if (this.provide === 'sessionStorage') { + storageObject = JSON.parse(sessionStorage.getItem(key) || 'null') + } + if (this.provide === 'localStorage') { + storageObject = JSON.parse(localStorage.getItem(key) || 'null') + } + if (!storageObject) { + storageObject = getDefaultConfig() + } + } catch (error) { + console.error(error) + } + if (!path) { + return storageObject as T + } + return this.getNestedProperty(path, storageObject, emptyValue) + } + /** + * 设置存储值 + * @param path 属性路径 + * @param value 值 + */ + setStorage(path: string, value: any) { + if (!this.getStorageKey()) { + return + } + let storageObject = this.getStorage('', getDefaultConfig()) + const keys = path.split('.') + let currentObj = storageObject as any + for (let i = 0; i < keys.length - 1; i++) { + if (typeof currentObj !== 'object' || currentObj === null) { + throw new Error(`Cannot set property '${path}' on object. Intermediate property does not exist.`) + } + const key = keys[i] + if (!(key in currentObj)) { + currentObj[key] = {} + } + currentObj = currentObj[key] + } + const lastKey = keys[keys.length - 1] + currentObj[lastKey] = value + let isDel = false + if (this.beforeStorage) { + storageObject = this.beforeStorage(storageObject, getDefaultConfig()) + if (JSON.stringify(storageObject) === JSON.stringify(getDefaultConfig())) { + isDel = true + } + } + // 存储 + const key = this.getStorageKey() + if (isDel) { + if (this.provide === 'sessionStorage') { + sessionStorage.removeItem(key) + } + if (this.provide === 'localStorage') { + localStorage.removeItem(key) + } + } else { + if (this.provide === 'sessionStorage') { + sessionStorage.setItem(key, JSON.stringify(storageObject)) + } + if (this.provide === 'localStorage') { + localStorage.setItem(key, JSON.stringify(storageObject)) + } + } + } + getTableSizeConfig() { + return this.getStorage('tableSize', undefined) + } + setTableSizeConfig(value: SizeType) { + this.setStorage('tableSize', value || 'middle') + } + getColumnConfig() { + return this.getStorage('column', []) + } + setColumnConfig(value: CustomColumnType[]) { + this.setStorage('column', value || []) + } + getLayoutConfig() { + return this.getStorage('layout', 'table') + } + setLayoutConfig(value: TableLayoutType) { + this.setStorage('layout', value || 'table') + } + getRefreshConfig() { + return this.getStorage('refresh', { + isAutoRefresh: -1, + autoRefreshTime: -1 + }) + } + setRefreshConfig(value: StorageObjectType['refresh']) { + this.setStorage('refresh', value) + } +} diff --git a/web-vue/src/components/customTable/utils/index.ts b/web-vue/src/components/customTable/utils/index.ts new file mode 100644 index 0000000000..e75c658c88 --- /dev/null +++ b/web-vue/src/components/customTable/utils/index.ts @@ -0,0 +1,35 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +/** + * 判断两个数组包含元素是否一致,but不区分顺序 + * @param arr1 + * @param arr2 + */ +export const compareArrays = (arr1: string[], arr2: string[]) => { + if (arr1.length !== arr2.length) { + return false + } + const matches = [] + for (let i = 0; i < arr1.length; i++) { + let found = false + for (let j = 0; j < arr2.length; j++) { + if (arr1[i] === arr2[j]) { + matches.push(j) + found = true + break + } + } + if (!found) { + return false + } + } + return true +} diff --git a/web-vue/src/components/logView/index.vue b/web-vue/src/components/logView/index.vue new file mode 100644 index 0000000000..3305e09b63 --- /dev/null +++ b/web-vue/src/components/logView/index.vue @@ -0,0 +1,137 @@ + + + diff --git a/web-vue/src/components/logView/index2.vue b/web-vue/src/components/logView/index2.vue new file mode 100644 index 0000000000..5aed9d3006 --- /dev/null +++ b/web-vue/src/components/logView/index2.vue @@ -0,0 +1,110 @@ + + + diff --git a/web-vue/src/components/logView/view-pre.vue b/web-vue/src/components/logView/view-pre.vue new file mode 100644 index 0000000000..52e3041b16 --- /dev/null +++ b/web-vue/src/components/logView/view-pre.vue @@ -0,0 +1,264 @@ + + + diff --git a/web-vue/src/components/parameterWidget/index.vue b/web-vue/src/components/parameterWidget/index.vue new file mode 100644 index 0000000000..e7acb7c2a3 --- /dev/null +++ b/web-vue/src/components/parameterWidget/index.vue @@ -0,0 +1,130 @@ + + diff --git a/web-vue/src/components/terminal/index.vue b/web-vue/src/components/terminal/index.vue new file mode 100644 index 0000000000..889f1a9893 --- /dev/null +++ b/web-vue/src/components/terminal/index.vue @@ -0,0 +1,174 @@ + + + diff --git a/web-vue/src/components/upgrade/index.vue b/web-vue/src/components/upgrade/index.vue index 0749745caa..8c3d6d0635 100644 --- a/web-vue/src/components/upgrade/index.vue +++ b/web-vue/src/components/upgrade/index.vue @@ -1,256 +1,559 @@ - + diff --git a/web-vue/src/d.ts/auto-global-import.d.ts b/web-vue/src/d.ts/auto-global-import.d.ts new file mode 100644 index 0000000000..630527da57 --- /dev/null +++ b/web-vue/src/d.ts/auto-global-import.d.ts @@ -0,0 +1,21 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const $confirm: typeof import('./global/global')['$confirm'] + const $error: typeof import('./global/global')['$error'] + const $info: typeof import('./global/global')['$info'] + const $message: typeof import('./global/global')['$message'] + const $notification: typeof import('./global/global')['$notification'] + 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-vue/src/d.ts/auto-import.d.ts b/web-vue/src/d.ts/auto-import.d.ts new file mode 100644 index 0000000000..0ce0ca81c3 --- /dev/null +++ b/web-vue/src/d.ts/auto-import.d.ts @@ -0,0 +1,83 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + 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'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useLink: typeof import('vue-router')['useLink'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/web-vue/src/d.ts/components.d.ts b/web-vue/src/d.ts/components.d.ts new file mode 100644 index 0000000000..a6ead151be --- /dev/null +++ b/web-vue/src/d.ts/components.d.ts @@ -0,0 +1,186 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + AAlert: typeof import('ant-design-vue/es')['Alert'] + AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete'] + ABackTop: typeof import('ant-design-vue/es')['BackTop'] + ABadge: typeof import('ant-design-vue/es')['Badge'] + AButton: typeof import('ant-design-vue/es')['Button'] + 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'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] + ACol: typeof import('ant-design-vue/es')['Col'] + ACollapse: typeof import('ant-design-vue/es')['Collapse'] + ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] + AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] + ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] + ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] + ADirectoryTree: typeof import('ant-design-vue/es')['DirectoryTree'] + ADivider: typeof import('ant-design-vue/es')['Divider'] + ADrawer: typeof import('ant-design-vue/es')['Drawer'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + 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'] + AImage: typeof import('ant-design-vue/es')['Image'] + AInput: typeof import('ant-design-vue/es')['Input'] + AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] + AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] + AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] + AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] + ALayout: typeof import('ant-design-vue/es')['Layout'] + ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] + ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter'] + ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] + AList: typeof import('ant-design-vue/es')['List'] + AListItem: typeof import('ant-design-vue/es')['ListItem'] + AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + 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'] + AQrcode: typeof import('ant-design-vue/es')['QRCode'] + 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'] + ASegmented: typeof import('ant-design-vue/es')['Segmented'] + 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'] + ASteps: typeof import('ant-design-vue/es')['Steps'] + ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] + ATable: typeof import('ant-design-vue/es')['Table'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] + ATag: typeof import('ant-design-vue/es')['Tag'] + ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATimeline: typeof import('ant-design-vue/es')['Timeline'] + ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem'] + ATimePicker: typeof import('ant-design-vue/es')['TimePicker'] + ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + ATransfer: typeof import('ant-design-vue/es')['Transfer'] + ATree: typeof import('ant-design-vue/es')['Tree'] + ATypographyParagraph: typeof import('ant-design-vue/es')['TypographyParagraph'] + 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'] + CodeOutlined: typeof import('@ant-design/icons-vue')['CodeOutlined'] + ColumnHeightOutlined: typeof import('@ant-design/icons-vue')['ColumnHeightOutlined'] + CompositionTransfer: typeof import('./../components/compositionTransfer/composition-transfer.vue')['default'] + CompressOutlined: typeof import('@ant-design/icons-vue')['CompressOutlined'] + CopyOutlined: typeof import('@ant-design/icons-vue')['CopyOutlined'] + CustomDrawer: typeof import('./../components/customDrawer/index.vue')['default'] + CustomInput: typeof import('./../components/customInput/index.vue')['default'] + CustomModal: typeof import('./../components/customModal/index.vue')['default'] + CustomSelect: typeof import('./../components/customSelect/index.vue')['default'] + CustomTable: typeof import('./../components/customTable/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'] + HolderOutlined: typeof import('@ant-design/icons-vue')['HolderOutlined'] + 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'] + LinkOutlined: typeof import('@ant-design/icons-vue')['LinkOutlined'] + 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'] + ParameterWidget: typeof import('./../components/parameterWidget/index.vue')['default'] + 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'] + 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'] + VerticalLeftOutlined: typeof import('@ant-design/icons-vue')['VerticalLeftOutlined'] + 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-vue/src/d.ts/global/global.ts b/web-vue/src/d.ts/global/global.ts new file mode 100644 index 0000000000..58c6e45b4a --- /dev/null +++ b/web-vue/src/d.ts/global/global.ts @@ -0,0 +1,57 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import { GlobalWindow } from '@/interface/common' +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 = () => { + return window as unknown as GlobalWindow +} +// 注册全局的组件 +export const $message = message +export const $notification = notification +// +export const $confirm = Modal.confirm +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', + duration: 4 +}) + +$message.config({ duration: 4 }) + +export const appStore = () => { + return useAppStore() +} + +export const userStore = () => { + return useUserStore() +} + +export const guideStore = () => { + return useGuideStore() +} + +export const router = () => { + return useRouter() +} + +export const route = () => { + return useRoute() +} diff --git a/web-vue/src/d.ts/vite-env.d.ts b/web-vue/src/d.ts/vite-env.d.ts new file mode 100644 index 0000000000..f699d7333f --- /dev/null +++ b/web-vue/src/d.ts/vite-env.d.ts @@ -0,0 +1,16 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +/// + +declare module '*.vue' { + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/web-vue/src/d.ts/vue3-smooth-dnd.d.ts b/web-vue/src/d.ts/vue3-smooth-dnd.d.ts new file mode 100644 index 0000000000..47ac5ae8c0 --- /dev/null +++ b/web-vue/src/d.ts/vue3-smooth-dnd.d.ts @@ -0,0 +1,15 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +// 屏蔽TS报错 +declare module 'vue3-smooth-dnd' { + export const Container: any + export const Draggable: any +} diff --git a/web-vue/src/i18n/index.ts b/web-vue/src/i18n/index.ts new file mode 100644 index 0000000000..fe721cc5f9 --- /dev/null +++ b/web-vue/src/i18n/index.ts @@ -0,0 +1,118 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import { createI18n } from 'vue-i18n' +import { GlobalWindow } from '@/interface/common' +import dayjs from 'dayjs' + +type LangType = { + label: string + isLoad?: boolean + antdLang?: any + isDayjsLoad?: boolean + antd: () => Promise + local: () => Promise + dayjs: () => Promise +} + +export const langDict: { [key: string]: LangType } = { + 'zh-cn': { + // 🇨🇳 + label: '\u7b80\u4f53\u4e2d\u6587', + antd: () => import(/* @vite-ignore */ 'ant-design-vue/es/locale/zh_CN'), + local: () => import(/* @vite-ignore */ './locales/zh_cn.json'), + dayjs: () => import(/* @vite-ignore */ 'dayjs/locale/zh-cn') + }, + 'zh-hk': { + // 🇭🇰 繁體中文(中國香港) + label: '\u7e41\u9ad4\u4e2d\u6587\uff08\u4e2d\u570b\u9999\u6e2f\uff09', + antd: () => import(/* @vite-ignore */ 'ant-design-vue/es/locale/zh_HK'), + local: () => import(/* @vite-ignore */ './locales/zh_hk.json'), + dayjs: () => import(/* @vite-ignore */ 'dayjs/locale/zh-hk') + }, + 'zh-tw': { + // 🇨🇳 繁體中文(中國臺灣) + label: '\u7e41\u9ad4\u4e2d\u6587\uff08\u4e2d\u570b\u81fa\u7063\uff09', + antd: () => import(/* @vite-ignore */ 'ant-design-vue/es/locale/zh_TW'), + local: () => import(/* @vite-ignore */ './locales/zh_tw.json'), + dayjs: () => import(/* @vite-ignore */ 'dayjs/locale/zh-tw') + }, + 'en-us': { + // 🇺🇸 + label: 'English', + antd: () => import(/* @vite-ignore */ 'ant-design-vue/es/locale/en_US'), + local: () => import(/* @vite-ignore */ './locales/en_us.json'), + dayjs: () => import(/* @vite-ignore */ 'dayjs/locale/en') + } +} + +export const supportLang = Object.keys(langDict).map((key: string) => { + return { + label: langDict[key].label, + value: key + } +}) + +export const supportLangArray = supportLang.map((item) => item.value) + +export const normalLang = (locale: string, def: string) => { + locale = locale.replace('_', '-').toLowerCase() + if (supportLangArray.includes(locale)) { + // 避免非法字符串 + return locale + } + console.warn(`[i18n] ${locale} is not support, use ${def} instead`) + return def +} +// 默认语言优先读取服务端配置 +const jw = window as unknown as GlobalWindow +let defaultLocaleTemp = jw.jpomDefaultLocale === '' ? 'zh-cn' : jw.jpomDefaultLocale +defaultLocaleTemp = normalLang(defaultLocaleTemp, 'zh-cn') +if (!langDict[defaultLocaleTemp]) { + defaultLocaleTemp = 'zh-cn' +} +export const defaultLocale = defaultLocaleTemp + +const i18n = createI18n, any, any>({ + legacy: false, + locale: defaultLocale, // 默认显示语言 + fallbackLocale: defaultLocale, // 默认显示语言 + warnHtmlMessage: false +}) + +export default i18n +export const changeLang = async (langKey: string) => { + langKey = langKey?.toLowerCase() + const lang = langDict[langKey] || langDict[defaultLocale] + const global = i18n.global as any + if (!lang.isLoad) { + // 动态加载对应的语言包 + const langFile = await lang.local() + global.setLocaleMessage(langKey, langFile) + if (i18n.mode === 'legacy') { + global.locale = langKey + } else { + global.locale.value = langKey + } + } + if (!lang.antdLang) { + lang.antdLang = await lang.antd() + } + if (!lang.isDayjsLoad) { + await lang.dayjs() + lang.isDayjsLoad = true + } + + dayjs.locale(langKey) + lang.isLoad = true + return lang.antdLang +} +// @ts-ignore +export const { t } = i18n.global diff --git a/web-vue/src/i18n/locales/en_us.json b/web-vue/src/i18n/locales/en_us.json new file mode 100644 index 0000000000..67639f25e1 --- /dev/null +++ b/web-vue/src/i18n/locales/en_us.json @@ -0,0 +1,2806 @@ +{ + "i18n_0006600738":"Join a Docker cluster", + "i18n_005de9a4eb":"The build history is used to record the information of each build, which can keep bundle information and build logs. At the same time, it can also quickly roll back the release", + "i18n_0079d91f95":"Are you sure you want to top this data?", + "i18n_007f23e18f":"Turn off TLS authentication", + "i18n_00a070c696":"Click to copy.", + "i18n_00b04e1bf0":"send packet", + "i18n_00d5bdf1c3":"number of calls", + "i18n_00de0ae1da":"Scripts to be executed before file upload (non-blocking commands)", + "i18n_01081f7817":"Please enter the suffix and file encoding that allow editing of the file. If you do not set the encoding, the system encoding will be taken by default, and multiple line breaks will be used. Example: Set the encoding: txt {'@'} utf-8, do not set the encoding: txt", + "i18n_010865ca50":"Do you really want to stop the project?", + "i18n_0113fc41fc":"full screen log", + "i18n_01198a1673":"Upload small file", + "i18n_01226f48fc":"For each subexpression, the following forms are also supported:", + "i18n_0128cdaaa3":"assignment type", + "i18n_01ad26f4a9":"Reset the trigger token information, the previous trigger token will be invalid after reset", + "i18n_01b4e06f39":"restart", + "i18n_01e94436d1":"original password", + "i18n_020d17aac6":"Send size", + "i18n_020f1ecd62":"Start uploading", + "i18n_020f31f535":"The path needs to be configured with an absolute path, and soft chain is not supported.", + "i18n_0215b91d97":"The build serial number id needs to be replaced with the actual situation.", + "i18n_0221d43e46":"Remote download URL is not empty", + "i18n_0227161b3e":"execution method", + "i18n_022b6ea624":"Are you sure you want to delete the current volume?", + "i18n_0253279fb8":"cloning depth", + "i18n_02d46f7e6f":"Do you really want to delete these build histories?", + "i18n_02d9819dda":"prompt", + "i18n_02db59c146":"Prohibited IP address", + "i18n_02e35447d4":"Download the bundle. If the button is not available, it means that the product file does not exist. Generally, the corresponding file is not generated by the build or the file related to the build history is deleted.", + "i18n_0306ea1908":"Delete Mirror", + "i18n_031020489f":"Current workspace The build record you triggered", + "i18n_03580275cb":"Please select the item you want to restart", + "i18n_0360fffb40":"And turn on this switch", + "i18n_036c0dc2aa":"System Cancel Distribution", + "i18n_0373ba5502":"You need to install the agent on the server that needs to be managed and add the agent information to the system", + "i18n_03816381ec":"Switch view", + "i18n_0390e2f548":"Parameter {count} description", + "i18n_03a74a9a8a":"log path", + "i18n_03c1f7c142":"Please fill in and select the built repository.", + "i18n_03d9de2834":"project operation and maintenance", + "i18n_03dcdf92f5":"privacy variable", + "i18n_03e59bb33c":"compact", + "i18n_03f38597a6":"speed", + "i18n_0428b36ab1":"copy", + "i18n_04412d2a22":"The operation cannot be withdrawn", + "i18n_044b38221e":"Java project (for example reference, the specifics need to be determined according to the actual situation of the project)", + "i18n_045cd62da3":"Model:", + "i18n_045f89697e":"Publish compressed packages", + "i18n_047109def4":"pending", + "i18n_04a8742dd7":"plugin runtime", + "i18n_04edc35414":"template node", + "i18n_051fa113dd":"How to connect to docker is realized through end point, and each operation of docker related api needs to log in to end point once.", + "i18n_05510a85b0":"All your operation logs in the system", + "i18n_059ac641c0":"Privilege:", + "i18n_05b52ae2db":"{Slot1} Select container function for container build (fromTag)", + "i18n_05cfc9af9d":"receive error", + "i18n_05e6d88e29":"The distribution node refers to the script that automatically synchronizes the script content to the node after editing the script. The DSL mode in the general user node distribution function", + "i18n_05e78c26b1":"In a single trigger address: the first random string is the command script ID, and the second random string is the token.", + "i18n_05f6e923af":"execution error", + "i18n_0647b5fc26":"Stop first", + "i18n_066431a665":"Please enter a certificate description", + "i18n_066f903d75":"Moving up or down after operation may not achieve the expected sequence", + "i18n_067638bede":"CPU number", + "i18n_067eb0fa04":"If the alarm contact here cannot be selected, it means that the administrator here has not set the mailbox, which can be set in the user profile in the drop-down menu in the upper right corner.", + "i18n_0693e17fc1":"Does it automatically scroll to the bottom when there is new content?", + "i18n_06986031a7":"Need to go to the original workspace to control node distribution", + "i18n_06e2f88f42":"Please enter a name", + "i18n_0703877167":"Close MFA", + "i18n_0719aa2bb0":"Reset password", + "i18n_0728fee230":"Please enter an announcement title", + "i18n_072fa90836":"compression ", + "i18n_0739b9551d":"port protocol", + "i18n_07683555af":"Current version number:", + "i18n_0793aa7ba3":"Maven sdk image usage: https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_07a03567aa":"virtual memory usage", + "i18n_07a0e44145":"Hostname:", + "i18n_07a828310b":"parallelism", + "i18n_07a8af8c03":"The actual process ID for the current project", + "i18n_07b6bb5e40":"Strict execution of scripts (build commands, event scripts, local publish scripts, container build commands) must return a status code of 0, otherwise the build status will be marked as failed", + "i18n_07d2261f82":"The default is the current time to the end of the year.", + "i18n_080b914139":"Upload package", + "i18n_0836332bf6":"upgrade protocol", + "i18n_083b8a2ec9":"A physical node bound to multiple server levels can also generate lonely data", + "i18n_08902526f1":"Skin:", + "i18n_0895c740a6":"swap memory usage", + "i18n_089a88ecee":"System time:", + "i18n_08ab230290":"Operation instructions", + "i18n_08ac1eace7":"Scripts to be executed after a successful file upload (non-blocking commands)", + "i18n_08b1fa1304":"Please enter your username", + "i18n_08b55fea3c":"manage", + "i18n_0934f7777a":"New label end point", + "i18n_095e938e2a":"Stop", + "i18n_09723d428d":"alarm contact", + "i18n_09d14694e7":"You need to get docker information in SSH monitoring.", + "i18n_09e7d24952":"Actual memory usage:", + "i18n_0a056b0d5a":"dynamic file", + "i18n_0a1d18283e":"build confirmation pop-up", + "i18n_0a47f12ef2":"If the lonely data is associated with other functions in the workspace, the corrected associated data will be invalid, and the corresponding function cannot query the associated data", + "i18n_0a54bd6883":"Gmail mailbox configuration", + "i18n_0a60ac8f02":"Yes", + "i18n_0a63bf5b41":"Soft memory limitations.", + "i18n_0a9634edf2":"Address wild-card, * means all addresses will use proxies", + "i18n_0aa60d1169":"You have not logged in yet", + "i18n_0aa639865c":"Do you really want to delete the machine SSH?", + "i18n_0ac4999a4c":"Network interface card information", + "i18n_0ac9e3e675":"After the binding is successful, it will no longer be displayed. It is strongly recommended to save this QR code or the following MFA key.", + "i18n_0af04cdc22":"Supports filling in two ways:", + "i18n_0af5d9f8e8":"The current area is a system management and asset management center", + "i18n_0b23d2f584":"differential construction", + "i18n_0b2fab7493":"For the current SSH authorization directory (file directory, file suffix, prohibit command), please go to [System Management] - > [Asset Management] - > [SSH Management] - > Operation Bar - > Association Button - > Corresponding Workspace - > Operation Bar - > Configuration Button", + "i18n_0b3edfaf28":"Set memory limits.", + "i18n_0b58866c3e":"Breakpoint/sharding single file download", + "i18n_0b76afbf5d":"The CPU allowed to execute (e.g., 0-3, 0", + "i18n_0b9d5ba772":"Please respect the open-source agreement and do not modify the version information without authorization, otherwise you may be held legally responsible.", + "i18n_0baa0e3fc4":"Publishing", + "i18n_0bac3db71c":"Failure after server level restart", + "i18n_0bbc7458b4":"Back to home page", + "i18n_0bc45241af":"The incoming parameters are: outGivingId, outGivingName, status, statusMsg, executeTime", + "i18n_0bf9f55e9d":"Can't close", + "i18n_0bfcab4978":"Node sdk mirror usage: https://registry.npmmirror.com/-/binary/node", + "i18n_0c0633c367":"The default workspace cannot be deleted", + "i18n_0c1de8295a":"Independence", + "i18n_0c1e9a72b7":"The micro-queue will be used to queue the build to avoid triggering the build to be interrupted almost simultaneously (the general user warehouse merge code will trigger multiple requests), the queue is kept in memory, and the restart will be lost.", + "i18n_0c1f1cd79b":"Do not restart automatically", + "i18n_0c1fec657f":"second", + "i18n_0c2487d394":"Drop-down search for the top 10 keywords related to the default search, as well as the machine nodes that have been selected", + "i18n_0c256f73b8":"Container name:", + "i18n_0c4eef1b88":"When it is 6 digits, the first digit indicates", + "i18n_0c5c8d2d11":"Basic information:", + "i18n_0c7369bbee":"Enable SSH access", + "i18n_0cbf83cc07":"Contact us", + "i18n_0ccaa1c8b2":" : Indicates that it matches this position all the time", + "i18n_0ce54ecc25":"paid community", + "i18n_0cf4f0ba82":"Do you really want to save the current configuration? If the configuration is wrong, the service may not be started. You need to manually restore Austria!!! Please pay attention to the restart status in time after successful saving!!", + "i18n_0cf81d77bb":"Please fill in the warehouse address.", + "i18n_0d44f4903a":"Do you really want to release (delete) the current project?", + "i18n_0d467f7889":"#Whether to enable the log backup function", + "i18n_0d48f8e881":"Please enter the service address", + "i18n_0d50838436":"data directory", + "i18n_0d98c74797":"other", + "i18n_0da9b12963":"user data", + "i18n_0de68f5626":"Log in to JPOM", + "i18n_0e052223a4":"Restart server level needs to be regained", + "i18n_0e16902c1e":"check status", + "i18n_0e1ecdae4a":"Full sequence execution (if there is an execution failure, it will end this time)", + "i18n_0e25ab3b51":"The allowed IP of the certificate needs to be the same as the docker host.", + "i18n_0e44ae17ae":"Server level machine networking", + "i18n_0e502fed63":"The restart timed out, please go to the server to check the console log to troubleshoot the problem.", + "i18n_0e55a594fd":"monitoring project", + "i18n_0e5f01b9be":"associative workspace ssh", + "i18n_0ea78e4279":"View log", + "i18n_0ec9eaf9c3":"more", + "i18n_0eccc9451d":"#Number of backup files retained", + "i18n_0ee3ca5e88":"Scan the code to appreciate and support the long-term development of open-source projects", + "i18n_0ef396cbcc":"Distribute results", + "i18n_0f004c4cf7":"third-party login", + "i18n_0f0a5f6107":"normal connection", + "i18n_0f189dbaa4":"No users", + "i18n_0f4f503547":"Please enter the version", + "i18n_0f539ff117":"Do you really want to batch delete the selected images? Images that are already in container use cannot be deleted!", + "i18n_0f59fe5338":"Firewall port", + "i18n_0f5fc9f300":"Document Management Center", + "i18n_0f8403d07e":"Refresh countdown", + "i18n_0fca8940a8":"No nodes.", + "i18n_0ff425e276":"File ID", + "i18n_1012e09849":"Processing failed", + "i18n_10145884ba":"N lines after file", + "i18n_1014b33d22":"group name", + "i18n_101a86bc84":"Please enter...", + "i18n_1022c545d1":"The plug-in side automatically checks the project when it starts. If it doesn't start, it will try to start it.", + "i18n_102dbe1e39":"Note: environment variables have scope: current workspace or global, cannot be referenced across workspaces", + "i18n_102e8ec6d5":"Network traffic information", + "i18n_1058a0be42":"Enable TLS authentication, certificate information:", + "i18n_1062619d5a":"The node account password is generated by the system by default: you can use the agent in the plug-in data directory.", + "i18n_108d492247":"Regular grammar reference", + "i18n_10c385b47e":"One-click distribution synchronizes system configuration of multiple nodes", + "i18n_10d6dfd112":"N lines after display", + "i18n_10f6fc171a":"SSH name", + "i18n_111e786daa":"Fill in the remarks Only this build will take effect", + "i18n_1125c4a50b":"Do you really want to delete the distribution information? After deleting, the items under the node will also be deleted", + "i18n_113576ce91":"Product catalog:", + "i18n_1149274cbd":"total number of users", + "i18n_115cd58b5d":"] Backup folder?", + "i18n_1160ab56fd":"Build command:", + "i18n_116d22f2ab":"Project ID:", + "i18n_11724cd00b":"Cluster creation time", + "i18n_117a9cbc8d":"Language:", + "i18n_11957d12e4":"Alarm", + "i18n_11e88c95ee":" Find the previous one", + "i18n_121e76bb63":"Please select the corresponding branch to build", + "i18n_1235b052ff":"Node Address (192.168.1.100:2123)", + "i18n_1278df0cfc":"Associated Node If the server has a java environment, but the plug-in is not running, a quick install button will be displayed", + "i18n_127de26370":"SMTP address: [smtp.qq.com], the username is generally the QQ number, the password is the email authorization code, and the port defaults to 587/465.", + "i18n_12934d1828":"The log directory refers to the console log storage directory", + "i18n_12afa77947":"Opening the cache build directory will keep the repository files, the second build will pull the code, and if the cache directory is not opened, the repository code will be re-pulled each time the build is not opened (larger projects are not recommended to close the cache)", + "i18n_12d2c0aead":"Please copy this password to inform this user", + "i18n_12dc402a82":"reference data", + "i18n_130318a2a1":"The route is invalid and cannot be redirected", + "i18n_1303e638b5":"Modification time", + "i18n_13627c5c46":"Configure ssh", + "i18n_138776a1dc":"The default is in the plugin-side data directory/{'${project Id }'}/{'${ project Id}'} .log", + "i18n_138a676635":"Attention", + "i18n_13c76c38b7":"#scriptId can refer to the script in the script library (G {'@'} xxx) where xxx is the script tag in the script library, provided that the corresponding script needs to be extracted and synchronized to the corresponding machine node", + "i18n_13d10a9b78":"No asset SSH", + "i18n_13d947ea19":"You need to add the asset machine first and then assign the machine node (logical node) to the current workspace.", + "i18n_13f7bb78ef":"Default statistics in the machine except the local interface (loopback or no hardware address) network interface card traffic sum", + "i18n_13f931c5d9":"View task", + "i18n_1432c7fcdb":"System Announcement", + "i18n_143bfbc3a1":"Click to resynchronize the current workspace logical node project information", + "i18n_143d8d3de5":"Otherwise, all data that meets the conditions will be deleted", + "i18n_148484b985":"You need to configure the docker container to be managed at the server level and assigned to the current workspace", + "i18n_1498557b2d":"Only one menu can be expanded at a time", + "i18n_14a25beebb":"Every 10 seconds", + "i18n_14d342362f":"label", + "i18n_14dcfcc4fa":"No reload has been performed", + "i18n_14dd5937e4":"Additional environment variables.env Added multiple comma-separated", + "i18n_14e6d83ff5":"Time:", + "i18n_14ee5b5dc5":"The command file will be executed in {'${plugin-side data directory}'}/script/xxxx.sh, bat", + "i18n_14feaa5b3a":"Refresh countdown ", + "i18n_1535fcfa4c":"send", + "i18n_156af3b3d1":"menu configuration", + "i18n_1593dc4920":"Do you really want to delete this record? Container tags that build associations after deletion will not be available", + "i18n_159a3a8037":"Update mirror", + "i18n_15c0ba2767":"Upload project file", + "i18n_15c46f7681":"Modify the interface HTTP status code to 200 and the response content is: success to determine the success of the operation, otherwise it may fail", + "i18n_15d5fffa6a":"response result", + "i18n_15e9238b79":"receive", + "i18n_15f01c43e8":"log backup list", + "i18n_15fa91e3ab":"Sky level", + "i18n_1603b069c2":"Monday", + "i18n_1622dc9b6b":"unknown", + "i18n_162e219f6d":"lost", + "i18n_164cf07e1c":"clear cover", + "i18n_16646e46b1":"Product file size:", + "i18n_16b5e7b472":"build directly", + "i18n_16f7fa08db":"Really?", + "i18n_17006d4d51":"Whether to automatically jump to the system page", + "i18n_170fc8e27c":"Thursday", + "i18n_174062da44":"distribution method", + "i18n_1775ff0f26":"It is recommended to add a specified time range", + "i18n_178ad7e9bc":"The id, token and trigger build in the parameters are consistent, buildNumId build sequence id", + "i18n_17a101c23e":"Lonely data means that there is data in the machine node, but it cannot be bound to the current system (relationship binding = node ID + workspace ID corresponds). Generally, such data will not appear.", + "i18n_17a74824de":"build method", + "i18n_17acd250da":"Move Down", + "i18n_17b4c9c631":"There are no nodes.", + "i18n_17b5e684e5":"You need to configure the file suffix that allows editing in the authorization configuration of [Plug-in Side Configuration] in the node management.", + "i18n_17c06f6a8b":"Last execution time", + "i18n_17d444b642":"operation mode", + "i18n_1810e84971":"To connect using SSH", + "i18n_1818e9c264":"JVM total memory", + "i18n_1819d0cdda":"If Sync to File Management is enabled, the sync to File Management is automatically performed during the build release process.", + "i18n_181e1ad17d":"Long press to drag and sort.", + "i18n_1857e7024c":"System version", + "i18n_185926bf98":"full screen", + "i18n_1862c48f72":"Local status:", + "i18n_1880b85dc5":"Black and white ambiance", + "i18n_18b0ab4dd2":"machine SSH name", + "i18n_18b34cf50d":"Do not scroll", + "i18n_18c63459a2":"default", + "i18n_18c7e2556e":"If the current build information has been updated on another page, you need to click the refresh button to get the latest information. Unsaved data will also be lost after clicking refresh.", + "i18n_18d49918f5":"Account is locked", + "i18n_18eb76c8a0":"Memory minimum 4M", + "i18n_192496786d":"event script", + "i18n_19675b9d36":"The clearing code (warehouse directory) is to delete everything in the storage warehouse directory in the server. After deleting, the next build will pull up the files in the warehouse again. It is generally used to solve conflicts between files in the server and files in the remote warehouse. The execution time depends on the size of the source code directory and the number of files. Please be patient if it expires, or try again later.", + "i18n_1974fe5349":"Binding successful", + "i18n_197be96301":"To be perfected", + "i18n_19f974ef6a":"When opening the difference release and opening the clear release, the files under the project directory will be automatically deleted, but the files not under the bundle directory will be automatically deleted. [Clear the release difference upload will delete the difference file before uploading the difference file]", + "i18n_19fa0be4d2":" official document", + "i18n_19fcb9eb25":"time", + "i18n_1a44b9e2f7":"Sync to other workspaces", + "i18n_1a55f76ace":"Build command, the relative path of the bundle is:", + "i18n_1a56bb2237":"Select at least one node and project", + "i18n_1a6aa24e76":"execute", + "i18n_1a704f73c2":"Please select a file", + "i18n_1a8f90122f":"prompt message ", + "i18n_1abf39bdb6":"#Cache this directory globally (multiple builds can share this cache directory)", + "i18n_1ad696efdc":"Build execution commands (non-blocking commands), such as: mvn clean package, npm run build. Supported variables: {'${BUILD_ID BUILD_NAME BUILD_SOURCE_FILE }'}、{'${ BUILD_NUMBER_ID}'}, .env in the warehouse directory, workspace variables", + "i18n_1ae2955867":"Specify the pom file package mvn -f xxx/pom.xml clean package", + "i18n_1afdb4a364":"Hide scroll bar. Longitudinal scroll mode Reminder: scroll wheel, horizontal scroll mode: Shift + scroll wheel", + "i18n_1b03b0c1ff":"The Docker or cluster that has been assigned to the workspace is not directly deleted. You need to delete the assets Docker or cluster one by one after the assigned workspaces are deleted.", + "i18n_1b38c0bc86":"Backup file storage directory:", + "i18n_1b5266365f":"original IP", + "i18n_1b5bcdf115":"Session has been closed [node-system-log]", + "i18n_1b7cba289a":"data statistics", + "i18n_1b8fff7308":"Open MFA", + "i18n_1b963fd303":"[Recommended] Tencent identity verification code", + "i18n_1b973fc4d1":"Group name:", + "i18n_1ba141c9ac":"Please select the item of the soft chain.", + "i18n_1ba584c974":"Configuration container", + "i18n_1baae8183c":"Whether to decompress", + "i18n_1c040e6b87":"In general, downgrade operations are not recommended", + "i18n_1c10461124":"Example: key, key1 or key = value, key1 = value1", + "i18n_1c13276448":"Current workspace association building", + "i18n_1c2e9d0c76":"No build", + "i18n_1c3cf7f5f0":"association", + "i18n_1c61dfb86f":"mount point", + "i18n_1c8190b0eb":"Please fill in the project DSL configuration content, you can click the switch tab above to view the configuration example", + "i18n_1c83d79715":"execution failed", + "i18n_1c9d3cb687":"Username ID", + "i18n_1cc82866a4":"sharding operand", + "i18n_1d0269cb77":"The SSH that has been assigned to the workspace is nothing more than direct deletion. You need to delete the asset SSH one by one in each assigned workspace.", + "i18n_1d263b7efb":"This option is only valid for this build", + "i18n_1d38b2b2bc":"Please select the project authorization path", + "i18n_1d53247d61":"Please select a logical node", + "i18n_1d650a60a5":"Hard disk", + "i18n_1d843d7b45":"This node has no projects yet", + "i18n_1dc518bddb":"Folder where the project is stored", + "i18n_1dc9514548":"It is not the same as the PING test. A successful test here means that the network must be unobstructed, and a failed test here means that the network is not necessarily unobstructed.", + "i18n_1de9b781bd":"To build with containers, the host where the docker container is located needs to have a public network, because the SDK and images that the environment depends on need to be downloaded remotely", + "i18n_1e07b9f9ce":"Please select the machine node to synchronize the system configuration", + "i18n_1e4a59829d":"Plug-in side boot self-start", + "i18n_1e5533c401":"configuration directory", + "i18n_1e5ca46c26":"Exclude publishing ANT expressions, multiple separated by commas", + "i18n_1e88a0cfaf":"Do not publish to docker cluster", + "i18n_1e93bdad2a":"Search project name", + "i18n_1eb378860a":"Do You Really Want to Kill This Process?", + "i18n_1eba2d93fc":"Reason for ban", + "i18n_1ece1616bf":"If the plug-in is running normally but the connection fails, check if the port is open, firewall rules, Cloud as a Service security group inbound rules", + "i18n_1ed46c4a59":"Distribution name (project name)", + "i18n_1f08329bc4":"Search command name", + "i18n_1f0c93d776":" : Executed every minute", + "i18n_1f0d13a9ad":"The server level distribution synchronization script cannot be deleted directly, it needs to be operated at the server level", + "i18n_1f1030554f":"Total {total}", + "i18n_1f130d11d1":"SMTP server", + "i18n_1f4c1042ed":"folder", + "i18n_1fa23f4daa":"expiration time", + "i18n_1fd02a90c3":"user", + "i18n_200707a186":"The post-creation build method does not support modification", + "i18n_2025ad11ee":"Do you really want to unbind the node script?", + "i18n_2027743b8d":"System name:", + "i18n_204222d167":"network latency", + "i18n_2064fc6808":"Do not show", + "i18n_207243d77a":"If you want to assign the workspace to other users, you also need to go to permission group management", + "i18n_207d9580c1":"Indicates Saturday", + "i18n_209f2b8e91":"Please enter your login password", + "i18n_20a9290498":"You come to the system management center.", + "i18n_20c8dc0346":"demo account", + "i18n_20e0b90021":"Do you really want to delete the surveillance?", + "i18n_20f32e1979":"Role:", + "i18n_211354a780":"The root inside is just a normal user privilege outside. Default false", + "i18n_21157cbff8":"millisecond", + "i18n_211a60b1d6":"Edit some basic parameters of the container", + "i18n_2141ffaec9":"Status data is acquired asynchronously with a time lag", + "i18n_2168394b82":"File id, precision search", + "i18n_2171d1b07d":"default parameter", + "i18n_2191afee6e":"The upgrade timed out, please go to the server to check the console log to troubleshoot the problem.", + "i18n_21d81c6726":"Configure labels for containers in the current workspace", + "i18n_21da885538":"You can use the node script:", + "i18n_21dd8f23b4":"open source protocol", + "i18n_21e4f10399":"Priority judgment disabled period", + "i18n_21efd88b67":"No data yet", + "i18n_220650a1f5":"After configuration, it will be saved to the current build", + "i18n_2213206d43":"Click Delay to view the network delay history data of the corresponding node.", + "i18n_222316382d":"associated node", + "i18n_2223ff647d":"clear release", + "i18n_2245cf01a3":"You do not have permission to access", + "i18n_2246d128cb":"WeCom notification address", + "i18n_22482533ff":"Private key content, if not filled in, the configuration in the default $HOME/.ssh directory will be used. Supported configuration file directory: file:/xxxx/xx", + "i18n_224aef211c":"build information", + "i18n_224e2ccda8":"configuration", + "i18n_2256690a28":"Node ID:", + "i18n_22670d3682":"Please select the script to use", + "i18n_226a6f9cdd":"Please check if the ws proxy is turned on.", + "i18n_226b091218":"type", + "i18n_22b03c024d":"QR code", + "i18n_22c799040a":"container", + "i18n_22cf31df5d":"Current access IP:", + "i18n_22e4da4998":"Indicates that the project is not currently running", + "i18n_22e888c2df":"expiration time", + "i18n_2300ad28b8":"read and write", + "i18n_2314f99795":"New version detected ", + "i18n_231f655e35":"Current program packaging time:", + "i18n_23231543a4":"correction", + "i18n_2331a990aa":"Scan the code to transfer money to support the long-term development of open-source projects", + "i18n_233fb56ab2":"Get it in Settings -- > Security Settings -- > Private Tokens", + "i18n_234e967afe":"Commands executed before publishing (non-blocking commands), usually close project commands, support variable substitution: {'${BUILD_ID BUILD_NAME BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID}'}", + "i18n_2351006eae":"Additional environment variables", + "i18n_23559b6453":"#Cache the maven repository file in the container to the docker volume", + "i18n_2356fe4af2":"Custom project management with script templates", + "i18n_2358e1ef49":"Affiliated workspace: ", + "i18n_235f0b52a1":"sending error", + "i18n_23b38c8dad":"Session closed [upgrade]", + "i18n_23b444d24c":"Quick configuration", + "i18n_23eb0e6024":"nickname", + "i18n_242d641eab":"Suffix", + "i18n_2432b57515":"Remarks", + "i18n_24384ba6c1":"Are you sure you want to resynchronize the current node project cache information?", + "i18n_24384dab27":"Please enter the value of value", + "i18n_244d5a0ed8":"build parameters", + "i18n_2456d2c0f8":"If the container exits with a non-zero exit code, restart the container. You can specify the number of times: on-failure: 2", + "i18n_2457513054":"Saturday", + "i18n_2482a598a3":"Plugin version number", + "i18n_248c9aa7aa":"build status", + "i18n_2493ff1a29":"custom process type", + "i18n_2499b03cc5":"Retained products:", + "i18n_249aba7632":"God", + "i18n_24ad6f3354":"If the project data in the machine before the crashed machine (asset machine) migration is only logically deleted (project files and logs are preserved)", + "i18n_24cc0de832":"Execute command", + "i18n_24d695c8e2":"cluster hostname", + "i18n_250688d7c9":"publish failed", + "i18n_250a999bb2":"Container labels, such as: xxxx: latest multiple separated by commas", + "i18n_25182fb439":"Workspace menu", + "i18n_251a89efa9":"View current status", + "i18n_252706a112":"[Recommended] WeChat Mini Program Search, Digital Shield OTP", + "i18n_2527efedcd":"User information URL", + "i18n_2560e962cf":"Please select a distribution item", + "i18n_257dc29ef7":"Search Configuration Reference", + "i18n_25b6c22d8a":"In order to avoid displaying too much content and causing the browser card, read the last few lines of the log. After modification, you need to enter to read it again. If it is less than 1, read all", + "i18n_25be899f66":"After filtering, this release operation only publishes the filter items, and it will only take effect for this operation", + "i18n_25c6bd712c":"Please enter the number of scheduled runs obtained", + "i18n_25f29ebbe6":"Number of script logs:", + "i18n_25f6a95de3":"Make sure you want to cancel the build [Name:", + "i18n_2606b9d0d2":"Distribution machine", + "i18n_260a3234f2":"Please select SSH.", + "i18n_2611dd8703":"When there is no corresponding node in the target workspace, a new node (logical node) will be automatically created.", + "i18n_26183c99bf":"File Center", + "i18n_2646b813e8":"login password", + "i18n_267bf4bf76":"Distributing to nodes requires attention that cross-workspace duplicate names will be overwritten by the last synchronization", + "i18n_2684c4634d":"Version:", + "i18n_26a3378645":"Please select a running method", + "i18n_26b5bd4947":"Loading...", + "i18n_26bb841878":"new build", + "i18n_26bd746dc3":"Do you really want to empty the project directory and files?", + "i18n_26c1f8d83e":"Last Operator", + "i18n_26ca20b161":"source", + "i18n_26eccfaad1":"Mirror image:", + "i18n_26f95520a5":"The execution command contains:", + "i18n_26ffe89a7f":"Project name:", + "i18n_27054fefec":"Execute script input parameters are: startReady, pull, executeCommand, release, done, stop, success", + "i18n_2770db3a99":"Loading project data...", + "i18n_2780a6a3cf":"TLS authentication", + "i18n_27b36afd36":"Most operations with a status code of 0 have no operation results or are executed asynchronously", + "i18n_27ba6eb343":"Gateway:", + "i18n_27ca568be2":"continue", + "i18n_27d0c8772c":"If mishandled, redundant data will be generated!!!", + "i18n_27f105b0c3":"Please select the node to upgrade", + "i18n_280379cee4":"Save and close", + "i18n_282c8cda1f":"If the reported node information contains multiple IP addresses, the user needs to confirm the use of specific IP address information", + "i18n_288f0c404c":"empty", + "i18n_28b69f9233":"The process of building an image does not use caching", + "i18n_28b988ce6a":"file type", + "i18n_28bf369f34":"The published file name is: file ID. Suffix, not the real name of the file (you can modify it at will using the uploaded script)", + "i18n_28c1c35cd9":"The master node cannot be directly deleted", + "i18n_28e0fcdf93":"You cannot use containers to build Olympics without containers or unconfigured labels", + "i18n_28e1c746f7":"SSH name", + "i18n_28e1eec677":"authorization path", + "i18n_28f6e7a67b":"static file", + "i18n_29139c2a1a":"file name", + "i18n_2926598213":"Project log", + "i18n_293cafbbd3":"crop", + "i18n_2953a9bb97":"You need to create an account for subsequent login to the management system, please remember the super administrator account password", + "i18n_295bb704f5":"language", + "i18n_29b48a76be":"Please choose a publishing method", + "i18n_29efa328e5":"undistributed", + "i18n_2a049f4f5b":"Distribution failed", + "i18n_2a0bea27c4":"execution domain", + "i18n_2a0c4740f1":"file", + "i18n_2a1d1da97a":"Package testing environment package mvn clean package -Dmaven.test.skip = true -Ptest", + "i18n_2a24902516":"Cluster ID:", + "i18n_2a38b6c0ae":"The upgrade was not successful:", + "i18n_2a3b06a91a":"Virtual MAC", + "i18n_2a3e7f5c38":"manual", + "i18n_2a6a516f9d":"Fill out the run command", + "i18n_2a813bc3eb":"Download now", + "i18n_2ad3428664":"Please select the service name to publish to the cluster", + "i18n_2adbfb41e9":"Parameters if passed in", + "i18n_2ae22500c7":"disabled period", + "i18n_2b04210d33":"Process number:", + "i18n_2b0623dab9":"Independent container", + "i18n_2b0aa77353":"Are you sure you want to start the current container?", + "i18n_2b0f199da0":"Do not execute or compile test cases mvn clean package -Dmaven.test.skip = true", + "i18n_2b1015e902":"Parameter descriptions have no practical effect", + "i18n_2b21998b7b":"Are you sure you want to turn off two-step verification? Account security will be affected after closing, and existing mfa keys will be invalid after closing", + "i18n_2b36926bc1":"There is no construction history.", + "i18n_2b4bb321d7":"Content Area Topic Switch", + "i18n_2b4cf3d74e":"Please select the build to use", + "i18n_2b52fa609c":"Abnormality occurs", + "i18n_2b607a562a":"line by line execution", + "i18n_2b6bc0f293":"operation", + "i18n_2b788a077e":"and other commonly used usernames to avoid excessive login failures caused by intentional or unintentional operations by other users, so that the super administrator account is abnormally locked.", + "i18n_2b94686a65":"Add environment variables to the container", + "i18n_2ba4c81587":"Please enter your email address", + "i18n_2bb1967887":"Please ask us for authorization, otherwise there will be legal risks.", + "i18n_2be2175cd7":"Execution container, label", + "i18n_2be75b1044":"global", + "i18n_2bef5b58ab":"If you don't fill it in, you won't update it.", + "i18n_2c014aeeee":"packing time", + "i18n_2c5b0e86e6":"User password reset successful", + "i18n_2c635c80ec":"The publish operation refers to publishing (uploading) the files in the bundle directory to the corresponding place in different ways after executing the build command", + "i18n_2c74d8485f":"After the download is completed, you need to manually select Update to Node to complete the node update.", + "i18n_2c8109fa0b":"Current directory: ", + "i18n_2c921271d5":"Vue project (for example reference, the specifics need to be determined according to the actual situation of the project)", + "i18n_2cdbbdabf1":"The bundle directory, the path to the relative repository, such as the target/xxx.jar vue dist of the java project", + "i18n_2cdcfcee15":"Feature-rich, designed for 2-step verification codes", + "i18n_2ce44aba57":"log directory", + "i18n_2d05c9d012":"Key words, support regularization", + "i18n_2d2238d216":"Account added successfully", + "i18n_2d3fd578ce":"Are you sure you want to get a batch deselected build? Note: Canceling/stopping a build may not necessarily shut down all associated processes normally", + "i18n_2d455ce5cd":"Downloading", + "i18n_2d58b0e650":"Select the build tab, not the latest commit", + "i18n_2d7020be7d":"For example, the common .env file", + "i18n_2d711b09bd":"content", + "i18n_2d842318fb":"cycle", + "i18n_2d94b9cf0e":"Dockerfile build method does not support rollback here", + "i18n_2d9569bf45":"Parameter value, after adding default parameters, you need to fill in the parameter value when manually executing the script", + "i18n_2d9e932510":"new directory", + "i18n_2de0d491d0":"hour", + "i18n_2e0094d663":"Do you really want to delete the cluster information? 1", + "i18n_2e1f215c5d":"Automatically create users", + "i18n_2e505d23f7":"Download import template", + "i18n_2e51ca19eb":"If the node option is disabled, it means that there is a recommended associated node for the corresponding data (this may happen in lower version project data)", + "i18n_2e740698cf":"cluster IP", + "i18n_2ea7e70e87":"The command file will be uploaded to {'${user.home}'}/.jpom/xxxx.sh will be automatically deleted upon completion", + "i18n_2ef1c35be8":"Executing CPU", + "i18n_2f4aaddde3":"delete", + "i18n_2f5e828ecd":"alias code", + "i18n_2f5e885bc6":"Get a single build log address", + "i18n_2f67a19f9d":"You need to select the corresponding service name published to the cluster, and you need to go to the cluster in advance to create a service.", + "i18n_2f6989595f":"Manage the list:", + "i18n_2f8d6f1584":"Yesterday", + "i18n_2f8dc4fb66":"Do you really want to release the distribution information? After the release, the project information under the node will still be retained. If you want to delete the project, you need to go to the node management.", + "i18n_2f8fd34058":"Script templates are command scripts stored at the server level to manage some script commands online, such as initializing the software environment, managing applications, etc", + "i18n_2f97ed65db":"occupy", + "i18n_2fc0d53656":"Machine state (cache)", + "i18n_2ff65378a4":"Do you really want to delete the SSH of the corresponding workspace?", + "i18n_2fff079bc7":"Published successfully", + "i18n_3006a3da65":"System version:", + "i18n_300fbf3891":"Stop before publishing means to close the project when publishing the file to the project file, and then replace the file. Avoid file occupation in the windows environment", + "i18n_302ff00ddb":"super administrator", + "i18n_3032257aa3":"Details", + "i18n_30849b2e10":"Process/Port", + "i18n_30aaa13963":"Serial Number (SN)", + "i18n_30acd20d6e":"user ID", + "i18n_30d9d4f5c9":"new connection", + "i18n_30e6f71a18":"Custom label wildcard expression", + "i18n_30e855a053":"Cancel distribution", + "i18n_30ff009ab3":"#Java mirror source https://mirrors.tuna.tsinghua.edu.cn/Adoptium/", + "i18n_3103effdfd":"Please enter your account name", + "i18n_31070fd376":"manual rollback", + "i18n_310c809904":"Bind to the current workspace", + "i18n_312e044529":" : Range: 0 (Sunday)~ 6 (Saturday), 7 can also represent Sunday, and supports case-insensitive aliases: _ ##_sun \",\" mon \",\" tue \",\" wed \",\" thu \",\" fri \",\" sat \",", + "i18n_312f45014a":"Creation time:", + "i18n_314f5aca4e":"In a single trigger address: the first random string is the build ID, and the second random string is the token.", + "i18n_315eacd193":"Move Up", + "i18n_31691a647c":"{Slot1} port", + "i18n_3174d1022d":"Container Construction Notes", + "i18n_3181790b4b":"Server level system configuration", + "i18n_318ce9ea8b":"User password prompt", + "i18n_31aaaaa6ec":"Build ID:", + "i18n_31ac8d3a5d":"thread synchronizer", + "i18n_31bca0fc93":"Joining the beta program can get the latest features, some optimized features, and the fastest bug-fixing version in time, but the beta version may also be unstable in some new features. You need to evaluate whether you can join the beta according to your business situation. If you encounter problems during the use of the beta version, you can give us feedback at any time, and we will answer you as soon as possible.", + "i18n_31eb055c9c":"Parallelism, the number of containers upgraded at the same time", + "i18n_31ecc0e65b":"project", + "i18n_32112950da":"Bulk Cancel", + "i18n_3241c7c05f":"It is recommended to use server level scripts to distribute to scripts:", + "i18n_32493aeef9":"Under construction", + "i18n_329e2e0b2e":"Specify directory packaging yarn & & yarn --cwd xxx build", + "i18n_32a19ce88b":"Console log path", + "i18n_32ac152be1":"update", + "i18n_32c65d8d74":"title", + "i18n_32cb0ec70e":"Please enter a node name", + "i18n_32d0576d85":"token", + "i18n_32dcc6f36e":"Restart strategy: no, always, less-stopped, on-failure", + "i18n_32e05f01f4":"cluster information", + "i18n_32f882ae24":"Matches zero or more characters", + "i18n_330363dfc5":"success", + "i18n_3306c2a7c7":"read default", + "i18n_33130f5c46":"Operation successful", + "i18n_3322338140":"Please select Post-Publish Action", + "i18n_332ba869d9":"Generally used in cases where the node environment is consistent", + "i18n_334a1b5206":"Install node", + "i18n_335258331a":"The default configuration file has been read into the editor", + "i18n_33675a9bb3":"The docker information associated with the cluster is lost, and the management function cannot continue to be used", + "i18n_339097ba2e":"Ready to distribute", + "i18n_33c9e2388e":"Project ID", + "i18n_3402926291":"Current log file size:", + "i18n_346008472d":"Matches lines containing exceptions", + "i18n_3477228591":"mirror image", + "i18n_35134b6f94":"View node script", + "i18n_3517aa30c2":"Variables supported in the script are: {'${PROJECT_ID }'}、{'${ PROJECT_NAME }'}、{'${ PROJECT_PATH}'}", + "i18n_353707f491":"You can go to [Node Distribution] = > [Distribution Authorization Configuration] to modify it.", + "i18n_353c7f29da":"Please select the template node", + "i18n_35488f5ba8":"Please select a node item", + "i18n_354a3dcdbd":"Every 30 seconds", + "i18n_3574d38d3e":"Remaining memory:", + "i18n_35b89dbc59":"Are you sure you want to download the latest version?", + "i18n_35cb4b85a9":"[Currently only the first item matched is used]", + "i18n_35fbad84cb":"Description The first one in ascending order according to the creation time", + "i18n_3604566503":"Please fill in the container address.", + "i18n_364bea440e":"Please select the script to reference", + "i18n_368ffad051":"{Slot1} Table of Contents", + "i18n_36b3f3a2f6":"Alarm title", + "i18n_36b5d427e4":"Please enter a workspace description", + "i18n_36d00eaa3f":"Differential construction:", + "i18n_36d4046bd6":"Reference script template", + "i18n_36df970248":"#version needs to exist in the corresponding mirror source", + "i18n_3711cbf638":"pre-occupied resources", + "i18n_37189681ad":"Data Id", + "i18n_373a1efdc0":"Please select the item you want to close", + "i18n_374cd1f7b7":"Create a cluster", + "i18n_375118fad1":"Physical Node Script Template Data:", + "i18n_375f853ad6":"Hardware information", + "i18n_3787283bf4":"Do you really want to delete the current file?", + "i18n_37b30fc862":"Please select a skin.", + "i18n_37c1eb9b23":"configuration file path", + "i18n_37f031338a":"Upload the compressed package and automatically decompress it.", + "i18n_37f1931729":"Data directory occupies space:", + "i18n_384f337da1":"Synchronization mechanism is adopted", + "i18n_3867e350eb":"environment variables", + "i18n_386edb98a5":"Custom script projects (python, nodejs, go, interface exploration, es) [recommended]", + "i18n_38a12e7196":"Select certificate file", + "i18n_38aa9dc2a0":"More configurations", + "i18n_38ce27d846":"Next step", + "i18n_38cf16f220":"OK.", + "i18n_38da533413":"The following command will be", + "i18n_3904bfe0db":"Set up a super administrator account", + "i18n_3929e500e0":"It is often the case that some operations such as migrating workspaces, migrating physical machines, etc. for projects may generate lonely data", + "i18n_396b7d3f91":"file size", + "i18n_398ce396cd":"Workspace synchronization", + "i18n_39b68185f0":"The node address is the IP: PORT of the plug-in side. The default port of the plug-in side is: 2123.", + "i18n_39c7644388":"port number", + "i18n_39e4138e30":"Cluster creation time:", + "i18n_39f1374d36":"time consuming", + "i18n_3a1052ccfc":"Reference environment variables", + "i18n_3a17b7352e":"minute", + "i18n_3a3778f20c":"Task ID", + "i18n_3a3c5e739b":"batch build parameters", + "i18n_3a3ff2c936":"volume label", + "i18n_3a536dcd7c":"126 mailboxes", + "i18n_3a57a51660":"Script version: {item}", + "i18n_3a6000f345":"Running thread synchronizer", + "i18n_3a6970ac26":"file sharing", + "i18n_3a6bc88ce0":"Do you really want to delete the file?", + "i18n_3a6c2962e1":"Key algorithm", + "i18n_3a71e860a7":"Current end point not open", + "i18n_3a94281b91":"Free scripting refers to the execution of arbitrary scripts directly in the machine node", + "i18n_3aa69a563b":"Node distribution refers to the fact that a project needs to run in multiple nodes (servers), and node distribution is used to manage the project uniformly (distributed project management functions can be realized).", + "i18n_3ac34faf6d":"wild-card", + "i18n_3adb55fbb5":"Migrate workspace", + "i18n_3ae4c953fe":"When the timed task has run to a time that matches these expressions, the task is started.", + "i18n_3ae4ddf245":"Do you really want to delete the Docker? Deleting only checks the data associations of the local system, not the data in the docker container", + "i18n_3aed2c11e9":"automatic", + "i18n_3b14c524f6":"number of reads", + "i18n_3b19b2a75c":"Do you really want to delete the script?", + "i18n_3b885fca15":"cache version number", + "i18n_3b9418269c":"Please fill in the associated container label", + "i18n_3b94c70734":"project status", + "i18n_3ba621d736":"Processing successful", + "i18n_3baa9f3d72":"Batch build parameters also support specified parameters, delay (delayed execution of builds, in seconds) branchName (branch name), branchTagName (tag), script (build script), resultDirFile (bundle), webhook (notification webhook)", + "i18n_3bc5e602b2":"email", + "i18n_3bcc1c7a20":"Last Modifier", + "i18n_3bdab2c607":"10 Minutes", + "i18n_3bdd08adab":"describe", + "i18n_3bf3c0a8d6":"Node", + "i18n_3bf9c5b8af":" Group name:", + "i18n_3c014532b1":"Construction time:", + "i18n_3c070ea334":"If the associated build, the associated repository is bound (used) by multiple builds and cannot be migrated", + "i18n_3c48d9b970":"Batch build parameter BODY json : [ { \" id \":\" 1 \",\" token \":\" a \"}]", + "i18n_3c586b2cc0":"Custom host", + "i18n_3c6248b364":"cache information", + "i18n_3c6fa6f667":"Cron expression", + "i18n_3c8eada338":"Please select an encoding method", + "i18n_3c91490844":"publish action", + "i18n_3c99ea4ec2":"For example, in 2, 3, 6/3, since \"/\" has a high priority, it is equivalent to 2, 3, (6/3), and the result is equivalent to 2, 3, 6", + "i18n_3c9eeee356":"Do you really want to delete log files?", + "i18n_3cc09369ad":"Really want to delete [", + "i18n_3d06693eb5":"Resources:", + "i18n_3d0a2df9ec":"parameter", + "i18n_3d3b918f49":"execute build", + "i18n_3d3d3ed34c":"Please enter Select Association Group", + "i18n_3d43ff1199":"top", + "i18n_3d48c9da09":"authorization configuration", + "i18n_3d61e4aaf1":"Specify label", + "i18n_3d6acaa5ca":"This container has no network", + "i18n_3d83a07747":"Host", + "i18n_3dc5185d81":"private", + "i18n_3dd6c10ffd":"Upload upgrade package", + "i18n_3e445d03aa":"File doesn't exist", + "i18n_3e51d1bc9c":"Please select the published SSH.", + "i18n_3e54c81ca2":"receive traffic", + "i18n_3e7ef69c98":"Monitor operation", + "i18n_3e8c9c54ee":"Select group", + "i18n_3ea6c5e8ec":"End of distribution", + "i18n_3eab0eb8a9":"local script", + "i18n_3ed3733078":"End point log", + "i18n_3edddd85ac":"day", + "i18n_3ee7756087":"Please select the node first.", + "i18n_3f016aa454":"Mirror tag:", + "i18n_3f18d14961":"2-step verification code", + "i18n_3f1d478da4":"Server level scripts, SSH scripts can be referenced using G {'@'}(\" xxxx \") format, and the system will automatically replace the script content in the referenced script library when there is a reference", + "i18n_3f2d5bd6cc":"Search in file lines 2 - 2", + "i18n_3f414ade96":"Parameter description, {slot1}, is only used to hint at the meaning of the parameter", + "i18n_3f553922ae":"Directories and files?", + "i18n_3f5af13b4b":"#scriptId can be the name of the script file in the project path or the script template ID in the system", + "i18n_3f719b3e32":"number of conflicts", + "i18n_3f78f88499":"Packing time:", + "i18n_3f8b64991f":"Automatically remove the excess folder names in the compressed package when decompressing.", + "i18n_3f8cedd1d7":"For static file binding and reading (it is not recommended to configure large directories to avoid scanning consuming too many resources)", + "i18n_3fb2e5ec7b":"login log", + "i18n_3fb63afb4e":"exit code", + "i18n_3fbdde139c":"Confirm password", + "i18n_3fca26a684":"Batch trigger parameter BODY json : [ { \" id \":\" 1 \",\" token \":\" a \"}]", + "i18n_3fea7ca76c":"state", + "i18n_402d19e50f":"login", + "i18n_40349f5514":"Number:", + "i18n_4055a1ee9c":"Common fields are: createTimeMillis, modifyTimeMillis", + "i18n_406a2b3538":"What is solitary data?", + "i18n_4089cfb557":"Associated grouping is mainly used for asset monitoring to achieve different server levels to perform asset monitoring under different groupings", + "i18n_40aff14380":"mirror ID", + "i18n_40da3fb58b":"new state", + "i18n_40f8c95345":"Temporary file directory", + "i18n_411672c954":"Please enter a file description", + "i18n_412504968d":"When there is no corresponding SSH in the target workspace, a new SSH will be automatically created.", + "i18n_41298f56a3":"Build failed", + "i18n_413d8ba722":"Legacy packages take up space:", + "i18n_413f20d47f":"The system uses the oshi library to monitor the system, and uses /proc/meminfo in oshi to obtain memory usage.", + "i18n_41638b0a48":"Used to distinguish whether files are of the same type, download management can be performed for the same type", + "i18n_417fa2c2be":"Parameter {index} description", + "i18n_4188f4101c":"No docker.", + "i18n_41d0ecbabd":"Block IO weight", + "i18n_41e8e8b993":"dark", + "i18n_41e9f0c9c6":"worker node", + "i18n_41fdb0c862":"Please upload or download the new version first.", + "i18n_4244830033":"Please select a certificate file", + "i18n_424a2ad8f7":"prepare", + "i18n_429b8dfb98":"project distribution", + "i18n_42a93314b4":"base image", + "i18n_42b6bd1b2f":"Warehouse path", + "i18n_42f766b273":"mount partition", + "i18n_42fd64c157":"Start first", + "i18n_4310e9ed7d":"Please select how the project will run", + "i18n_43250dc692":"trigger management", + "i18n_434d888f6f":"Please select the file in the file center", + "i18n_434d9bd852":"After creating a user, automatically associate the corresponding permission group", + "i18n_4360e5056b":"Loading data", + "i18n_436367b066":"Project Management", + "i18n_4371e2b426":"Please enter a project name", + "i18n_43886d7ac3":"New operating parameters", + "i18n_4393b5e25b":"loopback", + "i18n_43c61e76e7":"Note: Currently ssh-keygen -t rsa -C is not supported for SSH key access to git repository addresses.", + "i18n_43d229617a":"To be selected", + "i18n_43e534acf9":"loose", + "i18n_43ebf364ed":"Please select a backup type", + "i18n_4403fca0c0":"clear", + "i18n_44473c1406":"Open the cache build directory will keep the repository files, the second build will pull the code, and if the cache directory is not opened, the repository code will be re-pulled for each build (it is not recommended to close the cache for larger projects). Special instructions If the cache directory is missing version control related files will be automatically deleted and then re-pulled code", + "i18n_4482773688":"Please enter a permission group name", + "i18n_44876fc0e7":"If not, it means that the corresponding user has not configured a mailbox.", + "i18n_449fa9722b":"In order to consider system security, we strongly recommend that the super administrator enable two-step verification to ensure the security of the account", + "i18n_44a6891817":"new build", + "i18n_44c4aaa1d9":"operating mode", + "i18n_44d13f7017":"limited time", + "i18n_44ed625b19":"network exception", + "i18n_44ef546ded":"Project monitoring [Migration is not supported for the time being]", + "i18n_44efd179aa":"log out", + "i18n_45028ad61d":"certificate password", + "i18n_4524ed750d":"Workspace name", + "i18n_456d29ef8b":"log", + "i18n_458331a965":"Are you sure you want to upload the file to update to the latest version?", + "i18n_45a4922d3f":"Linked Data", + "i18n_45b88fc569":"Match zero or more directories in the path", + "i18n_45f8d5a21d":"Do you really want to delete the user?", + "i18n_45fbb7e96a":"Project Loneliness Data", + "i18n_46032a715e":"No build method selected yet", + "i18n_4604d50234":"error message", + "i18n_46097a1225":"Solitary data correction", + "i18n_46158d0d6e":"Disable monitoring", + "i18n_461e675921":"The current data is the default state. Moving up or down after the operation may not achieve the expected sorting. You also need to operate on the relevant data to achieve the expected sorting.", + "i18n_461ec75a5a":"Path:", + "i18n_461fdd1576":"Package production environment package mvn clean package -Dmaven.test.skip = true -Pprod", + "i18n_4637765b0a":"Not enabled", + "i18n_463e2bed82":"batch update", + "i18n_4642113bba":"Click on the dashboard to view the monitoring historical data", + "i18n_4645575b77":"Workspace description", + "i18n_464f3d4ea3":"role", + "i18n_465260fe80":"year", + "i18n_4696724ed3":"trigger", + "i18n_46a04cdc9c":"File description:", + "i18n_46aca09f01":"Unbinding will check the data correlation and will not actually request the node to delete the project information.", + "i18n_46ad87708f":"SSH name", + "i18n_46c8ba7b7f":"If the button is not available, please go to the association of the asset management ssh list to add the authorization folder allowed to be managed in the current workspace", + "i18n_46e3867956":"in progress", + "i18n_46e4265791":"Build ID", + "i18n_4705b88497":"scope", + "i18n_47072e451e":"Management Node:", + "i18n_470e9baf32":"Memory nodes allowed to execute", + "i18n_471c6b19cf":"Before migration, you check the connection status and network status of the outgoing and incoming machines to avoid unknown errors or interruptions that cause process failure and generate redundant data!!!!", + "i18n_4722bc0c56":"end point", + "i18n_473badc394":"published node", + "i18n_4741e596ac":"alarm time", + "i18n_475a349f32":"The current build has not generated a trigger", + "i18n_475cd76aec":"Statistical network interface cards:", + "i18n_47768ed092":"Extremely unsafe", + "i18n_47bb635a5c":"Data may have a time lag", + "i18n_47d68cd0f4":"service", + "i18n_47dd8dbc7d":"Search project ID", + "i18n_47e4123886":"new distribution", + "i18n_47ff744ef6":"Edit file", + "i18n_481ffce5a9":"match second", + "i18n_4826549b41":"Command templates are used to manage some scripting commands online, such as initializing software environments, managing applications, etc", + "i18n_48281fd3f0":"Do you really want to delete the build information? Deleting will also delete all build history information simultaneously", + "i18n_4838a3bd20":"Press and hold the Ctr or Alt/Option keys and click the button to quickly return to the first page", + "i18n_4871f7722d":"task update time", + "i18n_48735a5187":"Free space (unallocated)", + "i18n_48a536d0bb":"Modify the container configuration and rerun it", + "i18n_48d0a09bdd":"Light color", + "i18n_48e79b3340":"] Documents?", + "i18n_48fe457960":"(There are compatibility problems, you need to test in advance in actual use) go sdk image use: https://studygolang.com/dl/golang/go {'${GO_VERSION}'} .linux- {'${ARCH}'} .tar.gz", + "i18n_4956eb6aaa":"load", + "i18n_49574eee58":"Are you sure you want to operate?", + "i18n_49645e398b":"If the configuration is wrong, you need to restart the server level and add the command line parameter --rest: ip_config will restore the default configuration", + "i18n_497bc3532b":"JVM parameters", + "i18n_497ddf508a":"Create a new blank file", + "i18n_498519d1af":"refresh data", + "i18n_499f058a0b":"Logout successful", + "i18n_49a9d6c7e6":"Make a one-time donation sponsorship through the following QR code and invite the author to have a cup of coffee☕️", + "i18n_49d569f255":"Please enter the host to check", + "i18n_49e56c7b90":"Confirm modification", + "i18n_4a00d980d5":"Simple and easy to use", + "i18n_4a0e9142e7":"DingTalk", + "i18n_4a346aae15":"Plugin version:", + "i18n_4a4e3b5ae4":"Description:", + "i18n_4a5ab3bc72":"Operation:", + "i18n_4a6f3aa451":" : Executed at 5 minutes of each o'clock, 00:05, 01:05...", + "i18n_4a98bf0c68":"Task details", + "i18n_4aac559105":"weight", + "i18n_4ab578f3df":"Environment variables:", + "i18n_4ad6e58ebc":"Machine SSH", + "i18n_4af980516d":"For the security of your account, the system requires that two-step verification must be turned on to ensure the security of the account.", + "i18n_4b027f3979":"remind", + "i18n_4b0cb10d18":"Please enter SMTP host", + "i18n_4b1835640f":"Get it in Settings-- > Developer settings-- > Personal access tokens", + "i18n_4b386a7209":"Get variable value address", + "i18n_4b404646f4":"Container labels, such as: key1 = values1 & keyvalue2", + "i18n_4b5e6872ea":"resident set", + "i18n_4b96762a7e":"Last modification time", + "i18n_4b9c3271dc":"reset", + "i18n_4ba304e77a":"DingTalk account login", + "i18n_4bbc09fc55":"Search on file lines 3 - 20", + "i18n_4c096c51a3":"Port number:", + "i18n_4c0eead6ff":"new parameter", + "i18n_4c28044efc":"Confirm that the selected one ", + "i18n_4c69102fe1":"Then determine the allowable period. After configuring the allowable period, the user can only perform the operation of the corresponding function in the corresponding period", + "i18n_4c7c58b208":"Please select the node status", + "i18n_4c7e4dfd33":"When there is no corresponding node in the target workspace, a new docker (logical docker) will be automatically created.", + "i18n_4c83203419":"Jump to a third-party system", + "i18n_4c9bb42608":"prefix", + "i18n_4cbc136874":"Folder:", + "i18n_4cbc5505c7":"Differential build refers to whether the warehouse code has changed during the build, and if there is no change, the build will not be executed", + "i18n_4ccbdc5301":"menu", + "i18n_4cd49caae4":"distribution time", + "i18n_4ce606413e":"Warehouse type", + "i18n_4cfca88db8":"Select distribution file", + "i18n_4d18dcbd15":"Do you really want to restore backup information?", + "i18n_4d351f3c91":"IP ban", + "i18n_4d49b2a15f":"Automatic execution: docker", + "i18n_4d775d4cd7":"show", + "i18n_4d7dc6c5f8":"write", + "i18n_4d85ac1250":"system management", + "i18n_4d85c37f0d":"Workspace:", + "i18n_4d9c3a0ed0":"Script content", + "i18n_4dc781596b":"We use the following open-source software, and we are grateful that their open-source Jpom can be improved", + "i18n_4df483b9c7":"project file ", + "i18n_4e33dde280":"Current directory:", + "i18n_4e54369108":"File type does not have trigger function", + "i18n_4e7e04b15d":"Service name required", + "i18n_4ed1662cae":"Please select a connection method", + "i18n_4ee2a8951d":"Interface response ContentType is: text/plain", + "i18n_4ef719810b":"There are no active tasks", + "i18n_4effdeb1ff":"Search in file lines 1-2", + "i18n_4f08d1ad9f":"Algorithm OID", + "i18n_4f095befc0":"This configuration is only valid for server level administration, the ssh configuration of the workspace needs to be configured separately", + "i18n_4f35e80da6":"path", + "i18n_4f4c28a1fb":"File content format requirements: env_name = xxxxx Lines that do not meet the format will be automatically ignored", + "i18n_4f50cd2a5e":"Compact Mode", + "i18n_4f52df6e44":"Closing", + "i18n_4f8a2f0b28":"not running", + "i18n_4f8ca95e7b":"name", + "i18n_4f9e3db4b8":"Choose to build", + "i18n_4fb2400af7":"The container is running and can enter the end point", + "i18n_4fb95949e5":"Opening", + "i18n_4fdd2213b5":"Project ID", + "i18n_500789168c":"Empty and restore will first delete the files in the project directory and then restore the corresponding backup file to the current directory", + "i18n_5011e53403":"release cluster", + "i18n_503660aa89":"Exclude:", + "i18n_50411665d7":"number of reservations", + "i18n_50453eeb9e":"The current workspace has no logical nodes and cannot create node scripts.", + "i18n_504c43b70a":"Port/PID", + "i18n_5068552b18":"historical monitoring chart", + "i18n_50940ed76f":"Download successful", + "i18n_50951f5e74":"Please select a branch", + "i18n_50a299c847":"build name", + "i18n_50c7929dd9":" Welcome ", + "i18n_50d2671541":"Sure it's the same script", + "i18n_50ed14e70b":"Dark dracula", + "i18n_50f472ee4e":"Unit seconds, default 10 seconds, minimum 3 seconds", + "i18n_50f975c08e":"The number of days the bundle is retained, less than or equal to 0 is to follow the global retention configuration. Note that automatic cleaning will only clean the data with the record status of: (end of build, in release, failed release, failed release) to avoid some abnormal builds affecting the number of reservations", + "i18n_50fb61ef9d":"script name", + "i18n_50fe3400c7":"Do you really want to delete this execution record?", + "i18n_50fefde769":"Is it a compressed package?", + "i18n_512e1a7722":"Please select an operator", + "i18n_51341b5024":"Scripts distributed at the server level", + "i18n_514b320d25":"How to choose a construction method", + "i18n_5169b9af9d":"information loss", + "i18n_5177c276a0":"Clusters cannot be created manually, creating requires multiple server levels to use a database, and configuring different cluster IDs to automatically create cluster information", + "i18n_518df98392":"Search from the end", + "i18n_5195c0d198":"Can manage {count} workspaces", + "i18n_51c92e6956":"synchronization system configuration", + "i18n_51d47ddc69":"Callback URL", + "i18n_51d6b830d4":"Online build directory", + "i18n_52409da520":"contact person", + "i18n_527466ff94":"request parameters", + "i18n_527f7e18f1":"Please read the instructions and precautions in the update log before uploading and before updating", + "i18n_52a8df6678":"Is it a folder?", + "i18n_52b526ab9e":"Empty the browser cache configuration will restore the default.", + "i18n_52b6b488e2":"The script template is stored in the node (plug-in side), and the execution will also be executed in the node. The server level will regularly pull the execution log, and the pulling frequency is 100 pieces/minute", + "i18n_52c6af8174":"Please enter client side key [clientSecret]", + "i18n_52d24791ab":"Do you really want to delete these files?", + "i18n_52eedb4a12":"alarm method", + "i18n_52ef46c618":"Do not publish: only execute the build process and save the build history", + "i18n_532495b65b":"number of copies", + "i18n_53365c29c8":"Download status:", + "i18n_534115e981":"Incomplete information cannot be edited", + "i18n_5349f417e9":"Search keywords", + "i18n_536206b587":"The current machine has not monitored any data", + "i18n_537b39a8b5":"Required", + "i18n_53bdd93fd6":"View script library", + "i18n_541e8ce00c":"Regarding open-source software", + "i18n_542a0e7db4":"synchronous authorization", + "i18n_543296e005":"Please enter the authorization url [authorizationUri]", + "i18n_543a5aebc8":"Is it really necessary to delete the current variable?", + "i18n_543de6ff04":"distribution status message", + "i18n_54506fe138":"Reset selection", + "i18n_5457c2e99f":"#Use copy file to cache, otherwise use soft chain. copy file to cache node_modules can avoid npm WARN reify Removing non-directory", + "i18n_547ee197e5":"new directory", + "i18n_5488c40573":"Node project", + "i18n_54f271cd41":"Script template", + "i18n_5516b3130c":"Feishu account login", + "i18n_551e46c0ea":" Name: ", + "i18n_55405ea6ff":"export", + "i18n_556499017a":"The project file will be stored in", + "i18n_5569a840c8":"Please enter IP disallowed, multiple use newlines, support to configure IP segments 192.168.1.1/192.168.1.254, 192.168.1.0/24", + "i18n_55721d321c":"parameter description", + "i18n_55939c108f":"Enter a file or folder name", + "i18n_55abea2d61":"server level", + "i18n_55b2d0904f":"It is used when performing multi-node distribution. Sequential restart and complete sequential restart need to ensure that the project can be restarted normally.", + "i18n_55cf956586":"Join the cluster", + "i18n_55d4a79358":"The configuration needs to declare the use of a specific docker to perform build-related operations (it is recommended to use the docker in the server where the server level is located)", + "i18n_55da97b631":" The range is 0 to 59, but the first bit does not match. When it is 7 bits, the last bit indicates", + "i18n_55e690333a":"There is no Docker cluster in the current workspace", + "i18n_55e99f5106":"DingTalk notification address", + "i18n_55f01e138a":"WeChat Appreciation", + "i18n_56071a4fa6":"timeout", + "i18n_56230405ae":"Unbinding does not actually request the node to delete the script information.", + "i18n_562d7476ab":"Sunday", + "i18n_56469e09f7":"Please go to [System Administration] - > [Asset Management] - > [Docker Management] to add Docker, or associate and assign the newly added Docker authorization to this workspace", + "i18n_56525d62ac":"scan", + "i18n_566c67e764":"Machines that have been assigned to the workspace are nothing more than deleted directly. You need to delete each of the assigned workspaces one by one before deleting the asset machine.", + "i18n_5684fd7d3d":"The new password for the account is:", + "i18n_56bb769354":"Please read the instructions and precautions in the update log before downloading and before updating", + "i18n_56d9d84bff":"Number of items in the logical nodes in the workspace:", + "i18n_570eb1c04f":"Hard disk occupancy:", + "i18n_5734b2db4e":"number of rows read", + "i18n_576669e450":"Please select the project you want to start", + "i18n_5785f004ea":"Please do not manually delete the files below the data directory. If you need to delete it, you need to back it up in advance or it has been determined that the corresponding file is deprecated before deleting it.", + "i18n_578adf7a12":"Please confirm the configuration carefully, and it will take effect immediately after the ip configuration. When configuring, you need to ensure that the current ip can access! 127.0.0.1 the IP is not restricted by access. Support configuring IP segments 192.168.1.1/192.168.1.254, 192.168.1.0/24", + "i18n_578ca5bcfd":"163 mailboxes", + "i18n_57978c11d1":"The log pop-up window will not open in full screen.", + "i18n_579a6d0d92":"command value", + "i18n_57b7990b45":"When SSH already exists in the target workspace, SSH account, password, private key and other information will be automatically synchronized", + "i18n_57c0a41ec6":"The current data is the default state", + "i18n_57cadc4cf3":"Will use PING check", + "i18n_5805998e42":"restart strategy", + "i18n_5854370b86":"trace file", + "i18n_585ae8592f":"Rebuild container", + "i18n_5866b4bced":"Number of clusters:", + "i18n_587a63264b":"overlay restore", + "i18n_588e33b660":"If the account is enabled for MFA (two-step verification), logging in with Oauth2 will not verify MFA (two-step verification).", + "i18n_589060f38e":"Upgrading, please wait...", + "i18n_5893fa2280":"email account", + "i18n_58cbd04f02":"SSH refers to the release of products through SSH commands or the execution of multiple commands to achieve release (you need to add new ones in SSH in advance)", + "i18n_58e998a751":"Deletion checks for data relevance, and no project or script exists on the node", + "i18n_58f9666705":"size", + "i18n_590b9ce766":"Currently supported plugins are available (more plugins are expected):", + "i18n_590dbb68cf":"End time:", + "i18n_590e5b46a0":"Automatic backup", + "i18n_592c595891":"start time", + "i18n_5936ed11ab":"The script library is used to store and manage general-purpose scripts, and the scripts in the script library cannot be executed directly.", + "i18n_593e04dfad":"Menu theme", + "i18n_597b1a5130":"update status", + "i18n_59a15a0848":"The synchronization mechanism adopts IP + PORT + connection to confirm that it is the same server.", + "i18n_59c316e560":"distribution file", + "i18n_59c75681b4":"Notification object", + "i18n_59d20801e9":"Search on file lines 17 - 20", + "i18n_5a0346c4b1":"Edit user", + "i18n_5a1367058c":"Back to Home", + "i18n_5a1419b7a2":"data name", + "i18n_5a42ea648d":"Self-built gitlab access address", + "i18n_5a5368cf9b":"Wrong password", + "i18n_5a63277941":"The values are: stop, beforeStop, start, beforeRestart, fileChange", + "i18n_5a7ea53d18":"Docker information", + "i18n_5a8727305e":"Please do not exit the management node first.", + "i18n_5a879a657b":"swap memory", + "i18n_5aabec5c62":"Parent ID", + "i18n_5ab90c17a3":"End of mission", + "i18n_5ad7f5a8b2":"result", + "i18n_5afe5e7ed4":"Edit associated item", + "i18n_5b1f0fd370":"Used to create node distribution projects, file center publishing files", + "i18n_5b3ffc2910":"Distributing", + "i18n_5b47861521":"Name:", + "i18n_5baaef6996":"Click to resynchronize the current workspace logic node script template information", + "i18n_5badae1d90":"There is no script.", + "i18n_5bb162ecbb":"JVM Remaining Memory", + "i18n_5bb5b33ae4":"So here we are.", + "i18n_5bca8cf7ee":"Custom host, xxx: 192.168.0.x", + "i18n_5bcda1b4d7":"Session has been closed [system-log]", + "i18n_5bd1d267a9":"Get it in preferences-- > Access Tokens", + "i18n_5c3b53e66c":"Modify file", + "i18n_5c4d3c836f":"MFA verification is required.", + "i18n_5c502af799":"Container name required", + "i18n_5c56a88945":"deactivate", + "i18n_5c89a5353d":"distribution node", + "i18n_5c93055d9c":"Generally, the server cannot be connected and it has been determined that it is no longer in use.", + "i18n_5ca6c1b6c7":"Please fill in the cluster name", + "i18n_5cb39287a8":"monitoring function", + "i18n_5cc7e8e30a":"Modify file permissions", + "i18n_5d07edd921":"Please fill in the cluster IP.", + "i18n_5d14e91b01":"Primary ID", + "i18n_5d368ab0a5":"Execution commands are automatically replaced with sh command files and environment variables are automatically loaded:/etc/profile,/etc/bashrc,~/.bashrc,~/. bash_profile", + "i18n_5d414afd86":"Search from the end, the first 2 lines of the file, and the last 3 lines of the file", + "i18n_5d459d550a":"Processing", + "i18n_5d488af335":"Remote download file", + "i18n_5d5fd4170f":"The values are: 1.", + "i18n_5d6f47d670":"The project is a static folder", + "i18n_5d803afb8d":"Cannot communicate normally with the node", + "i18n_5d817c403e":"No data was selected.", + "i18n_5d83794cfa":"Node name:", + "i18n_5d9c139f38":"content theme", + "i18n_5dc09dd5bd":"Reconnect ", + "i18n_5dc1f36a27":"certificate description", + "i18n_5dc78cb700":"The number of bundle reservations, less than or equal to 0 is to follow the global reserved configuration (if the value is greater than 0, it will be compared with the minimum value of the global configuration for reference). Note that automatic cleaning will only clean the data with the record status of: (build end, release in progress, release failed, release failed) Avoid some abnormal builds affecting the number of reservations. The number of reservations will be checked when creating a new build record", + "i18n_5dc7b04caa":"Number of processes viewed", + "i18n_5dff0d31d0":"If you need to execute it automatically, fill in the cron expression. The second level is not turned on by default, you need to modify the configuration file: [system.timerMatchSecond])", + "i18n_5e32f72bbf":"Refresh file table", + "i18n_5e46f842d8":"Monitor users", + "i18n_5e9f2dedca":"Was it successful?", + "i18n_5ecc709db7":"All environment variables are not loaded by default during execution, and they need to be loaded by themselves in the script.", + "i18n_5ed197a129":"Reset initialization pass parameters at startup", + "i18n_5ef040a79d":"discard packet", + "i18n_5ef72bdfce":"Command content supports workspace environment variables", + "i18n_5effe31353":"cull folder", + "i18n_5f4c724e61":"Please enter a task name", + "i18n_5f5cd1bb1e":"The new associated project refers to associating the project that has been created in the node to distribute the project to the node to achieve unified management", + "i18n_5fafcadc2d":"Session has been closed [node-script-consloe]", + "i18n_5fbde027e3":"You can refer to the environment variables variable placeholder {'${xxxx}'} xxxx of the workspace as the variable name", + "i18n_5fc6c33832":" jump to line", + "i18n_5fea80e369":"No assets DOCKER", + "i18n_5fffcb255d":"plugin run", + "i18n_601426f8f2":"Push to warehouse", + "i18n_603dc06c4b":"The page you are visiting does not exist", + "i18n_60585cf697":" Welcome", + "i18n_607558dbd4":"number of items", + "i18n_607e7a4f37":"view", + "i18n_609b5f0a08":"time", + "i18n_60b4c08f5c":"Are you sure you want to stop the current container?", + "i18n_6106de3d87":"JDK version", + "i18n_61341628ab":" : representation list", + "i18n_6143a714d0":"encoding format", + "i18n_616879745d":"0:00 am and 12:00 noon", + "i18n_61955b0e4b":"No project status and control functions", + "i18n_61a3ec6656":"introduce", + "i18n_61bfa4e925":"You need the dockerfile in the repository. If you view multiple folders, you can specify a secondary directory. If springboot-test-jar: springboot-test-jar/Dockerfile", + "i18n_61c0f5345d":"SMTP address: [smtp.163.com, smtp.126.com...], the password is the email authorization code, the default port is 25, and the SSL port is 465.", + "i18n_61e7fa1227":"Edit Node", + "i18n_61e84eb5bb":"Start time:", + "i18n_620489518c":"Parameter {index} value", + "i18n_620efec150":"More open source instructions", + "i18n_62170d5b0a":"Search Reference", + "i18n_6228294517":"Menu configuration only works for non-super administrators", + "i18n_622d00a119":"Path to execute the script", + "i18n_624f639f16":"Universal mailbox", + "i18n_625aa478e2":"Search from the end, 0 lines before the file, 3 lines after the file", + "i18n_625fb26b4b":"cancel", + "i18n_627c952b5e":"total space", + "i18n_6292498392":" Find next", + "i18n_629a6ad325":"Safety Management", + "i18n_629f3211ca":"Trim type", + "i18n_631d5b88ab":"Please enter the project storage path authorization. The carriage return supports entering multiple paths. The system will automatically filter../path and do not allow entering the root path.", + "i18n_632a907224":"Reset to regenerate the trigger address. After the reset is successful, the previous trigger address will be invalid. The trigger is bound to generate the trigger to the operator. If the corresponding account is deleted, the trigger will be invalid.", + "i18n_6334eec584":"Every five seconds.", + "i18n_635391aa5d":"Download product", + "i18n_637c9a8819":"Select at least 1 node project", + "i18n_638cddf480":"Creator, full match", + "i18n_639fd37242":"For the currently used docker swarm cluster, you need to create a swarm cluster before you can choose.", + "i18n_63b6b36c71":"Select certificate", + "i18n_63c9d63eeb":"Multiple menus can be expanded simultaneously", + "i18n_63dd96a28a":"Password support for referencing workspace variables:", + "i18n_63e975aa63":"Installation ID:", + "i18n_640374b7ae":"mount volume", + "i18n_641796b655":"Build complete", + "i18n_6428be07e9":"Configuration System Announcement", + "i18n_643f39d45f":"non-suspended", + "i18n_6446b6c707":"Nickname length is 2-10", + "i18n_646a518953":"Please enter the project ID", + "i18n_6470685fcd":": means to match any time at this position (consistent with _ ##_ * \")", + "i18n_649231bdee":"file suffix", + "i18n_64933b1012":"Storage options", + "i18n_6496a5a043":"command name", + "i18n_649d7fcb73":"The new cluster requires manual configuration of cluster management asset groupings and cluster access addresses", + "i18n_649d90ab3c":"Close right", + "i18n_649f8046f3":"Please select an SSH node", + "i18n_64c083c0a9":"result description", + "i18n_64eee9aafa":"boot time", + "i18n_652273694e":"host", + "i18n_65571516e2":"Build Notes:", + "i18n_657969aa0f":"Edit Docker", + "i18n_657f3883e3":"Do not execute the release process", + "i18n_65894da683":"Publication method:", + "i18n_65cf4248a8":"Cannot be initialized", + "i18n_65f66dfe97":"Clear the current buffer content", + "i18n_66238e0917":"Binding an external system account is not supported when the existing account is inconsistent with the external system account.", + "i18n_663393986e":"unbind", + "i18n_6636793319":"Do you really want to delete the node? Deletion checks data correlation, and there is no project or script for the node", + "i18n_664c205cc3":"Do you really want to clear the warehouse hidden field information? (password, private key)", + "i18n_667fa07b52":"Node upgrade to", + "i18n_66aafbdb72":"Latest build ID", + "i18n_66ab5e9f24":"new", + "i18n_66b71b06c6":"Upload compressed files (automatic decompression)", + "i18n_66c15f2815":"Match lines containing numbers", + "i18n_66e9ea5488":"log name", + "i18n_6707667676":"hostname", + "i18n_6709f4548f":"random generation", + "i18n_67141abed6":"Project authorization path + project folder", + "i18n_67425c29a5":"Timeout (s)", + "i18n_674a284936":"The second part will only be matched when isMatchSecond is true, and it is turned off by default.", + "i18n_674e7808b5":"MFA verification code", + "i18n_679de60f71":"Please fill in the log item name", + "i18n_67aa2d01b9":"Workspace menus, environment variables, and node distribution authorization need to be configured one by one", + "i18n_67b667bf98":"partial backup", + "i18n_67e3d3e09c":"batch build", + "i18n_67e7f9e541":"monitoring cycle", + "i18n_6816da19f3":"Close other", + "i18n_6835ed12b9":"The key to environment variables", + "i18n_685e5de706":"Container Construction", + "i18n_6863e2a7b5":"script execution history", + "i18n_686a19db6a":"auto delete", + "i18n_68a1faf6e2":"Batch builds pass in other parameters and the modifications will be performed synchronously", + "i18n_68af00bedb":"Table view to use workspace synchronization", + "i18n_68c55772ca":"Please enter the authorized party's web application ID", + "i18n_69056f4792":"Some operating status codes may be 0.", + "i18n_690a3d1a69":"execution container", + "i18n_691b11e443":"Current workspace", + "i18n_6928f50eb3":"Support for configuring system parameters:", + "i18n_69384c9d71":"Click to view historical trends", + "i18n_693a06987c":"Please fill in the user account number.", + "i18n_6948363f65":"Cancel timing, no longer timing execution (support! prefix disables timing execution, such as:! 0 0/1 * * * ?)", + "i18n_694fc5efa9":"refresh", + "i18n_695344279b":"File upload id generation failed:", + "i18n_6953a488e3":"Select logical node", + "i18n_697d60299e":"The currently logged in account is detected.", + "i18n_69c3b873c1":"build locally", + "i18n_69c743de70":"IP of the node", + "i18n_69de8d7f40":"reduction", + "i18n_6a359e2ab3":"scriptId can also be imported into the script library, which needs to be synchronized to the machine node in advance", + "i18n_6a49f994b1":"The build process executes the corresponding script, starts building, builds complete, starts publishing, publishes complete, builds exceptions, publishes exceptions", + "i18n_6a4a0f2b3b":"The synchronization mechanism uses the node address to determine that it is the same server (node).", + "i18n_6a588459d0":"Workspace name", + "i18n_6a620e3c07":"synchronization", + "i18n_6a658517f3":"task log", + "i18n_6a66d4cdf3":"Delay, container rollback interval", + "i18n_6a6c857285":"distribution node", + "i18n_6a8402afcb":"Parsing the file, ready to upload ", + "i18n_6a8c30bd06":"Loading editor", + "i18n_6a922e0fb6":"plug-in port", + "i18n_6a9231c3ba":"Function args parameter, optional", + "i18n_6aa7403b18":"If SSH is used but SSH cannot be selected, it means that the system has not detected the docker service.", + "i18n_6aab88d6a3":"Save and restart", + "i18n_6ab78fa2c4":"email address", + "i18n_6ac61b0e74":"It is recommended to restore files that are consistent with the current version or files from a nearby version", + "i18n_6ad02e7a1b":"Page resources loading....", + "i18n_6adcbc6663":"Configuration method: SSH list - > operation bar - > associated button - > corresponding workspace - > operation bar - > configuration button", + "i18n_6af7686e31":"Refresh every minute", + "i18n_6b0bc6432d":"operator", + "i18n_6b189bf02d":"Number of containers:", + "i18n_6b29a6e523":"Launch project", + "i18n_6b2e348a2b":"Timed execution", + "i18n_6b46e2bfae":"Is it really the current workspace?", + "i18n_6b4fd0ca47":"Support for configuring the sender: Following the RFC-822 standard, the sender can be in the following form:", + "i18n_6b6d6937d7":"163 Mailbox SSL", + "i18n_6bb5ba7438":"Limit commands that are prohibited at online end points", + "i18n_6be30eaad7":"Please enter a timeout", + "i18n_6bf1f392c0":"current state", + "i18n_6c08692a3a":"If the password is not changed, you don't need to fill it in.", + "i18n_6c14188ba0":"Cannot download directory", + "i18n_6c24533675":"Please select an alarm contact or fill in the webhook.", + "i18n_6c72e9d9de":"Edit distribution project", + "i18n_6c776e9d91":"Project start, stop, restart, file change will request the corresponding address, optional, GET request", + "i18n_6d5f0fb74b":"Do you need to push the image to a remote warehouse after it is successfully built?", + "i18n_6d68bd5458":"full backup", + "i18n_6d7f0f06be":"Please select a publish action", + "i18n_6d802636ab":"privacy", + "i18n_6da242ea50":"Task Id", + "i18n_6dcf6175d8":"Go now.", + "i18n_6de1ecc549":"View server level scripts", + "i18n_6e02ee7aad":"The length of the period, expressed in microseconds.", + "i18n_6e2d78a20e":"Search from the end, the first 20 lines of the file, and the last 3 lines of the file", + "i18n_6e60d2fc75":"Page Enables Compact Mode", + "i18n_6e69656ffb":"File cannot be empty", + "i18n_6e70d2fb91":"Build parameters, such as: key1 = value1 & key2 = value2", + "i18n_6ea1fe6baa":"basic information", + "i18n_6eb39e706c":"editing machine", + "i18n_6ef90ec712":"Please fill in the name of the image to be pulled", + "i18n_6f15f0beea":"The two passwords do not match...", + "i18n_6f32b1077d":"Please enter a workspace note, leave blank and use the default name", + "i18n_6f5b238dd2":" SSH and local command issuance perform variable replacement. System reserved variables are: {'${BUILD_ID }'}、{'${ BUILD_NAME }'}、{'${ BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID}'}", + "i18n_6f6ee88ec4":"Support open source", + "i18n_6f73c7cf47":"Retention days:", + "i18n_6f7ee71e77":"static directory", + "i18n_6f854129e9":"Group/label", + "i18n_6f8907351b":"synchronization node configuration", + "i18n_6f8da7dcca":"The node address format is: IP: PORT (example: 192.168.1.100:2123)", + "i18n_6f9193ac80":"Enable two-step verification", + "i18n_6fa1229ea9":"One-click distribution synchronizes the authorization configuration of multiple nodes", + "i18n_6ffa21d235":"Distributing nodes refers to synchronizing variables to corresponding nodes, and current variables can also be used in node scripts", + "i18n_7006410585":"No matter what exit code is returned, always restart the container.", + "i18n_7010264d22":"No authentication is enabled.", + "i18n_702430b89d":"Page Enables Loose Mode", + "i18n_702afc34a0":"Difference release:", + "i18n_7030ff6470":"error", + "i18n_7035c62fb0":"account number", + "i18n_704f33fc74":"Search from scratch, 0 lines before the file, 3 lines after the file", + "i18n_706333387b":"This feature does not guarantee that the newly added container and the previous container parameters are exactly the same. Please use it with caution.", + "i18n_7088e18ac9":"volume", + "i18n_708c9d6d2a":"Please choose", + "i18n_70a6bc1e94":"The current system has been initialized and cannot be initialized repeatedly.", + "i18n_70b3635aa3":"execution time", + "i18n_70b5b45591":"Quick installation", + "i18n_70b9a2c450":"Do you really want to exit the system?", + "i18n_710ad08b11":"disable", + "i18n_7114d41b1d":"Super admin has no restrictions.", + "i18n_712cdd7984":"Cooperation consultation", + "i18n_713c986135":"The container build will generate the relevant mount directory in the docker, which generally does not require human operation", + "i18n_7156088c6e":"Encoding method", + "i18n_71584de972":"Non-server boot self-start, if you need to boot self-start, it is recommended to configure", + "i18n_715ec3b393":"Used to quickly synchronize the configuration of other machine nodes", + "i18n_7173f80900":"refuse", + "i18n_71a2c432b0":"edit variable", + "i18n_71bbc726ac":"follower system", + "i18n_71c6871780":"timed task expression", + "i18n_71dc8feb59":"Not configured", + "i18n_71ee088528":"Bridge mode:", + "i18n_7220e4d5f9":"way", + "i18n_7229ecc631":"time", + "i18n_7293bbb0ff":"Total number of inodes", + "i18n_729eebb5ff":"There is no corresponding SSH.", + "i18n_72d14a3890":"Please select the user's permission group", + "i18n_72d46ec2cf":"Login information expired", + "i18n_72d4ade571":", is only used for the meaning of the prompt parameter", + "i18n_72e7a5d105":"Mirror ID", + "i18n_72eae3107e":"Grey green abbott", + "i18n_72ebfe28b0":"timing", + "i18n_7307ca1021":"auto start", + "i18n_7327966572":"delete completely", + "i18n_7329a2637c":"cluster ID", + "i18n_73485331c2":"file information", + "i18n_73578c680e":"The data directory refers to the files and data storage directories generated during the execution of the program", + "i18n_73651ba2db":"batch restart", + "i18n_7370bdf0d2":"script log", + "i18n_738a41f965":"Project name", + "i18n_73a87230e0":"File system type", + "i18n_73b7b05e6e":" Distribute the script to the corresponding machine node, and the corresponding machine node can refer to the corresponding script ", + "i18n_73b7e8e09e":"If you encounter any problems during the use of the beta version, you can give us feedback at any time, and we will answer you as soon as possible.", + "i18n_73c980987a":"The loading container is available in the label....", + "i18n_73d8160821":"Configure in yaml/yml format, scriptId is the relative path or script template ID of the script file under the project path, you can view scriptId in the script template editing pop-up window", + "i18n_73ed447971":"We highly recommend that you use a TLS certificate", + "i18n_73f798a129":"free community", + "i18n_7457228a61":"Remote download address", + "i18n_74bdccbb5d":"My Workspace", + "i18n_74c5c188ae":"The successful operation interface HTTP status code is 200.", + "i18n_74d5f61b9f":"build trigger", + "i18n_74d980d4f4":"In the single page list, file type items will be automatically sorted to the end", + "i18n_74dc77d4f7":"Container ID", + "i18n_74dd7594fc":"Request when an alarm occurs", + "i18n_74ea72bbd6":"cluster management", + "i18n_751a79afde":"30 Minutes", + "i18n_7527da8954":"normal user", + "i18n_7548ea6316":"Click to collapse the left menu bar", + "i18n_75528c19c7":"Automatic restart", + "i18n_7561bc005e":"Build process request, optional, GET request", + "i18n_75769d1ac8":"read", + "i18n_757a730c9e":"Unable to connect", + "i18n_758edf4666":"Search from scratch, 2 lines before the file, 3 lines after the file", + "i18n_75c63f427a":"This option is an experimental property, and the actual effect is basically the same.", + "i18n_75fc7de737":"route", + "i18n_7617455241":"If there are two fields in the file: MemAvailable and MemTotal, then oshi will use it directly, so in this system, the memory occupation calculation method: memory occupation = (total-available)/total", + "i18n_762e05a901":"Difference publishing refers to whether there are differences in the files in the corresponding bundle and project folders, and if there are incremental differences, then upload or overwrite the files.", + "i18n_7650487a87":"Address", + "i18n_76530bff27":"Please enter a private token", + "i18n_7653297de3":"jump", + "i18n_765592aa05":"If the container data directory is not mounted, please back up the data in advance before using this feature.", + "i18n_765d09eea5":"The current file is not readable, you need to configure readable file authorization", + "i18n_767fa455bb":"directory", + "i18n_768e843a3e":"Class 192", + "i18n_769d88e425":"complete", + "i18n_76aebf3cc6":"log size", + "i18n_76ebb2be96":"1 minute", + "i18n_77017a3140":"associative container tag", + "i18n_770a07d78f":"When there is no corresponding script in the target workspace, a new script will be automatically created", + "i18n_771d897d9a":"status code", + "i18n_77373db7d8":"Receive alarm message, optional, GET request", + "i18n_7737f088de":"batch restart", + "i18n_773b1a5ef6":"Please select a language mode", + "i18n_775fde44cf":"Process port cache:", + "i18n_7760785daf":"free script", + "i18n_7764df7ccc":"Enabling differential releases but not empty releases is equivalent to only incremental and change updates", + "i18n_77688e95af":"Container rebuilding refers to recreating an identical container using the container parameters that have already been created.", + "i18n_7777a83497":"Please enter build notes, length less than 240", + "i18n_77834eb6f5":"You use this system.", + "i18n_7785d9e038":"The node ID is not stored in the lower version of the project data, and the corresponding project data will also come out in the lonely data (such data does not affect the use).", + "i18n_77b9ecc8b1":"backup name", + "i18n_77c1e73c08":"Script storage path: {'${user.home}'}/.jpom/xxxx.sh, script execution path: {'${user.home}'}, script execution method: bash {'${user.home}'}/.jpom/xxxx.sh par1 par2", + "i18n_77c262950c":"Import multiple projects at once using Access Tokens", + "i18n_77e100e462":"No status message yet", + "i18n_780afeac65":"Whether to turn it on", + "i18n_780fb9f3d0":"Update time:", + "i18n_7824ed010c":"Do you really want to cancel the current posting task?", + "i18n_7854b52a88":"enable", + "i18n_787fdcca55":"System Configuration", + "i18n_788a3afc90":"Lost contact", + "i18n_78a4b837e3":"IP that can communicate", + "i18n_78b2da536d":"The build process requests the corresponding address, starts building, builds complete, starts publishing, publishes complete, builds exceptions, publishes exceptions", + "i18n_78ba02f56b":"Do you really want to completely delete the distribution information? After deleting, the items under the node will also be completely deleted, and the project-related files will be automatically deleted (including project logs, log backups, and project files).", + "i18n_78caf7115c":"task name", + "i18n_78dccb6e97":"All nodes (plug-in side)", + "i18n_79076b6882":"Do you really want to delete these build information in bulk? Deletion will also delete all build history information synchronously. If the deletion fails halfway through, the deletion operation will be terminated.", + "i18n_7912615699":"connection status", + "i18n_791870de48":"Warehouse password", + "i18n_791b6d0e62":"Ranking is sorted alphabetically a-z", + "i18n_79698c57a2":"The current workspace has no nodes yet", + "i18n_798f660048":"template node", + "i18n_799ac8bf40":"Support variable references: {'${TASK_ID }'}、{'${ FILE_ID }'}、{'${ FILE_NAME }'}、{'${ FILE_EXT_NAME}'}", + "i18n_79a7072ee1":"Token URL", + "i18n_79c6b6cff7":"association grouping", + "i18n_79d3abe929":"copy", + "i18n_7a30792e2a":"Edit SSH", + "i18n_7a3c815b1e":"file directory", + "i18n_7a4ecc606c":"Mirror tags, such as: key1 = values1 & keyvalue2 use URL encoding", + "i18n_7a5dd04619":"Note that the execution of relevant commands requires a corresponding environment in the server where it is located", + "i18n_7a7e25e9eb":"Are you sure you want to move this data down? The move down operation may be invalid because the subsequent data of the list does not have a sorted value operation!", + "i18n_7a811cc1e5":"copy ", + "i18n_7a93e0a6ae":"Select the enterprise version or purchase a license.", + "i18n_7aa81d1573":"Please enter a file name", + "i18n_7aaee3201a":"If you need to delete it, you need to back it up in advance or it has been determined that the corresponding file is deprecated before deleting it!!!!", + "i18n_7afb02ed93":"There are currently no environment variables to reference", + "i18n_7b2cbfada9":"Stop before publishing:", + "i18n_7b36b18865":"partition ID", + "i18n_7b61408779":"#Project file backup path", + "i18n_7b8e7d4abc":"Do you really want to delete the execution record?", + "i18n_7b961e05d0":"Indicates the last day of the month", + "i18n_7bcbf81120":"receive packet", + "i18n_7bcc3f169c":"#Built-in variable ${JPOM_WORKING_DIR} ${JPOM_BUILD_ID}", + "i18n_7bf62f7284":"Manually cancel distribution", + "i18n_7c0ee78130":"build log", + "i18n_7c223eb6e9":"Release system announcement", + "i18n_7c9bb61536":"log item name", + "i18n_7cb8d163bb":"variable name", + "i18n_7cc3bb7068":"Nodes are not actually requested to delete project information", + "i18n_7ce511154f":"Cannot be modified after creation", + "i18n_7d23ca925c":"Server level time", + "i18n_7d3f2fd640":"Search in file lines 3 - 2147483647", + "i18n_7ddbe15c84":"network", + "i18n_7dde69267a":"Grouping of unbound clusters:", + "i18n_7de5541032":"If ssh does not configure the authorization directory, it cannot be selected.", + "i18n_7dfc7448ec":"Do you really want to delete the warehouse information?", + "i18n_7dfcab648d":"product", + "i18n_7e000409bb":"It is prone to mining", + "i18n_7e1b283c57":" add", + "i18n_7e2b40fc86":"Select Node", + "i18n_7e300e89b1":"Distributed successfully", + "i18n_7e33f94952":"If you want to execute the command after switching the path, you need to", + "i18n_7e359f4b71":"Total number of hard disks:", + "i18n_7e58312632":"Edit Log Search", + "i18n_7e866fece6":"Please enter 2-step verification code", + "i18n_7e930b95ef":"publish file", + "i18n_7e951d56d9":"operating time", + "i18n_7e9f0d2606":"Project refers to a certain project in the node, and the project needs to be created in the node in advance", + "i18n_7ef30cfd31":"Additional environment variables refer to reading the environment variables file specified by the warehouse to add to the execution build runtime", + "i18n_7f0abcf48d":"You need to go to the editor to bind an ssh information for a node to enable this function.", + "i18n_7f3809d36b":"end of build", + "i18n_7f5bcd975b":"CPU usage", + "i18n_7f7c624a84":"batch operation", + "i18n_7f7ee903da":"Publish hidden files", + "i18n_7fb5bdb690":"Software Acknowledgments", + "i18n_7fb62b3011":"batch delete", + "i18n_7fbc0f9aae":"Execution time starts", + "i18n_7fc88aeeda":"Change Password", + "i18n_800dfdd902":"Today.", + "i18n_80198317ff":"And recommend or share with your friends:", + "i18n_8023baf064":"notification status", + "i18n_80669da961":"CPU usage", + "i18n_807ed6f5a6":"No data yet", + "i18n_8086beecb3":"Tag name:", + "i18n_808c18d2bb":"A value of true indicates that the project is currently running", + "i18n_809b12d6a0":"Please be patient, there is no need to refresh the page for the time being.", + "i18n_80cfc33cbe":"Confirm reset", + "i18n_81301b6813":"Open end point", + "i18n_81485b76d8":"Please enter the host address", + "i18n_814dd5fb7d":"Do you really want to delete the backup information?", + "i18n_815492fd8d":"Legacy packages take up space", + "i18n_8160b4be4e":"Abnormal shutdown", + "i18n_819767ada1":"username", + "i18n_8198e4461a":"Project:", + "i18n_81afd9e713":"queue waiting", + "i18n_81c1dff69c":"solution", + "i18n_81d7d5cd8a":"Detailed description of the command", + "i18n_81e4018e9d":"dangling type", + "i18n_82416714a8":"Ports to be tested", + "i18n_824607be6b":"retention days", + "i18n_824914133f":"No script library", + "i18n_8283f063d7":"Project complete catalog", + "i18n_828efdf4e5":"number of MFA enabled", + "i18n_82915930eb":"concurrent execution", + "i18n_829706defc":"Online build (build associated repositories, build history)", + "i18n_829abe5a8d":"group", + "i18n_82b89bd049":"The log pop-up window will open in full screen.", + "i18n_82d2c66f47":"batch distribution", + "i18n_8306971039":"user", + "i18n_8309cec640":"Please select the node project. It may be that there is no project in the node, and you need to go to the node to create a project.", + "i18n_833249fb92":"Current file time", + "i18n_8347a927c0":"modify", + "i18n_835050418f":"Are you sure you want to upload the latest plugin package?", + "i18n_83611abd5f":"publish", + "i18n_8363193305":"Please enter the callback redirect url [redirectUri]", + "i18n_8388c637f6":"self-starting", + "i18n_83aa7f3123":"distribution id", + "i18n_83c61f7f9e":"Please select a monitoring user", + "i18n_83ccef50cd":"When the target workspace already exists, the script will be automatically synchronized, the script content, default parameters, scheduled execution, description", + "i18n_83f25dbaa0":"Bind node", + "i18n_8400529cfb":"Reset custom process name information", + "i18n_8432a98819":"operation function", + "i18n_843f05194a":"Show all", + "i18n_84415a6bb1":"Reset the download token information, the previous download token will be invalid after reset", + "i18n_844296754e":"virtual memory", + "i18n_84592cd99c":"It can be understood as a directory packaged for the project. Such as Jpom project execution (build command)", + "i18n_84597bf5bc":"Alibaba Cloud Enterprise Email Configuration", + "i18n_84632d372f":"Click to view details", + "i18n_84777ebf8b":"Safety reminder", + "i18n_847afa1ff2":"Please enter IP authorization, multiple use newline, 0.0.0.0 open all IP, support configuration IP segment 192.168.1.1/192.168.1.254, 192.168.1.0/24", + "i18n_848c07af9b":"admin panel", + "i18n_848e4e21da":"Such as: --server", + "i18n_8493205602":"open", + "i18n_84aa0038cf":"system log", + "i18n_84b28944b7":"Timeout (S)", + "i18n_84d331a137":"Seconds (if the value is too small, the node state may not be obtained)", + "i18n_84e12f7434":"Session has been closed [ssh-terminal]", + "i18n_853d8ab485":"Building now.", + "i18n_85451d2eb5":"Please enter a variable value", + "i18n_8580ad66b0":"Do you really want to delete the project completely? A thorough project will automatically delete project-related files (including project logs, log backups, and project files)", + "i18n_85be08c99a":"No data was found", + "i18n_85cfcdd88b":"Local build refers to the execution of build commands directly on the server at the server level", + "i18n_85da2e5bb1":"Restart, please wait...", + "i18n_85ec12ccd3":"Delay, container upgrade interval", + "i18n_85f347f9d0":"User restrictions Users can only operate the corresponding functions in the corresponding workspace", + "i18n_85fe5099f6":"cluster", + "i18n_86048b4fea":"remove", + "i18n_860c00f4f7":"per hour", + "i18n_863a95c914":"Do you really want to save the current configuration? If the configuration is wrong, the service may not be started. You need to manually restore Austria!!!", + "i18n_867cc1aac4":" : Range: 0~ 23", + "i18n_869b506d66":"For independent project distribution, please go to the distribution management to modify it.", + "i18n_869ec83e33":"unused", + "i18n_86b7eb5e83":"Do you need to delete all associated data before deleting the current workspace?", + "i18n_86c1eb397d":"Switch account", + "i18n_86cd8dcead":"startup time", + "i18n_86e9e4dd58":"Warehouse lastcommit", + "i18n_86f3ec932c":"read size", + "i18n_86fb7b5421":"Node account", + "i18n_8704e7bdb7":"Please enter the token url [accessTokenUri]", + "i18n_871cc8602a":"secondary directory", + "i18n_8724641ba8":"Interval (/) > Interval (-) > List (,)", + "i18n_8756efb8f4":"Do you really want to delete the current folder?", + "i18n_87659a4953":"Are you sure you want to close the beta program?", + "i18n_8780e6b3d1":"file management", + "i18n_878aebf9b2":"login name", + "i18n_87d50f8e03":"Container ID", + "i18n_87db69bd44":"Restrict resources", + "i18n_87dec8f11e":"Incorrect workspace data", + "i18n_87e2f5bf75":"append script template", + "i18n_87eb55155a":"Number of lines:", + "i18n_8813ff5cf8":"If the environment variables are installed and configured after starting the server level, you need to restart the server level through the end point command to take effect", + "i18n_883848dd37":"Actual memory usage", + "i18n_8844085e15":"0 a.m.", + "i18n_884ea031d3":"Please enter a variable description", + "i18n_8887e94cb7":"Sequential execution (if the execution fails, it will continue)", + "i18n_888df7a89e":"Not recommended.", + "i18n_88ab27cfd0":"Group/Tag:", + "i18n_88b4b85562":"Packaging pre-release environment npm i & & npm run build: stage", + "i18n_88b79928e7":"Certificate lost", + "i18n_88c5680d0d":"Management status:", + "i18n_88c85a2506":"The newly added node (plug-in side) will automatically", + "i18n_88e6615734":"Unbinding will check the data correlation, and will automatically delete the node item and script cache information. Generally, the server cannot be connected and has been determined to be no longer used.", + "i18n_88f5c7ac4a":"Please select a sort field", + "i18n_8900539e06":"write size", + "i18n_89050136f8":"post-publication action", + "i18n_891db2373b":"auto refresh", + "i18n_897d865225":"Number of mirrors:", + "i18n_89944d6ccb":"Labels are limited to alphanumeric and 1-10 in length.", + "i18n_899dbd7b9a":"CPU model", + "i18n_899fe0c5dd":"The node address is the IP: PORT of the plug-in side. The default port of the plug-in side is: 212.", + "i18n_89a40a1a8b":"Distribution process request, optional, GET request", + "i18n_89cfb655e0":"container name tag", + "i18n_89d18c88a3":"Please enter a file task name", + "i18n_89f5ca6928":"Support wild-card", + "i18n_8a1767a0d2":"After turning on this option, hidden files can be published normally.", + "i18n_8a3e316cd7":"Do not code", + "i18n_8a414f832f":"cluster address", + "i18n_8a49e2de39":"QQ mailbox configuration", + "i18n_8a4dbe88b8":"Click to enter node management", + "i18n_8a745296f4":"Boot time:", + "i18n_8aa25f5fbe":"release type", + "i18n_8ae2b9915c":"Please fill in the", + "i18n_8aebf966b2":"cluster access address", + "i18n_8b1512bf3a":"If the port", + "i18n_8b2e274414":"Last Reloaded Results", + "i18n_8b3db55fa4":"Cluster ID:", + "i18n_8b63640eee":"Account is disabled", + "i18n_8b6e758e4c":"Hover over the dashboard to reveal the specific meaning", + "i18n_8b73b025c0":"Simple to use, but does not support key export backups", + "i18n_8b83cd1f29":"Name of mirror to pull", + "i18n_8ba971a184":"private token", + "i18n_8ba977b4b7":"#Restrict backup to specified file suffixes (regular support)", + "i18n_8bd3f73502":"node password", + "i18n_8be76af198":"163 mailbox configuration", + "i18n_8be868ba1b":"Class 10", + "i18n_8c0283435b":": represents a continuous interval, e.g. in points, indicating 2, 3, 4, 5, 6, 7, 8 points", + "i18n_8c24b5e19c":"Please use the application scan code to bind the token, and then enter the verification code to confirm the binding before it takes effect.", + "i18n_8c2da7cce9":"No certificate", + "i18n_8c4db236e1":"Please enter a script tag. The tag can only be letters or numbers. The length needs to be less than 20 and it is globally unique.", + "i18n_8c61c92b4b":"backup type", + "i18n_8c66392870":"Requires ssh-keygen -m PEM -t rsa -b 4096 -C", + "i18n_8c67370ee5":"If the product is synced to the file center, the current value is shared", + "i18n_8c7c7f3cfa":"Server level scripting", + "i18n_8c7ce1da57":"After enabling dockerTag version increment, the last digit of the version number will be automatically synchronized to the build sequence ID during each build, such as: the current build is the 100th build testtag: 1.0 - > testtag: 1.100, testtag: 1.0.release - > testtag: 1.100.release. If there is no matching number, the increment operation will be ignored.", + "i18n_8c7d19b32a":"Memory Node (MEM) allowed for execution (0-3, 0, 1). Valid only on NUMA systems.", + "i18n_8cae9cb626":"Light color idea", + "i18n_8ccbbb95a4":"Please fill in the remote URL.", + "i18n_8cd628f495":"All IP addresses:", + "i18n_8d0fa2ee2d":"Please enter the port number", + "i18n_8d1286cd2e":"No distribution logs", + "i18n_8d13037eb7":"Status message:", + "i18n_8d202b890c":"Bulk trigger address", + "i18n_8d3d771ab6":"edit cluster", + "i18n_8d5956ca2a":"Configure in yaml/yml format, scriptId is the relative path of the script file under the project path or the server level script template ID, you can view the scriptId in the server level script template editing pop-up window", + "i18n_8d5c1335b6":"Container name numeric letters, and the length is greater than 1", + "i18n_8d62b202d9":"Please select the file to use", + "i18n_8d63ef388e":"pause", + "i18n_8d6d47fbed":"#Execute in the specified directory:./project directory /root/specific directory, the default is {'${jpom_agent_data_path}'}/script_run_cache ", + "i18n_8d6f38b4b1":"file description", + "i18n_8d90b15eaf":"#host file upload to container /host:/container: true", + "i18n_8d92fb62a7":"Please select the template node", + "i18n_8d9a071ee2":"import", + "i18n_8da42dd738":"Second level (second level is not enabled by default, you need to modify the configuration file: [system.timerMatchSecond])", + "i18n_8dbe0c2ffa":"Occupy space:", + "i18n_8dc09ebe97":"obtain", + "i18n_8dc8bbbc20":"file system", + "i18n_8de2137776":"cluster task", + "i18n_8e2ed8ae0d":"[Independent distribution]", + "i18n_8e331a52de":"Only allowed IP addresses", + "i18n_8e34aa1a59":"Configure this machine node as a template", + "i18n_8e389298e4":"export image", + "i18n_8e38d55231":"Do you really want to log out of the system completely? Logging out completely will log out and clear the browser cache", + "i18n_8e54ddfe24":"start", + "i18n_8e6184c0d3":"Projects may support linking the following data:", + "i18n_8e6a77838a":"Please select the machine node to distribute to", + "i18n_8e872df7da":"Be careful that the entire line cannot contain spaces", + "i18n_8e89763d95":"Host IP", + "i18n_8e8bcfbb4f":"Are you sure you want to trim the corresponding information? Trimming will automatically clean the corresponding data.", + "i18n_8e9bd127fb":"The authorization directory needs to be configured for the workspace in advance", + "i18n_8ea4c3f537":"Search from the end, the first 100 lines of the file, and the last 100 lines of the file", + "i18n_8ea93ff060":"Node script templates are command scripts stored in nodes for online management of some script commands, such as initializing software environments, managing applications, etc", + "i18n_8ef0f6c275":"Close the beta program", + "i18n_8f0bab9a5a":"Number of log files being read", + "i18n_8f0c429b46":"Migration operations are not transactional in nature and may generate redundant data if the process is interrupted or constraints are not met!!!!", + "i18n_8f36f2ede7":"Workspace name:", + "i18n_8f3747c057":"service name", + "i18n_8f40b41e89":"Expiration time:", + "i18n_8f7a163ee9":"Quick installation of plug-ins", + "i18n_8f8f88654f":"No node information yet", + "i18n_8fb7785809":"If there is encryption in the process of generating the private key, then you need to fill the encrypted password into the password box above", + "i18n_8fbcdbc785":"Please enter an alias code", + "i18n_8fd9daf8e9":"Guaranteed to use ignoble TLS certificates on the intranet", + "i18n_8fda053c83":"write count", + "i18n_8ffded102f":"If you need to automatically build regularly, fill in the cron expression. The second level is not turned on by default, you need to modify the configuration file: [system.timerMatchSecond])", + "i18n_900c70fa5f":"warning", + "i18n_9014d6d289":"backup list", + "i18n_90154854b6":"Please enter host", + "i18n_901de97cdb":"Mode Request interface parameters are passed into the request body ContentType Please use: text/plain", + "i18n_903b25f64e":"unknown state", + "i18n_904615588b":"File type does not have console functionality", + "i18n_9057ac9664":"Please select the trigger type", + "i18n_9065a208e8":"To download the remote file to the project folder through the URL, you need to configure the allowed HOST authorization in the authorization directory configuration under the corresponding workspace.", + "i18n_906f6102a7":"Restart successful", + "i18n_9086111cff":"Associated workspace docker", + "i18n_90b5a467c1":"refresh directory", + "i18n_90c0458a4c":"Import backup", + "i18n_90eac06e61":"host directory", + "i18n_912302cb02":"Browser", + "i18n_9136e1859a":"Docker image", + "i18n_913ef5d129":"Perform a restart", + "i18n_916cde39c4":"All parameters will be concatenated into strings to execute the script separated by spaces. Parameters that need to pay attention to the order of parameters and unfilled values will be automatically ignored", + "i18n_916ff9eddd":"Please enter a nickname", + "i18n_91985e3574":"automatic detection", + "i18n_91a10b8776":" script library ", + "i18n_920f05031b":"state description", + "i18n_922b76febd":"Run mode Required", + "i18n_923f8d2688":"Post-issue command", + "i18n_9255f9c68f":"Session has been closed [tail-file]", + "i18n_92636e8c8f":"skip", + "i18n_9282b1e5da":"WeCom scan code", + "i18n_929e857766":"Certificate type", + "i18n_92c6aa6db9":"If docker exists in your SSH machine but the system does not detect it, you need to go to [Configuration Management] -", + "i18n_92dde4c02b":"ad serving", + "i18n_92e3a830ae":"help", + "i18n_92f0744426":"Container building refers to the use of docker containers to execute builds, which can be isolated from the host environment without installing a dependent environment", + "i18n_92f3fdb65f":"Warehouse:", + "i18n_92f9a3c474":"The page will automatically refresh after switching languages", + "i18n_9300692fac":"Markup Reference", + "i18n_9302bc7838":"Please enter the port to check", + "i18n_930882bb0a":"a", + "i18n_9308f22bf6":"In a single trigger address: the first random string is the script ID, and the second random string is the token.", + "i18n_930fdcdf90":"Configuration name (e.g. size)", + "i18n_9324290bfe":"Such as: key1", + "i18n_932b4b7f79":"Attention: Priority in each subexpression:", + "i18n_934156d92c":"Create a distribution project", + "i18n_9341881037":"Are you sure you want to take a batch build? Note: Running multiple builds at the same time will consume a large amount of resources. Please use batch builds with caution. If the number of batch builds exceeds the number of build task queues waiting, the build task will be automatically cancelled.", + "i18n_935b06789f":"You have not performed the operation yet.", + "i18n_9362e6ddf8":"Dangerous operation!!!", + "i18n_938dd62952":"execution path", + "i18n_939d5345ad":"submit", + "i18n_93e1df604a":"machine grouping", + "i18n_93e894325d":"batch boot", + "i18n_9402665a2c":" Continuous search (dialog box does not close automatically, press Enter to find the next one, press Shift-Enter to find the previous one)", + "i18n_9412eb8f99":"Please fill in the platform address.", + "i18n_9443399e7d":" , the range is 1970~ 2099, but the 7th bit is not parsed or matched", + "i18n_94763baf5f":"You can go to [Plug-in Side Configuration] = > [Authorization Configuration] in the node management to modify it.", + "i18n_947d983961":"Warm reminder", + "i18n_948171025e":"Session has been closed [docker-log]", + "i18n_949934d97c":"oversized", + "i18n_949a8b7bd2":"column settings", + "i18n_94aa195397":"certificate file", + "i18n_94ca71ae7b":"Please select the certificate to use", + "i18n_94d4fcca1b":"Create account", + "i18n_952232ca52":"The build history may occupy a lot of hard disk space. It is recommended to configure the number of reservations according to the actual situation", + "i18n_953357d914":"Ignore check state", + "i18n_953ec2172b":"Unrestarted successfully:", + "i18n_954fb7fa21":"Do you really want to delete the project? Deleting the project will not delete the project-related files. It is recommended to clean the project-related files first and then delete the project.", + "i18n_956ab8a9f7":"Profile? Once a profile is created, it cannot be deleted through the admin page.", + "i18n_957c1b1c50":"cluster node", + "i18n_95a43eaa59":"founder", + "i18n_95b351c862":"edit", + "i18n_95c5c939e4":"The selectable list and the project authorization directory are the same, i.e. the same configuration", + "i18n_95dbee0207":"Remote Download Secure HOST", + "i18n_96283fc523":"Backup file does not exist", + "i18n_964d939a96":" Long name:", + "i18n_969098605e":"Environment variables refer to some fixed parameter values that are configured in the system and are used for quick reference during script execution.", + "i18n_96b78bfb6a":"Do not manually delete the files below the data directory!!!!", + "i18n_96c1c8f4ee":"Grey green abcdef", + "i18n_96c28c4f17":"Which cluster to join", + "i18n_96d46bd22e":"manual refresh statistics", + "i18n_96e6f43118":"Container runtime", + "i18n_974be6600d":"Password must contain numbers, letters, characters, and be greater than 6 digits", + "i18n_977bfe8508":"Label (TAG)", + "i18n_979b7d10b0":"build break", + "i18n_97a19328a8":"Open now", + "i18n_97cb3c4b2e":"Workspace environment variables for building command correlation", + "i18n_97d08b02e7":"Network port test", + "i18n_97ecc1bbe9":"output flow", + "i18n_981cbe312b":"to", + "i18n_9829e60a29":"live version number", + "i18n_98357846a2":"Table view to use batch operation function", + "i18n_983f59c9d4":"CAPTCHA", + "i18n_9878af9db5":"Please go to [System Administration] - > [Asset Management] - > [Docker Management] to add Docker and create a cluster, or associate and assign existing Docker cluster authorizations to this workspace", + "i18n_9880bd3ba1":"This tool is used to check whether the cron expression is correct and to schedule the running time", + "i18n_989f1f2b61":"Do you really want to restart the project?", + "i18n_98a315c0fc":"authorization", + "i18n_98cd2bdc03":"Table view to use the synchronization configuration feature", + "i18n_98d69f8b62":"Workspace", + "i18n_98e115d868":"Running timed tasks", + "i18n_9914219dd1":"Search from scratch", + "i18n_9932551cd5":"memory", + "i18n_993a5c7eee":"#Configuration instructions: https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerCreate", + "i18n_99593f7623":"Client side ID", + "i18n_9964d6ed3f":"mount", + "i18n_996dc32a98":"System type", + "i18n_9970ad0746":"theme", + "i18n_9971192b6a":"Such as: http", + "i18n_9973159a4d":"Match a character", + "i18n_998b7c48a8":"View container", + "i18n_99b3c97515":"hour level", + "i18n_99cba05f94":"Do you really want to delete SSH? Scripts associated with the current ssh will be invalid after deletion", + "i18n_99d3e5c718":" Start searching", + "i18n_99f0996c0a":"Please select a log directory", + "i18n_9a00e13160":"In a single trigger address: the first random string is the project ID (server level), the second random string is the token", + "i18n_9a0c5b150c":"Edit, command", + "i18n_9a2ee7044f":"variable value", + "i18n_9a436e2a53":"Trim objects with specified labels, multiple separated by commas", + "i18n_9a4b872895":"cluster operation", + "i18n_9a56bb830e":"user nickname", + "i18n_9a77f3523e":"Mirror tag", + "i18n_9a7b52fc86":"all", + "i18n_9a8eb63daf":"Configure Workspace Permissions", + "i18n_9ab433e930":"Other configurations", + "i18n_9ac4765895":"An integer value that represents the relative CPU weight of this container relative to other containers.", + "i18n_9adf43e181":"Number under construction", + "i18n_9ae40638d2":"Deployment certificate", + "i18n_9af372557e":"server port", + "i18n_9aff624153":"monitor", + "i18n_9b0bc05511":"Search on file lines 1 - 100", + "i18n_9b1c5264a0":"After uploading", + "i18n_9b280a6d2d":"Node:", + "i18n_9b3e947cc9":"Node status:", + "i18n_9b5f172ebe":"bind cluster", + "i18n_9b7419bc10":"QQ mailbox", + "i18n_9b74c734e5":"The node account password is the account password of the plug-in side, not the user account (administrator) password.", + "i18n_9b78491b25":"Please enter the authorization path. Carriage return supports entering multiple paths. The system will automatically filter../path and do not allow entering the root path.", + "i18n_9b7ada2613":" : Range: 1 to 31,", + "i18n_9b9e426d16":"Number of log files read:", + "i18n_9ba71275d3":"Please be patient. There is no need to refresh the page for the time being. It will be automatically refreshed after the upgrade is successful.", + "i18n_9baca0054e":"Modifier", + "i18n_9bbb6b5b75":"Do you really want to delete the log search?", + "i18n_9bd451c4e9":"Node already exists", + "i18n_9be8ff8367":"Support variable substitution: {'${BUILD_ID }'}、{'${ BUILD_NAME }'}、{'${ BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID}'}", + "i18n_9bf4e3c9de":"Creating a distribution project means creating a new node that belongs to the project and distributing it to the project. After the creation is successful, the project information will be automatically synchronized to the corresponding node, and the modified node distribution information will also be automatically synchronized to the corresponding node", + "i18n_9bf5aa6672":"Web socket error, please check if ws proxy is enabled", + "i18n_9c19a424dc":"Please enter the original password", + "i18n_9c2a917905":"search command", + "i18n_9c2f1d3f39":"The root inside has true root privileges.", + "i18n_9c3a3e1b03":"There is no automatic deletion of the parent", + "i18n_9c3a5e1dad":"Please go to [System Management] - > [Asset Management] - > [Machine Management] to add a node, or associate and assign the newly added machine authorization to this workspace.", + "i18n_9c3c05d91b":"Node address It is recommended to use the intranet address", + "i18n_9c55e8e0f3":"The CPU allowed to execute (e.g., 0-3, 0, 1).", + "i18n_9c66f7b345":"Do you really want to delete the machine? Delete will check data correlation", + "i18n_9c84cd926b":"The new machine also needs to bind the workspace, because we recommend that different clusters be allocated to different workspaces for management", + "i18n_9c942ea972":"The certificate generation method can refer to the documentation) to connect to docker to improve security", + "i18n_9c99e8bec9":"If not, publish to the root directory of the project", + "i18n_9cac799f2f":"Select group name", + "i18n_9caecd931b":"field", + "i18n_9cd0554305":"If you don't need to keep more build history information, you can go to the server level to modify the build-related configuration parameters", + "i18n_9ce5d5202a":"Running Jar packages:", + "i18n_9d577fe51b":"File source", + "i18n_9d5b1303e0":"When creating a cluster, you will try to get the cluster information in docker. If there is cluster information, the cluster information will be automatically synchronized to the system. Otherwise, if there is no cluster information, a swarm cluster will be automatically created.", + "i18n_9d7d471b77":"Please select a node role", + "i18n_9d89cbf245":"distribution name", + "i18n_9dd62c9fa8":"End point command unlimited", + "i18n_9ddaa182bd":"Plugin end time:", + "i18n_9de72a79fe":"View file", + "i18n_9e09315960":"rebuild", + "i18n_9e2e02ef08":"distribution type", + "i18n_9e4ae8a24f":"DingTalk scan code", + "i18n_9e560a4162":"Way to generate SSH key", + "i18n_9e5ffa068e":"Basic information", + "i18n_9e6b699597":"nanoCPUs Minimum 1000000", + "i18n_9e78f02aad":"Parameter description, parameter description has no practical effect, it is only used to prompt the meaning of parameters", + "i18n_9e96d9c8d3":"System load:", + "i18n_9e98fa5c0d":"File name bar supports right-click menu", + "i18n_9ec961d8cb":"Select bundle", + "i18n_9ee0deb3c8":"The network is deserted! Please try again...:", + "i18n_9ee9d48699":"Modification is not supported after creation", + "i18n_9f01272a10":" legal risk", + "i18n_9f0de3800b":"Please fill in the warehouse name.", + "i18n_9f52492fbc":"For configuration details, please refer to the configuration example.", + "i18n_9f6090c819":"The incoming parameters are: buildId, buildName, type, statusMsg, triggerTime", + "i18n_9f6fa346d8":"Please enter an SSH name", + "i18n_9f70e40e04":"running time", + "i18n_9fb12a2d14":"The command to execute after publishing (non-blocking command), usually the start project command, such as: ps -aux | grep java, supports variable substitution: {'${BUILD_ID }'}、{'${ BUILD_NAME }'}、{'${ BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID}'} ", + "i18n_9fb61a9936":"version increment", + "i18n_9fc2e26bfa":"Please select an item", + "i18n_9fca7c455f":"login time", + "i18n_9febf31146":"Please select a file", + "i18n_9ff5504901":"Incoming environment variables are: buildId, buildName, type, statusMsg, triggerTime, buildNumberId, buildSourceFile", + "i18n_a001a226fd":"update time", + "i18n_a03c00714f":"batch shutdown", + "i18n_a03ea1e864":"Please select the node to distribute to", + "i18n_a04b7a8f5d":"A single trigger request supports parsing parameters into environment variables and passing them into the script for execution. For example, the passed parameter name abc = efg is introduced in the script as: {'${trigger_abc}'}", + "i18n_a050cbc36d":"Your browser version is too low to support this feature", + "i18n_a056d9c4b3":"Select script", + "i18n_a05c1667ca":"Building history", + "i18n_a08cbeb238":"And configure the correct environment variables", + "i18n_a09375d96c":"dangling", + "i18n_a093ae6a6e":"Automatic renewal", + "i18n_a0a111cbbd":"Distributing items -", + "i18n_a0a3d583b9":"Total memory:", + "i18n_a0b9b4e048":"Please enter client side ID [clientId]", + "i18n_a0d0ebc519":"global proxy", + "i18n_a0e31d89ff":"It is generally recommended to take more than 10 seconds.", + "i18n_a0f1bfad78":"Data directory size includes: temporary files, online build files, database files, etc", + "i18n_a11cc7a65b":"Please enter content", + "i18n_a13d8ade6a":"The associated node data is acquired asynchronously with a certain time lag", + "i18n_a14da34559":"resource monitoring exception", + "i18n_a156349591":" view", + "i18n_a1638e78e8":"Match lines containing a or b", + "i18n_a17450a5ff":"Select compressed file", + "i18n_a17b5ab021":"The current file already exists", + "i18n_a17bc8d947":"The console log is only the log information output by the startup project, not the project log. You can turn off the console log backup function:", + "i18n_a189314b9e":"Do not publish", + "i18n_a1a3a7d853":"ready-made generation", + "i18n_a1b745fba0":"Please enter a backup name", + "i18n_a1bd9760fc":"timed task", + "i18n_a1c4a75c2d":"It is an open-source software. If you feel good about using this project, or want to support us as we continue to develop, you can support us in the following ways:", + "i18n_a1da57ab69":"Alipay transfer", + "i18n_a1e24fe1f6":"user time", + "i18n_a1f58b7189":"Parameter {count} value", + "i18n_a1fb7f1606":"script management", + "i18n_a20341341b":"Show the first N lines", + "i18n_a24d80c8fa":"The corresponding address will be requested for project start, stop, restart, and file changes.", + "i18n_a25657422b":"variable name", + "i18n_a2a0f52afe":"If left blank, the default configuration in the $HOME/.ssh directory will be used. The priority is: id_dsa > id_rsa > identity.", + "i18n_a2ae15f8a7":"build process", + "i18n_a2e62165dc":"Do you really want to save the current configuration? IP authorization, please configure Austria carefully (authorization refers to IP that only allows access), and it will take effect immediately after configuration. If the configuration is wrong, it will be inaccessible, and you need to manually restore Austria!!!", + "i18n_a2ebd000e4":"Do nothing.", + "i18n_a3296ef4f6":"Full screen end point", + "i18n_a33a2a4a90":"Scripts for server level synchronization cannot be modified here", + "i18n_a34545bd16":"Build parameters, such as: key1 = values1 & keyvalue2 use URL encoding", + "i18n_a34b91cdd7":"Environment variables can also be used for warehouse account passwords, ssh password references", + "i18n_a34c24719b":"Start executing the task", + "i18n_a35740ae41":"operation prompt", + "i18n_a3751dc408":" Execution every minute at 12:00", + "i18n_a37c573d7b":"This can be a Unix timestamp, a timestamp in date format, or a Go duration string (e.g. 10m, 1h30m), calculated relative to the time of the daemon machine.", + "i18n_a38ed189a2":"Please read the instructions and precautions in the update log before uploading the update and", + "i18n_a39340ec59":"prohibition order", + "i18n_a396da3e22":"The current workspace has no projects and no nodes", + "i18n_a3d0154996":"file status", + "i18n_a3f1390bf1":"After modification, if there is original associated data, it will be invalid and the association needs to be reconfigured.", + "i18n_a4006e5c1e":"Create backup", + "i18n_a421ec6187":"Edit environment variables", + "i18n_a4266aea79":"Do you really want to delete this service?", + "i18n_a436c94494":"Feishu scan code", + "i18n_a472019766":"Node Id", + "i18n_a497562c8e":"executor", + "i18n_a4f5cae8d2":"on state", + "i18n_a4f629041c":"The path needs to be configured with an absolute path.", + "i18n_a50fbc5a52":"Support specifying the network interface card name to bind:", + "i18n_a51cd0898f":"container name", + "i18n_a51d8375b7":"Select static file", + "i18n_a52a10123f":"If the upgrade fails, you need to manually restore the Austrian", + "i18n_a52aa984cd":"Do you really want to delete the permission group?", + "i18n_a53d137403":"Session has been closed [free-script]", + "i18n_a5617f0369":"SSH connection information", + "i18n_a577822cdd":"Save and build", + "i18n_a59d075d85":"Customize the clone depth to avoid all clones in large repositories", + "i18n_a5d550f258":"interval time", + "i18n_a5daa9be44":"Please check whether the package is complete before uploading, otherwise it may not start normally after the update!!", + "i18n_a5e9874a96":"Please choose which docker cluster to publish to", + "i18n_a5f84fd99c":"non-private", + "i18n_a6269ede6c":"management node", + "i18n_a62fa322b4":"The certificate will be packaged into a zip file and uploaded to the corresponding folder.", + "i18n_a637a42173":"The chosen build history no longer exists", + "i18n_a63fe7b615":"After being assigned to the workspace, you also need to configure the corresponding workspace in the association to use the Olympic Games perfectly.", + "i18n_a657f46f5b":"week", + "i18n_a66644ff47":"Class 172", + "i18n_a66fff7541":"Remote download URL", + "i18n_a6bf763ede":"machine node", + "i18n_a6fc9e3ae6":"Upload file", + "i18n_a74b62f4bb":"Hard disk information", + "i18n_a75a5a9525":"Publish directory, bundle and upload to the corresponding directory", + "i18n_a75b96584d":"Service Id", + "i18n_a75f781415":"Server level menu", + "i18n_a7699ba731":"Upload successful", + "i18n_a76b4f5000":"number of administrators", + "i18n_a77cc03013":"If you refer to the script library, you need to distribute the corresponding script to the machine node in advance to use it normally.", + "i18n_a795fa52cd":"quit completely", + "i18n_a7a9a2156a":"Please enter the confirmation password", + "i18n_a7c8eea801":"Attempt to auto-renew successfully", + "i18n_a7ddb00197":"Alibaba Cloud Enterprise Email SSL", + "i18n_a805615d15":"The values of type are: startReady, pull, executeCommand, release, done, stop, success, error", + "i18n_a810520460":"password", + "i18n_a823cfa70c":"container label", + "i18n_a84a45b352":"upgrade strategy", + "i18n_a8754e3e90":"Fill in the correct Internet Protocol Address", + "i18n_a87818b04f":"Wait to start", + "i18n_a8920fbfad":"Please change the command path to the actual path in your server", + "i18n_a89646d060":"After creating the workspace, the corresponding data needs to be managed separately in the corresponding workspace", + "i18n_a8f44c3188":"The account number is the account used for the system-specific demonstration.", + "i18n_a90cf0796b":"Information:", + "i18n_a912a83e6f":"plugin version", + "i18n_a918bde61d":"You have not yet built", + "i18n_a91ce167c1":"File ID", + "i18n_a9463d0f1a":"Search mode, by default, view the last number of lines in the file, search from the beginning refers to the search from the specified line down, and search from the end refers to the number of lines searched from the end of the file up", + "i18n_a94feac256":"The loading speed is determined according to the network speed. If the network is not good, the download will be slower.", + "i18n_a952ba273f":"Please note your intention when contacting.", + "i18n_a9795c06c8":"No SSH", + "i18n_a98233b321":"Recommendations for using the Microsoft Family Bucket", + "i18n_a9886f95b6":"Are you sure you want to delete this script library?", + "i18n_a9add9b059":"Data storage directory:", + "i18n_a9b50d245b":"Do not bind", + "i18n_a9c52ffd40":"Strictly implement:", + "i18n_a9c999e0bd":"It is recommended to customize the file name in the uploaded script. SSH upload defaults to: {'${FILE_ID }'}.{'${ FILE_EXT_NAME}'}", + "i18n_a9de52acb0":"How to operate", + "i18n_a9eed33cfb":"If the version difference is large, you need to reinitialize the data to ensure that it is consistent with the fields in the current program.", + "i18n_a9f94dcd57":"deploy", + "i18n_aa53a4b93a":"No network interface information", + "i18n_aa9236568f":"Statistical Trend", + "i18n_aabdc3b7c0":"project path", + "i18n_aac62bc255":"Click to view the log.", + "i18n_aad7450231":"Please enter the cluster you want to bind to", + "i18n_aadf9d7028":"Used to download remote files for node distribution and file uploads", + "i18n_aaeb54633e":"overload", + "i18n_ab006f89e7":"manual delete", + "i18n_ab13dd3381":"Login with your own Gitlab account", + "i18n_ab3615a5ad":"Download the installation package", + "i18n_ab3725d06b":"Session has been closed", + "i18n_ab7f78ba4c":"Space ID (full match)", + "i18n_ab968d842f":"Build image Try to update the new version of the base image", + "i18n_ab9a0ee5bd":"Folder path, you need to dockerfile in the warehouse.", + "i18n_ab9c827798":"No docker cluster", + "i18n_abb6b7260b":"If you multiple select ssh, the directory below only displays the first item in the option, but the authorization directory needs to ensure that each item is configured with the corresponding directory", + "i18n_abba4043d8":"Search from scratch, 20 lines before the file, 3 lines after the file", + "i18n_abba4775e1":"command parameters", + "i18n_abd9ee868a":"Network mode: bridge, container: < name | id >, host, container, none", + "i18n_abdd7ea830":"Please enter a new password", + "i18n_abee751418":"Container Id: ", + "i18n_ac00774608":"first", + "i18n_ac0158db83":"Task ID", + "i18n_ac2f4259f1":"New Version:", + "i18n_ac408e4b03":"Please select a certificate type", + "i18n_ac5f3bfa5b":"Select items to monitor, file type items cannot be monitored", + "i18n_ac762710a5":"Support custom sorting fields: sort", + "i18n_ac783bca36":"Do you really want to log out and switch accounts to log in?", + "i18n_acb4ce3592":"Please select a file in a static file", + "i18n_acd5cb847a":"fail", + "i18n_ace71047a0":"Please go to [System Management] - > [Asset Management] - > [SSH Management] to add SSH, or associate and assign the newly added SSH authorization to this workspace", + "i18n_acf14aad3c":"There is no need to configure one by one. After configuration, the previous configuration will be overwritten.", + "i18n_ad209825b5":"Please select a trim type", + "i18n_ad311f3211":"Please select a warehouse.", + "i18n_ad35f58fb3":"occupied space", + "i18n_ad4b4a5b3b":"host", + "i18n_ad780debbc":"Rollback strategy", + "i18n_ad8b626496":"Do you really want to delete the build history?", + "i18n_ad9788b17d":"abnormal recovery", + "i18n_ad9a677940":"Specify settings file package mvn -s xxx/settings.xml clean package", + "i18n_adaf94c06b":"execution result", + "i18n_adbec9b14d":"Create backup information", + "i18n_adcd1dd701":"Back to list", + "i18n_add91bb395":"logical node", + "i18n_ae0d608495":"Whether to use MFA", + "i18n_ae0fd9b9d2":"backup time", + "i18n_ae12edc5bf":"Click Copy File Path", + "i18n_ae17005c0c":"not joined", + "i18n_ae35be7986":"Token, full match", + "i18n_ae653ec180":"detailed description", + "i18n_ae6838c0e6":"Node distribution", + "i18n_ae809e0295":"Suffix, precision search", + "i18n_aeade8e979":"uninitialized", + "i18n_aeb44d34e6":"One-time donation sponsorship", + "i18n_aec7b550e2":"Delete Workspace Confirmation", + "i18n_aed1dfbc31":"middle", + "i18n_aefd8f9f27":"Please select a restore method", + "i18n_af013dd9dc":"It will be automatically refreshed after successful restart.", + "i18n_af0df2e295":"You need to configure the file suffix that allows editing in the ssh information.", + "i18n_af14cd6893":"Please fill in the build DSL configuration content, you can click the switch tab above to view the configuration example", + "i18n_af3a9b6303":"WeCom scan code account login", + "i18n_af427d2541":"data update time", + "i18n_af4d18402a":"It has been disconnected.", + "i18n_af51211a73":"A scroll bar will appear on the page content", + "i18n_af708b659f":"Memory:", + "i18n_af7c96d2b9":"The synchronization mechanism uses the container host to determine that it is the same server (docker).", + "i18n_af924a1a14":"Abnormal download", + "i18n_af98c31607":"Number of physical node projects:", + "i18n_afa8980495":"Please enter the suffix and file encoding that allow editing of the file. If you do not set the encoding, the system encoding will be taken by default. Example: Set the encoding: txt {'@'} utf-8, do not set the encoding: txt", + "i18n_afb9fe400b":"Usage rate:", + "i18n_b04070fe42":"Select proxy type", + "i18n_b04209e785":"Linked Data:", + "i18n_b05345caad":"owner", + "i18n_b07a33c3a8":"Please select a distribution node", + "i18n_b0b9df58fd":"SSH Node", + "i18n_b0fa44acbb":"Occupancy:", + "i18n_b10b082c25":"The values are: stop, beforeStop, start, beforeRestart", + "i18n_b1192f8f8e":"Is it really necessary to cancel the current distribution?", + "i18n_b11b0c93fa":"If the actual random access memory in the Linux may be too different from the values calculated by the free and total fields directly using the free -h command, then this is caused by the swap memory in your current server", + "i18n_b12d003367":"privacy field", + "i18n_b153126fc2":"Please enter a workspace name", + "i18n_b15689296a":"risk notice", + "i18n_b15d91274e":"close", + "i18n_b166a66d67":"Are you sure you want to move this number up?", + "i18n_b17299f3fb":"Plugin-side process ID:", + "i18n_b1785ef01e":"Node name", + "i18n_b186c667dc":"The distribution process requests the corresponding address, starts distribution, distribution completes, distribution fails, and cancels distribution.", + "i18n_b188393ea7":"Published SSH", + "i18n_b1a09cee8e":"empty restore", + "i18n_b1dae9bc5c":"administrator", + "i18n_b28836fe97":"The distribution ID is equivalent to the project ID.", + "i18n_b28c17d2a6":" (MEM) (0-3, 0, 1). Valid only on NUMA systems.", + "i18n_b29fd18c93":"Please select a project for publication", + "i18n_b2f296d76a":"5 minutes", + "i18n_b30d07c036":"Batch shutdown startup", + "i18n_b328609814":"Administrators have: Partial permissions to manage the server", + "i18n_b339aa8710":"form", + "i18n_b33c7279b3":"authentication method", + "i18n_b3401c3657":"container directory", + "i18n_b341f9a861":"task time", + "i18n_b343663a14":"Empty publishing means that before uploading a new file, all the files in the project folder directory will be deleted before saving the new file", + "i18n_b36e87fe5b":"Do not execute, but compile test cases mvn clean package -DskipTests", + "i18n_b37b786351":"group name", + "i18n_b384470769":"synchronous cache", + "i18n_b38d6077d6":"login IP", + "i18n_b38d7db9b0":"Download the build log. If the button is not available, it means that the log file does not exist. Generally, the build history related files are deleted.", + "i18n_b3913b9bb7":"Please enter the build environment variables: xx = abc multiple variables carriage return and line feed", + "i18n_b399058f25":"Powerful and secure password management paid app", + "i18n_b39909964f":"Please enter your email account", + "i18n_b3b1f709d4":"cull", + "i18n_b3bda9bf9e":"Please select a workspace", + "i18n_b3ef35a359":"source warehouse", + "i18n_b3f9beb536":": 3 to 18 minutes, executed every 5 minutes, i.e. 0:03, 0:08, 0:13, 0:18, 1:03, 1:08...", + "i18n_b3fe677b5f":"failure rate", + "i18n_b408105d69":"The password field and key field will not be returned during editing. Please click me if you need to reset or clear it.", + "i18n_b437a4d41d":"Also supports URL parameter format: test_par = 123abc & test_par2 = abc21", + "i18n_b44479d4b8":"Available tags", + "i18n_b4750210ef":"Cluster modification time:", + "i18n_b499798ec5":"Disable distribution node", + "i18n_b4a8c78284":"Select a workspace", + "i18n_b4c83b0b56":"Warehouse account number", + "i18n_b4dd6aefde":"Session has been closed [script-console]", + "i18n_b4e2b132cf":"The plug-in side running port is used by default:", + "i18n_b4fc1ac02c":"Cancel build", + "i18n_b4fd7afd31":"Personality configuration", + "i18n_b513f53eb4":"Timeout, in seconds", + "i18n_b515d55aab":"You can upload it in advance through Certificate Management or click Select Certificate at the back to select/import the certificate.", + "i18n_b53dedd3e0":"Commands executed before publishing (non-blocking commands), typically the close project command", + "i18n_b55f286cba":"Please read the instructions and precautions in the update log before loading, and", + "i18n_b56585aa18":"After configuration, you can control whether you want to prohibit users from operating certain functions for a certain period of time, and give priority to judging the disabled period", + "i18n_b57647c5aa":"Do you really want to unbind the node associated with the script?", + "i18n_b57ecea951":"Already running time:", + "i18n_b5a1e1f2d1":"Trigger type:", + "i18n_b5a6a07e48":"Tuesday", + "i18n_b5b51ff786":"Upload SQL file", + "i18n_b5c291805e":"initialization system", + "i18n_b5c3770699":"console", + "i18n_b5c5078a5d":"List of all IPv4 devices", + "i18n_b5ce5efa6e":"cluster service", + "i18n_b5d0091ae3":"Build ID", + "i18n_b5d2cf4a76":"When the target workspace already exists, the script will be automatically synchronized, the script content, default parameters, automatic execution, description", + "i18n_b5fdd886b6":"Full screen view log", + "i18n_b60352bc4f":"virtual", + "i18n_b6076a055f":"Login failed", + "i18n_b61a7e3ace":"Script name:", + "i18n_b63c057330":"Do you really want to delete the operation monitoring?", + "i18n_b650acd50b":"Restore default name", + "i18n_b6728e74a4":"Run directory:", + "i18n_b6a828205d":"cache build", + "i18n_b6afcf9851":"Forbidden commands are commands that are not allowed to be executed at the end point, separated by multiple commas. (Superadmin has no restrictions)", + "i18n_b6c9619081":"Port:", + "i18n_b6e8fb4106":"platform login", + "i18n_b6ee682dac":"Number of plugins:", + "i18n_b714160f52":"distribution project ID", + "i18n_b71a7e6aab":"local command", + "i18n_b7579706a3":"check", + "i18n_b7c139ed75":"If the project directory is large or involves deep directories, it is recommended to turn off the scan to avoid getting the project directory scan for too long and affecting performance", + "i18n_b7cfa07d78":"Confirm binding", + "i18n_b7df1586a9":"When the target workspace already exists, the docker repository configuration information will be automatically synchronized", + "i18n_b7ea5e506c":"System information", + "i18n_b7ec1d09c4":"Service ID", + "i18n_b7f770d80b":"You need to install the dependent npm i & & npm run build first.", + "i18n_b8545de30e":"Please select at least one node", + "i18n_b85b213579":"Sender name", + "i18n_b86224e030":"Node state", + "i18n_b87c9acca3":"Is it really necessary to force out of the cluster?", + "i18n_b8915a4933":"Is it really possible to turn off two-factor authentication for the current user?", + "i18n_b8ac664d98":"Check data table", + "i18n_b90a30dd20":"If you don't fill in here, the password will not be changed.", + "i18n_b91961bf0b":"The passed parameters are: projectId, projectName, type, result", + "i18n_b922323119":"Mirror tags, such as: key1 = value1 & key2 = value2", + "i18n_b939d47e23":"public key", + "i18n_b953d1a8f1":"Can't close", + "i18n_b96b07e2bb":"Trim only unused and unmarked images", + "i18n_b9a4098131":"Trigger address", + "i18n_b9af769752":"Mirror name required", + "i18n_b9b176e37a":"Please select a script", + "i18n_b9bcb4d623":"Plugin:", + "i18n_b9c1616fd5":"Docker connected by SSH method is not recommended for container building (SSH method is very unstable for building)", + "i18n_b9c4cf7483":" Replace all", + "i18n_b9c52d9a85":"File name:", + "i18n_ba17b17ba2":"There are no SSH script commands", + "i18n_ba1f68b5dd":"This makes", + "i18n_ba20f0444c":"forced deletion", + "i18n_ba311d8a6a":"script", + "i18n_ba3a679655":"Configure in yaml/yml format", + "i18n_ba452d57f2":"The most used partitions:", + "i18n_ba52103711":"Residual inode number", + "i18n_ba619a0942":"Default build errors will automatically ignore hidden files", + "i18n_ba6e91fa9e":"permission", + "i18n_ba6ea3d480":"The page is full screen and the height is 100%. Partial areas can be scrolled.", + "i18n_ba8d1dca4a":"Attention:", + "i18n_baafe06808":"security group rules", + "i18n_bab17dc6b1":"Select process name", + "i18n_baef58c283":"Please enter label, alphanumeric, length 1-10", + "i18n_baefd3db91":"Authorize the directory that can be accessed directly, and multiple carriage returns can be used to wrap lines.", + "i18n_bb316d9acd":"The download speed is determined according to the network speed. If the network is not good, the download will be slower.", + "i18n_bb4409015b":"machine ssh name", + "i18n_bb4740c7a7":"Execute, order", + "i18n_bb5aac6004":"Bundle sync to file center retention days", + "i18n_bb667fdb2a":"No alarm", + "i18n_bb7eeae618":"Statistics only:", + "i18n_bb8d265c7e":"Version must be greater than 18.", + "i18n_bb9a581f48":"Login is successful, MFA verification is required.", + "i18n_bb9ef827bf":"ban access", + "i18n_bba360b084":"Do you really want to delete the corresponding trigger?", + "i18n_bbbaeb32fc":"machine delay", + "i18n_bbcaac136c":"Is there incorrect data in the table?", + "i18n_bbd63a893c":"Automatically detect whether there is a docker in the server where the server level is located. If there is, it will be automatically added to the list", + "i18n_bbf2775521":"mirror name", + "i18n_bc2c23b5d2":"The trimming operation will delete the relevant data, please operate with caution. Please confirm the consequences of this operation before using it.", + "i18n_bc2f1beb44":"Do you really want to unlock the user?", + "i18n_bc4b0fd88a":"Network Reachable Testing", + "i18n_bc8752e529":"distribution project", + "i18n_bcaf69a038":"Please select a node", + "i18n_bcc4f9e5ca":"such as", + "i18n_bcf48bf7a8":"authorization URL", + "i18n_bcf83722c4":"Variable description", + "i18n_bd0362bed3":"new group", + "i18n_bd49bc196c":"Edit project", + "i18n_bd4e9d0ee2":"Original Name:", + "i18n_bd5d9b3e93":"Which docker to use to build, fill in the docker tag (the tag is configured on the docker editing page) The default query is the first one available, if the tag queries out multiple, it will be built in sequence", + "i18n_bd6c436195":"Please enter a script description", + "i18n_bd7043cae3":"remote download", + "i18n_bd7c8c96bc":"manual upload", + "i18n_bda44edeb5":"Can't operate", + "i18n_bdc1fdde6c":"Beta plan:", + "i18n_bdd4cddd22":"Will restore [", + "i18n_bdd87b63a6":"WeChat QR code", + "i18n_bdd9d38d7e":"column width", + "i18n_be166de983":"Soft chain project", + "i18n_be1956b246":"Dark 2 blackboard", + "i18n_be2109e5b1":"Are you sure you want to reset the user password?", + "i18n_be24e5ffbe":"Java project (java -jar xxx)", + "i18n_be28f10eb6":"Please select the published primary catalog and fill in the secondary catalog", + "i18n_be381ac957":"Please select the warehouse to use", + "i18n_be3a4d368e":"Distribution in progress, 2: distribution ended, 3: cancelled, 4: distribution failed", + "i18n_be4b9241ec":"The default status code of 200 indicates successful execution.", + "i18n_be5b6463cf":"syntax reference", + "i18n_be5fbbe34c":"save", + "i18n_beafc90157":"The script library distributed to the machine node is referenced using G {'@'}(\" xxxx \") format in the node script support, and the system will automatically replace the script content in the referenced script library when there is a reference", + "i18n_bebcd7388f":"Loading build data", + "i18n_bec98b4d6a":"Status:", + "i18n_becc848a54":"Absolute path of private key file (new file before absolute path)", + "i18n_bef1065085":"Chrome extension", + "i18n_bf0e1e0c16":"Enter the warehouse name or warehouse path to {slot1}", + "i18n_bf77165638":"Are you sure you want to restart the current container?", + "i18n_bf7da0bf02":"new password", + "i18n_bf91239ad7":"command description", + "i18n_bf93517805":"The following configuration information is only valid for the current browser", + "i18n_bf94b97d1a":"Modification time:", + "i18n_bfacfcd978":"Automatically load environment variables", + "i18n_bfc04cfda7":"branch", + "i18n_bfda12336c":"Search view", + "i18n_bfe68d5844":"link", + "i18n_bfe8fab5cd":"You need to configure the authorization directory (authorization can be used to publish normally). The authorization directory is mainly used to determine which directories can be published to", + "i18n_bfed4943c5":"parameter value", + "i18n_c00fb0217d":"Please fill in the user's nickname", + "i18n_c03465ca03":"banned quantity", + "i18n_c0996d0a94":" : Executed every Monday and Tuesday at 11:59", + "i18n_c0a9e33e29":"Please select Build the corresponding branch, required", + "i18n_c0d19bbfb3":"Please enter the value of the key", + "i18n_c0d38f475f":"soft memory", + "i18n_c0d5d68f5f":"ignore", + "i18n_c0e498a259":"Click the icon to view all associated tasks", + "i18n_c0f4a31865":"logical deletion", + "i18n_c11eb9deff":"File MD5", + "i18n_c12ba6ff43":"We have the right to pursue all illegal gains of individuals who destroy open source and profit from it, and we are welcome to provide us with clues of infringement.", + "i18n_c163613a0d":"If the current cluster still exists, there may be data inconsistencies.", + "i18n_c1690fcca5":"Import certificate", + "i18n_c16ab7c424":"]? Note: Canceling/stopping the build may not necessarily shut down all associated processes normally", + "i18n_c1786d9e11":"Node address", + "i18n_c17aefeebf":"System name:", + "i18n_c18455fbe3":"Authorization information error", + "i18n_c195df6308":"abnormal", + "i18n_c1af35d001":"bundle", + "i18n_c1b72e7ded":"For variable name", + "i18n_c23fbf156b":"ssh not selected", + "i18n_c26e6aaabb":"There are legal risks in modifying or deleting copyright information without authorization", + "i18n_c2add44a1d":"Some examples:", + "i18n_c2b2f87aca":"Script lonely data", + "i18n_c2ee58c247":"build command", + "i18n_c2f11fde3a":"Initialize the system account", + "i18n_c31ea1e3c4":"No operation logs", + "i18n_c325ddecb1":"The length of the CPU cycle, expressed in microseconds.", + "i18n_c32e7adb20":"Please enter remote download safe HOST, carriage return support input multiple paths, sample https://www.test.com, etc", + "i18n_c34175dbef":"Console log backup path: ", + "i18n_c3490e81bf":"The previous container will be automatically deleted before restarting the creation.", + "i18n_c34f1dc2b9":"The id, token, and trigger builds in the parameters are consistent", + "i18n_c3512a3d09":"Please select Select Type", + "i18n_c35c1a1330":"sorted value", + "i18n_c360e994db":"sort", + "i18n_c36ab9a223":"Create a new network stack for containers on the docker bridge", + "i18n_c37ac7f024":"Clear Code", + "i18n_c3aeddb10d":"There are no logical nodes in the current workspace, so the node distribution cannot be created.", + "i18n_c3f28b34bb":"cluster name", + "i18n_c446efd80d":"Edit Script", + "i18n_c4535759ee":"System prompt", + "i18n_c46938460b":"The system uses the docker http interface to communicate and manage with docker, but the default is", + "i18n_c469afafe0":"Are you sure you want to delete the current container?", + "i18n_c494fbec77":"add manually", + "i18n_c4a61acace":"Order?", + "i18n_c4b5d36ff0":"Node status will automatically identify whether there is a java environment in the server, if there is no Java environment can not quickly install the node", + "i18n_c4cfe11e54":"If the uploaded compressed file is automatically decompressed, the supported compressed package types are tar.", + "i18n_c4e0c6b6fe":"Filter items", + "i18n_c4e2cd2266":"You also need to operate on the relevant data to achieve the expected sorting.", + "i18n_c5099cadcd":"number of plugins", + "i18n_c53021f06d":"Fill in [xxx", + "i18n_c530a094f9":"Build method:", + "i18n_c538b1db4a":"Soft chain project (similar to a file whose copy of the project uses the relevant path)", + "i18n_c583b707ba":"Description of parameters", + "i18n_c5a2c23d89":"non-full screen", + "i18n_c5aae76124":"Bind SSH", + "i18n_c5bbaed670":"status code error", + "i18n_c5c3583bfc":"Send verification code", + "i18n_c5c69827c5":"Network port restrictions", + "i18n_c5de93f9c7":"Manual confirmation is required.", + "i18n_c5e7257212":"Current node ID:", + "i18n_c5f9a96133":"The system will restrict a lot of permissions to the demo account by default. It is not recommended to use the demo account for non-demo scenarios.", + "i18n_c600eda869":"The command file will be executed in {'${data directory}'}/script/xxxx.sh, bat", + "i18n_c618659cea":"custom branch wildcard expression", + "i18n_c6209653e4":"SMTP server domain name", + "i18n_c68dc88c51":"Please enter a monitor name", + "i18n_c6a3ebf3c4":"receive size", + "i18n_c6e4cddba0":"Please select the function to monitor.", + "i18n_c6f1c6e062":"Directory cannot be edited", + "i18n_c6f6a9b234":"Move to another workspace", + "i18n_c704d971d6":"Please fill in the build and product", + "i18n_c7099dabf6":"Uploading file", + "i18n_c71a67ab03":"After distribution", + "i18n_c75b14a04e":"The monitoring frequency can be modified in the server level configuration file", + "i18n_c75d0beca8":"Open the page operation guidance and navigation", + "i18n_c7689f4c9a":"Here the build command will eventually be executed on the server. If there are multiple lines of commands then", + "i18n_c76cfefe72":"Port", + "i18n_c7c4e4632f":"The allowable failure rate during the update period", + "i18n_c7e0803a17":"The password will only appear once, and the password cannot be viewed again after closing the window.", + "i18n_c806d0fa38":"compressed package", + "i18n_c83752739f":"Supported fields can be viewed through the interface return", + "i18n_c840c88b7c":"Really want to empty [", + "i18n_c84ddfe8a6":"execution log", + "i18n_c8633b4b77":"Import repository via private token", + "i18n_c87bd94cd7":"Mirror Id: ", + "i18n_c889b9f67d":"Add related items", + "i18n_c89e9681c7":"Temporary file footprint", + "i18n_c8a2447aa9":"join", + "i18n_c8b2aabc07":"If the file does not exist: MemAvailable, then MemAvailable = MemFree + Active (file) + Inactive (file) + SReclaimable, so in this system, the memory occupation calculation method: memory occupation = (total- (MemFree + Active (file) + Inactive (file) + SReclaimable))/total", + "i18n_c8c452749e":"Select SQL file", + "i18n_c8c45e8467":"Please configure according to your own project start time", + "i18n_c8c6e37071":"Warm reminder", + "i18n_c8ce4b36cb":"rename", + "i18n_c90a1f37ce":"Node ID", + "i18n_c96b442dfb":"Universal Mailbox SSL", + "i18n_c96f47ec1b":"Asynchronous requests do not guarantee orderliness", + "i18n_c972010694":"Product catalog", + "i18n_c9744f45e7":"No", + "i18n_c97e6e823a":"Restart the container unless it has been stopped", + "i18n_c983743f56":"total memory", + "i18n_c996a472f7":"Refresh every day at 0/12 o'clock", + "i18n_c99a2f7ed8":"start command", + "i18n_c9b0f8e8c8":"Really want to delete", + "i18n_c9b79a2b4f":"After the global proxy configuration, it will take effect on the network of the server. The proxy implementation method: ProxySelector", + "i18n_c9daf4ad6b":"multithreading", + "i18n_ca32cdfd59":"Memory usage:", + "i18n_ca69dad8fc":"After the process executes the script, the last line of the output must be: running", + "i18n_ca774ec5b4":"The last build was based on commitId:", + "i18n_caa9b5cd94":"Label values need to be configured into the build DSL", + "i18n_cab7517cb4":"Node address:", + "i18n_cabdf0cd45":"Select product", + "i18n_cac26240b5":"container log", + "i18n_cac6ff1d82":"Get build status addresses in bulk", + "i18n_cad01fe13c":"Black and white ambiance-mobile", + "i18n_caf335a345":"Java information", + "i18n_cb09b98416":"Personalized configuration area", + "i18n_cb156269db":"In seconds, the minimum is 1 second", + "i18n_cb25f04b46":"Search in the last 3 lines of the file", + "i18n_cb28aee4f0":"Node distribution [Migration is not supported for the time being]", + "i18n_cb46672712":"Log reading [Migration is not supported for the time being]", + "i18n_cb93a1f4a5":", root, manager", + "i18n_cb951984f2":"Exists", + "i18n_cb9b3ec760":"Batch trigger parameter BODY json : [ { \" id \":\" 1 \",\" token \":\" a \",\" action \":\" status \"}]", + "i18n_cbc44b5663":"End of execution time", + "i18n_cbcc87b3d4":"Name, copyright, etc", + "i18n_cbce8e96cf":"Certificate Information", + "i18n_cbdc4f58f6":"Please enter the name of the machine", + "i18n_cbdcabad50":"release", + "i18n_cbee7333e4":"Local command refers to the execution of multiple commands locally at the server level to achieve publication", + "i18n_cc3a8457ea":"Node state is acquired asynchronously with a certain time lag", + "i18n_cc42dd3170":"open", + "i18n_cc51f34aa4":"editing service", + "i18n_cc5dccd757":"Number of script templates in logical nodes in the workspace:", + "i18n_cc617428f7":"Privacy variables refer to some important information such as password fields or key keys. Privacy fields can only be modified and cannot be viewed (the corresponding value cannot be seen in the edit pop-up window). Once the privacy field is created, it cannot be switched to a non-privacy field.", + "i18n_cc637e17a0":"Product:", + "i18n_cc92cf1e25":"Please fill in the product catalog", + "i18n_cc9a708364":"status code error ", + "i18n_cca4454cf8":"Please enter the content of the announcement", + "i18n_ccb2fdd838":"Switch workspaces", + "i18n_ccb91317c5":"command content", + "i18n_ccea973fc7":"Current node address:", + "i18n_cd1aedc667":"In Settings -- > Apps -- > Generate Token", + "i18n_cd649f76d4":"time frame", + "i18n_cd998f12fa":"When a node already exists in the target workspace, the node authorization information and agent configuration information will be automatically synchronized", + "i18n_cda84be2f6":"operation log", + "i18n_cdc478d90c":"system name", + "i18n_ce043fac7d":"The current workspace does not have SSH yet.", + "i18n_ce07501354":"Click on a number to see a running task", + "i18n_ce1ecd8a5b":"There are many more related dependencies on open source components", + "i18n_ce23a42b47":"task name", + "i18n_ce40cd6390":"associative script", + "i18n_ce559ba296":"Run command", + "i18n_ce7e6e0ea9":"The current configuration is only valid for the selected workspace, other workspaces need to be configured separately", + "i18n_ce84c416f9":"Please enter user information url [userInfoUri]", + "i18n_ced3d28cd1":"Do not scan", + "i18n_ceee1db95a":"container port", + "i18n_ceffe5d643":"two-step verification app", + "i18n_cf38e8f9fd":"The authorization path configuration currently distributed for the node", + "i18n_cfa72dd73a":"Please enter the cron expression to check", + "i18n_cfb00269fd":"Execute script", + "i18n_cfbb3341d5":"The currently logged in account is:", + "i18n_cfd482e5ef":"No data yet, please add the node project data first", + "i18n_cfeea27648":"Create file /xxx/xxx/xxx", + "i18n_cfeff30d2c":"Table of Contents:", + "i18n_d00b485b26":"Rollback", + "i18n_d0132b0170":"It cannot be operated during the restoration process...", + "i18n_d02a9a85df":"Please select an alarm contact", + "i18n_d047d84986":"A/Total", + "i18n_d0874922f0":"Binding the specified directory can be managed online, and building the ssh publication directory also needs to be configured here", + "i18n_d0a864909b":"How to generate public and private keys", + "i18n_d0b2958432":"version number", + "i18n_d0b7462bdc":"This editor can only edit the name information of the current SSH in this workspace", + "i18n_d0be2fcd05":": indicates an interval, e.g. on minutes, every two minutes, likewise * can be replaced by a list of numbers, separated by commas", + "i18n_d0c06a0df1":"Please enter the verification code", + "i18n_d0c879f900":"Before uploading", + "i18n_d0eddb45e2":"private key", + "i18n_d0f53484dc":"Script ID:", + "i18n_d1498d9dbf":"build notes", + "i18n_d159466d0a":"Linked Data Name", + "i18n_d175a854a6":"build directory", + "i18n_d17eac5b5e":"I want to join", + "i18n_d18d658415":"script ID", + "i18n_d19bae9fe0":"After the plug-in is successfully installed and started, the node information will be actively reported. If the reported IP + PROP can communicate normally, the node information will be added.", + "i18n_d1aa9c2da9":"Subnet mask:", + "i18n_d1b8eaaa9e":"You need to enter the verification code to confirm the binding before it takes effect.", + "i18n_d1f0fac71d":"Script cannot be saved without node selected", + "i18n_d1f56b0a7e":"The incoming parameters are: monitors Id, monitors Name, nodeId, nodeName, projectId, projectName, title, content, runStatus", + "i18n_d263a9207f":"Support html format", + "i18n_d27cf91998":"Reference address:", + "i18n_d2cac1245d":"Whether to [", + "i18n_d2e2560089":"file name", + "i18n_d2f484ff7e":"If the last line of output is not the expected format item status will be Not running", + "i18n_d2f4a1550a":"No node scripts", + "i18n_d301fdfc20":"After turning on automatic user creation, the first login only automatically creates an account, and the administrator needs to manually assign the permission group.", + "i18n_d30b8b0e43":"unbuilt", + "i18n_d31d625029":"Joining the beta program can provide timely access to the latest features, some optimization features, and the fastest bug-fixing version, but the beta version may also be unstable in some new features.", + "i18n_d324f8b5c9":" replace", + "i18n_d35a9990f4":"There are no more node projects, please create the project first", + "i18n_d373338541":"Support IP address, domain name, hostname", + "i18n_d3ded43cee":"View the build log", + "i18n_d3e480c8c0":"number of failures", + "i18n_d3fb6a7c83":", will automatically jump to the login page after 2 seconds", + "i18n_d40b511510":"certificate", + "i18n_d438e83c16":"project grouping", + "i18n_d4744ce461":"The permission group has not been configured, so users cannot be created", + "i18n_d47ea92b3a":"Edit certificate", + "i18n_d4aea8d7e6":"execution times", + "i18n_d4e03f60a9":"Check the project status when the plug-in side starts, and try to execute the startup project if the project status is not running.", + "i18n_d5269713c7":"Indicates that when bundled as a folder, it will be packaged as", + "i18n_d57796d6ac":" : Range: 0~ 59", + "i18n_d584e1493b":"Search ssh name", + "i18n_d58a55bcee":"close", + "i18n_d5a73b0c7f":"upload", + "i18n_d5c2351c0e":"Please select a week that can be executed", + "i18n_d5c68a926e":"execution order", + "i18n_d5d46dd79b":"minute level", + "i18n_d615ea8e30":"Select upgrade file", + "i18n_d61af4e686":"Breakpoint/sharding alias download", + "i18n_d61b8fde35":"Switch cluster", + "i18n_d64cf79bd4":"Are you sure you want to join the beta program?", + "i18n_d65d977f1d":"Fill in the operating parameters", + "i18n_d679aea3aa":"Running", + "i18n_d6937acda5":"The folder where the project is stored, the folder where the jar package is stored", + "i18n_d6a5b67779":"system process", + "i18n_d6cdafe552":"Session has been closed [project-console]", + "i18n_d6eab4107a":"Java project (java -jar Springboot war) [not recommended]", + "i18n_d72471c540":"browser ID", + "i18n_d731dc9325":"Timestamp:", + "i18n_d7471c0261":"Please select an execution node", + "i18n_d75c02d050":"Stop project", + "i18n_d7ac764d3a":"The distribution interval time (sequential restart, full sequential restart) method will only take effect", + "i18n_d7ba18c360":"The distribution node refers to a script that automatically synchronizes the content of the script node after editing the script", + "i18n_d7bebd0e5e":"Please go to the console to control the status operation.", + "i18n_d7c077c6f6":"command log", + "i18n_d7cc44bc02":"user profile", + "i18n_d7d11654a7":"does not exist", + "i18n_d7ec2d3fea":"name", + "i18n_d7ee59f327":"Private key, if left blank, the default configuration in the $HOME/.ssh directory will be used. Supported configuration file directory: file:", + "i18n_d7ef19d05b":"Template node not configured yet", + "i18n_d81bb206a8":"none", + "i18n_d82ab35b27":"The first N lines of the file", + "i18n_d82b19148f":"Please select the machine node to synchronize the authorization", + "i18n_d83aae15b5":"Online build files are mainly saved, warehouse files, build history products, etc. Active cleaning is not supported. If the file occupies too much, you can configure retention rules and whether to save warehouse, product files, etc. for a single build configuration", + "i18n_d84323ba8d":"The warehouse may exist repeatedly after automatic migration, please solve it manually.", + "i18n_d87940854f":"number of plans", + "i18n_d87f215d9a":"Card", + "i18n_d88651584f":"free space", + "i18n_d8a36a8a25":"Edit Docker cluster", + "i18n_d8bf90b42b":"Other users can configure permissions to lift restrictions", + "i18n_d8c7e04c8e":"information", + "i18n_d8db440b83":"You need to evaluate whether you can join the beta based on your business situation.", + "i18n_d911cffcd5":"Download address", + "i18n_d921c4a0b6":"Really want to delete \"", + "i18n_d926e2f58e":"Cancel task", + "i18n_d937a135b9":"Light eclipse", + "i18n_d94167ab19":"network port", + "i18n_d9435aa802":"parse mode", + "i18n_d9531a5ac3":"The permission group cannot be created without configuring the user.", + "i18n_d9569a5d3b":"Multiple IPs can be used", + "i18n_d9657e2b5f":"Please enter the project folder", + "i18n_d9ac9228e8":"create", + "i18n_d9c28e376c":"Function Management", + "i18n_da1abf0865":"Verification code 6 is a pure number", + "i18n_da1cb76e87":"Please enter the script content", + "i18n_da317c3682":"[Recommended] Use the fast installation method to import the machine and automatically add logical nodes", + "i18n_da4495b1b4":"Address:", + "i18n_da509a213f":"The workspace is used to isolate data. There can be different data, different permissions, different menus, etc. under the workspace to achieve permission control", + "i18n_da671a4d16":"WeChat:", + "i18n_da79c2ec32":"configuration example", + "i18n_da89135649":"Enterprise Services", + "i18n_da8cb77838":"Online upgrade", + "i18n_da99dbfe1f":"distribution status", + "i18n_dab864ab72":"quick binding", + "i18n_dabdc368f5":"Please select an assignment type", + "i18n_dacc2e0e62":"Hardware hard disk", + "i18n_dadd4907c2":"The table of contents,", + "i18n_daf783c8cd":"point", + "i18n_db06c78d1e":"test", + "i18n_db094db837":"Modify variable value address", + "i18n_db2d99ed33":"The cluster address has not been configured, so the cluster cannot be switched.", + "i18n_db4470d98d":"alarm status", + "i18n_db4b998fbd":"Oauth2 usage tips", + "i18n_db5cafdc67":"Do you really want to untie the node?", + "i18n_db686f0328":"Public key, if not filled in, the default configuration in the $HOME/.ssh directory will be used. Supported configuration file directory: file:", + "i18n_db709d591b":"out of sync", + "i18n_db732ecb48":"delay", + "i18n_db81a464ba":"When the build is executed, a container will be generated for execution, and the corresponding container will be automatically deleted after the build is completed.", + "i18n_db9296212a":"timed build", + "i18n_dba16b1b92":"build event", + "i18n_dbad1e89f7":"two-step verification", + "i18n_dbb166cf29":"service id", + "i18n_dbb2df00cf":"release directory", + "i18n_dbba7e107a":"publish project", + "i18n_dbc0b66ca4":"result", + "i18n_dc0d06f9c7":"Please fill in the published secondary catalog", + "i18n_dc2961a26f":"Node name:", + "i18n_dc2c61a605":"Build your own GitLab", + "i18n_dc32f465da":"Container address tcp", + "i18n_dc3356300f":"If you want to configure SSH, please go to [System Management] - > [Asset Management] - > [SSH Management] to configure.", + "i18n_dc39b183ea":"Are you sure you want to ignore the binding two-step verification? It is strongly recommended that the super administrator turn on two-step verification to ensure account security.", + "i18n_dcc846e420":"Batch build all parameters example", + "i18n_dcd72e6014":"Get a single build state address", + "i18n_dce5379cb9":"hide", + "i18n_dcf14deb0e":"Proxy Address (127.0.0.1:8888)", + "i18n_dd1d14efd6":"View script", + "i18n_dd23fdf796":"You can switch nodes during editing, but pay attention to whether the data matches.", + "i18n_dd4e55c39c":"Not started", + "i18n_dd95bf2d45":"normal login", + "i18n_ddc7d28b7b":"variable", + "i18n_dddf944f5f":"Reset the page operation guide and navigation successfully.", + "i18n_ddf0c97bce":"Please pay attention to backing up your data to prevent data loss!!", + "i18n_ddf7d2a5ce":"command", + "i18n_de17fc0b78":"Maximum usage of hard disk:", + "i18n_de3394b14e":"No asset machine", + "i18n_de4cf8bdfa":"Pay to join our technical exchange group to answer all your questions first", + "i18n_de5dadc480":"Pull log", + "i18n_de6bc95d3b":"Clear the current directory file", + "i18n_de78b73dab":"single trigger address", + "i18n_debdfce084":"Please enter a cluster name", + "i18n_decef97c7c":"Server level IP authorization configuration", + "i18n_deea5221aa":"tag", + "i18n_df011658c3":"scope", + "i18n_df1da2dc59":"The number of microseconds of CPU time that a container can obtain in one CPU cycle.", + "i18n_df3833270b":"Address:", + "i18n_df39e42127":"automate", + "i18n_df59a2804d":"prohibit scanning", + "i18n_df5f80946d":"#node mirror source https://registry.npmmirror.com/-/binary/node/", + "i18n_df9497ea98":"exist", + "i18n_df9d1fedc5":"Node distribution refers to the deployment of a project in multiple nodes using node distribution to complete project publishing operations in multiple nodes in one step", + "i18n_dfb8d511c7":"user name", + "i18n_dfcc9e3c45":"post-distribution operation", + "i18n_e039ffccc8":"Restore this file to the project directory?", + "i18n_e049546ff3":"Modified in [System Configuration Catalog]", + "i18n_e06497b0fb":"View currently available containers", + "i18n_e06caa0060":"File modification time", + "i18n_e074f6b6af":"SMTP server port", + "i18n_e07cbb381c":"There are no warehouses.", + "i18n_e09d0d8c41":"It is not recommended to use common names such as", + "i18n_e0a0e26031":"Create a container using the current image", + "i18n_e0ae638e73":"Retaining products refers to whether to retain bundle-related files after the build is completed for rollback", + "i18n_e0ba3b9145":"Node script", + "i18n_e0ce74fcac":"auto scroll", + "i18n_e0d6976b48":"Please select the cluster affinity group", + "i18n_e0ea800e34":"Package formal environment npm i & & npm run build: prod", + "i18n_e0ec07be7d":"Client side key", + "i18n_e0f937d57f":"Temporary token", + "i18n_e0fcbca309":"Workspace ID:", + "i18n_e15f22df2d":"Do you really want to clear the build information?", + "i18n_e166aa424d":"About the system", + "i18n_e17a6882b6":"script tag", + "i18n_e19cc5ed70":"synchronization node authorization", + "i18n_e1c965efff":"Please select a status", + "i18n_e1fefde80f":"The node account password is generated by the system by default: it can be viewed through the agent_authorize .json file in the plug-in data directory (if the account password is customized, there will be no such file)", + "i18n_e222f4b9ad":"Before execution, you need to check whether the address in the command can be accessed in the corresponding server. If it cannot be accessed, the node cannot be automatically bound.", + "i18n_e257dd2607":"Please select SSH connection information", + "i18n_e26dcacfb1":" check ", + "i18n_e2adcc679a":"Unit ns second", + "i18n_e2b0f27424":"The project ID will only be unique in the machine node, and the same project ID is allowed in different workspaces (the same workspace).", + "i18n_e2be9bab6b":"Copy any of the following commands to the server that has not installed the plug-in side to execute. You need to release it before execution", + "i18n_e2f942759e":"session exception", + "i18n_e30a93415b":"With a private token, you can manage the repository within your account without entering your account password. You can specify the permissions that the token has when you create it.", + "i18n_e319a2a526":"Reset to regenerate the trigger address. After the reset is successful, the previous trigger address will be invalid. The build trigger is bound to the generate trigger to the operator. If the corresponding account is deleted, the trigger will be invalid.", + "i18n_e31ca72849":"Upload compressed file", + "i18n_e354969500":"Token Import", + "i18n_e362bc0e8a":"Path: {source} (host) = > {destination} (container)", + "i18n_e39de3376e":"distribution", + "i18n_e39f4a69f4":"number of scripts", + "i18n_e39ffe99e9":"Please enter your password", + "i18n_e3cf0abd35":"Email verification code", + "i18n_e3e85de50c":"Please choose a build method", + "i18n_e3ead2bd0d":"Common build command examples", + "i18n_e3ee3ca673":"Do not append script templates", + "i18n_e4013f8b81":"machine name", + "i18n_e414392917":"Cluster information:", + "i18n_e42b99d599":"month", + "i18n_e43359ca06":"Please select an SSH node", + "i18n_e44f59f2d9":"pre-issue command", + "i18n_e475e0c655":"certificate sharing", + "i18n_e48a715738":"New file", + "i18n_e4b51d5cd0":"running state", + "i18n_e4bea943de":"Warehouse address", + "i18n_e4bf491a0d":"Downloading, please wait...", + "i18n_e4d0ebcd58":"Select cluster", + "i18n_e5098786d3":"Args parameter", + "i18n_e54029e15b":"exit the cluster", + "i18n_e54c5ecb54":"edit build", + "i18n_e5915f5dbb":"(There are compatibility issues, you need to test in advance in actual use) python3 sdk image use: https://repo.huaweicloud.com/python/{'${PYTHON3_VERSION}'}/Python- {'${PYTHON3_VERSION}'} .tar.xz", + "i18n_e5a63852fd":"Node password, please check the information output by node startup", + "i18n_e5ae5b36db":"Key words highlighting, support regularization (regularization may affect performance, please use as appropriate)", + "i18n_e5f71fc31e":"Search", + "i18n_e60389f6d6":"Current front-end packaging time:", + "i18n_e60725e762":"Wednesday", + "i18n_e63fb95deb":"number of queues", + "i18n_e64d788d11":"Upgrade successful", + "i18n_e6551a2295":"Referencing the workspace environment variables can be easily modified using the same password in many places later", + "i18n_e6bf31e8e6":"Long term token", + "i18n_e6cde5a4bc":"The latest version was not checked.", + "i18n_e6e453d730":"Please enter a variable name", + "i18n_e6e5f26c69":"QQ mailbox SSL", + "i18n_e703c7367c":"Current status:", + "i18n_e710da3487":"time", + "i18n_e72a0ba45a":"user group", + "i18n_e72f2b8806":"Enter the warehouse name or warehouse path to search.", + "i18n_e747635151":"Script name", + "i18n_e76e6a13dd":"Do not reference environment variables", + "i18n_e78e4b2dc4":"level", + "i18n_e7d83a24ba":"number of successes", + "i18n_e7e8d4c1fb":"Breakpoint/sharding download", + "i18n_e7ffc33d05":"Execute after upload", + "i18n_e8073b3843":"Please select a user permission group", + "i18n_e825ec7800":"protocol type", + "i18n_e8321f5a61":"Publication method:", + "i18n_e83a256e4f":"confirm", + "i18n_e84b981eb4":"Configuration value (e.g. 5g)", + "i18n_e8505e27f4":"Please read the instructions and precautions in the update log before upgrading and", + "i18n_e8e3bfbbfe":"Confirm close", + "i18n_e8f07c2186":"If not filled in, the txt in the compressed package will be parsed.", + "i18n_e9290eaaae":"Close left", + "i18n_e930e7890f":"The expression is similar to Linux crontab expression in that the expression is divided into five parts using spaces, in order:", + "i18n_e95f9f6b6e":"SSL connection", + "i18n_e96705ead1":"If the button is not available, it means that the current node has been closed and needs to be enabled in editing.", + "i18n_e976b537f1":"cache monitoring", + "i18n_e97a16a6d7":" Execute every two minutes", + "i18n_e9bd4484a7":"Sender email account", + "i18n_e9c2cb1326":"secondary ID", + "i18n_e9e9373c6f":"Performing a task", + "i18n_e9ea1e7c02":"File storage days, default 3650 days", + "i18n_e9ec2b0bee":"And wait for the previous project to start and complete before closing the next project", + "i18n_e9f2c62e54":"After adding default parameters, you need to fill in the parameter values when manually executing the script", + "i18n_ea15ae2b7f":"option", + "i18n_ea3c5c0d25":"Temporary file occupies space:", + "i18n_ea58a20cda":"MACHINE DOCKER", + "i18n_ea7fbabfa1":"Please enter your account name", + "i18n_ea89a319ec":"#Host directory and container directory mount /host:/container: ro", + "i18n_ea8a79546f":"Please enter the published file id.", + "i18n_ea9f824647":"Pull warehouse timeout in seconds", + "i18n_eaa5d7cb9b":"expiration date", + "i18n_eadd05ba6a":"medium", + "i18n_eaf987eea0":"Weight (relative weight).", + "i18n_eb164b696d":"exclude publishing", + "i18n_eb5bab1c31":"optional", + "i18n_eb79cea638":"Friday", + "i18n_eb7f9ceb71":"Script library:", + "i18n_eb969648aa":"Please back up the data in advance before operating.", + "i18n_ebc2a1956b":"edit monitoring", + "i18n_ebc96f0a5d":"Total memory (memory + swap). Set to -1 to disable swap.", + "i18n_ec1f13ff6d":"Total:", + "i18n_ec219f99ee":"End of execution", + "i18n_ec22193ed1":"Please select a group", + "i18n_ec537c957a":"{Slot1} machine directory", + "i18n_ec6e39a177":"Are you sure you want to download the latest version of the update?", + "i18n_ec7ef29bdf":"Please enter static, carriage return supports entering multiple paths, the system will automatically filter../path, and the root path is not allowed to be entered", + "i18n_ec989813ed":"Status information:", + "i18n_eca37cb072":"creation time", + "i18n_ecdf9093d0":"Expand multiple simultaneously", + "i18n_ecff77a8d4":"use", + "i18n_ed145eba38":"Hard disk occupancy", + "i18n_ed19a6eb6f":"Online build file occupies space", + "i18n_ed367abd1a":"Modify user profile", + "i18n_ed39deafd8":"Edit repository", + "i18n_ed40308fe9":"#maven mirror source https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_ed6a8ee039":" : Represents multiple timing expressions", + "i18n_ed8ea20fe6":"Installation ID", + "i18n_edb4275dcd":"Gmail", + "i18n_edb881412a":"Note: In order to avoid unnecessary events to execute the script, the remarks of the selected script contain the keyword of the event parameter that needs to be implemented. If the success event needs to be executed, then the remarks of the selected script need to include the success keyword", + "i18n_edc1185b8e":"Attempt to auto-renew failed", + "i18n_edd716f524":"Please select a first-level directory for publication", + "i18n_ede2c450d1":"No login logs", + "i18n_ee19907fad":"#Basic image, currently only supports ubuntu-latest", + "i18n_ee4fac2f3c":"In order to avoid monitoring blockage caused by some nodes failing to respond in time, the node statistical timeout time is not affected by the node timeout configuration, and the default timeout time (10 seconds) will be used.", + "i18n_ee6ce96abb":"s second", + "i18n_ee8ecb9ee0":"priority", + "i18n_ee9a51488f":"Please enter a publishing directory", + "i18n_eeb6908870":"previous step", + "i18n_eec342f34e":"The default account is: jpomAgent.", + "i18n_eee6510292":"Configure the authorization directory", + "i18n_eee83a9211":"resource", + "i18n_eeef8ced69":"Unbinding checks for data correlations and automatically deletes node items and script cache information", + "i18n_eef3653e9a":"JVM {slot1}, {slot2}. For example: -Xms512m -Xmx512m", + "i18n_eef4dfe786":"Java project (java -Djava.ext.dirs = lib -cp conf: run.jar $MAIN_CLASS)", + "i18n_ef016ab402":"Confirm the creation of this", + "i18n_ef28d3bff2":"The page content automatically expands and a screen scroll bar appears", + "i18n_ef651d15b0":"After creation, it cannot be modified. The distribution ID is equivalent to the project ID.", + "i18n_ef734bf850":"More details", + "i18n_ef7e3377a0":"allowed time slot", + "i18n_ef800ed466":"The main class where the program runs (jar mode can be left blank)", + "i18n_ef8525efce":"Assign to another workspace", + "i18n_ef9c90d393":"There is no script library.", + "i18n_efae7764ac":"Account login", + "i18n_efafd0cbd4":"Password (6-18 digits, letters, symbols)", + "i18n_efb88b3927":"system uptime", + "i18n_efd32e870d":"Plugin build time", + "i18n_efe71e9bec":"Do you really want to unbind the current node distribution?", + "i18n_efe9d26148":"Do you really want to delete the certificate? Deleting will delete the certificate file together?", + "i18n_f038f48ce5":"Edit script", + "i18n_f04a289502":"Svn ssh required Login user", + "i18n_f05e3ec44d":"Forbidden access, current IP restricts access", + "i18n_f06f95f8e6":"Lonely Data", + "i18n_f087eb347c":"Build command example", + "i18n_f08afd1f82":"Selected", + "i18n_f0a1428f65":"Account support for referencing workspace variables:", + "i18n_f0aba63ae7":"issuer", + "i18n_f0db5d58cb":"Enable two-step verification to make the account more secure", + "i18n_f0eb685a84":"File to be compatible with your machine", + "i18n_f105c1d31d":"executor of last resort", + "i18n_f113c10ade":"empty", + "i18n_f139c5cf32":"Enter a new name", + "i18n_f175274df0":"Login name, account number, cannot be modified after creation", + "i18n_f1a2a46f52":"#Which docker to use to build, fill in the docker tag, the default query is the first one available, if the tag queries more than one, also select the first result", + "i18n_f1b2828c75":"Install the plug-in", + "i18n_f1d8533c7f":"Please enter the certificate information or select the certificate information. Fill in the certificate information rules: Serial number: Certificate type", + "i18n_f1fdaffdf0":"background build", + "i18n_f240f9d69c":"Branch name:", + "i18n_f26225bde6":"detail", + "i18n_f26ef91424":"download", + "i18n_f282058f75":"Static file projects (frontend, logs, etc.)", + "i18n_f2d05944ad":"Create a Docker cluster", + "i18n_f30f1859ba":"If you modify it based on Jpom secondary development", + "i18n_f332f2c8df":"gateway", + "i18n_f3365fbf4d":"Docker not fetched or monitoring disabled", + "i18n_f33db5e0b2":"Click to refresh the build information", + "i18n_f37f8407ec":"File ID:", + "i18n_f3947e6581":"Open-source is not the same as free", + "i18n_f3e93355ee":"Restart project", + "i18n_f425f59044":"System version:", + "i18n_f49dfdace4":"permission group", + "i18n_f4b7c18635":"The password length is 6-20.", + "i18n_f4baf7c6c0":"not started", + "i18n_f4bbbaf882":"Branch/Tab", + "i18n_f4dd45fca9":"Please enter a remote address", + "i18n_f4edba3c9d":"Unknown table type", + "i18n_f4fb0cbecf":"No results yet.", + "i18n_f5399c620e":"Do you really want to delete this node from the cluster?", + "i18n_f562f75c64":"service address", + "i18n_f56c1d014e":"Successful execution", + "i18n_f5c3795be5":"official", + "i18n_f5d0b69533":"The complete private key content, such as", + "i18n_f5d14ee3f8":"Disk occupancy", + "i18n_f5f65044ea":"The server level installed by the container cannot use local builds (because local builds depend on the local environment of the startup server level, container installation is not easy to manage local dependent plugins)", + "i18n_f63345630c":"#cache node_modules files in a container to a docker volume", + "i18n_f63870fdb0":"Please fill in the container name", + "i18n_f652d8cca7":"Try auto-renewal...", + "i18n_f66335b5bf":"Error message:", + "i18n_f66847edb4":"Web App ID", + "i18n_f668c8c881":"Cluster name:", + "i18n_f685377a22":"script library ", + "i18n_f68f9b1d1b":"last heartbeat time", + "i18n_f6d6ab219d":"The time when the update did succeed after it was completed", + "i18n_f6d96c1c8c":"For compatibility with Quartz expressions, both 6-bit and 7-bit expressions are supported, including:", + "i18n_f6dee0f3ff":"distribution ID", + "i18n_f712d3d040":"Remarks Example:", + "i18n_f71316d0dd":"substitution reference", + "i18n_f71a30c1b9":"Data directory footprint", + "i18n_f7596f3159":"If you need to switch the build command in advance in other workspaces", + "i18n_f76540a92e":"In preparation", + "i18n_f782779e8b":"end time", + "i18n_f7b9764f0f":"The corresponding address will be requested for project start, stop, and restart.", + "i18n_f7e8d887d6":"Workspace environment variables", + "i18n_f7f340d946":"Do you really want to clear SSH hidden field information? (password, private key)", + "i18n_f8460626f0":"Node account, please check the information output by the node startup.", + "i18n_f86324a429":"Use ANT expression to filter the specified directory to publish and exclude the specified directory", + "i18n_f89cc4807e":"The authorization path refers to the folder where the project files are stored in the service", + "i18n_f89fa9b6c6":"Select warehouse", + "i18n_f8a613d247":"Please select a node", + "i18n_f8b3165e0d":"The current project is disabled", + "i18n_f8f20c1d1e":"Trim objects created before this timestamp, for example: 24h", + "i18n_f8f456eb9a":"Type Project-specific types: reload, restart", + "i18n_f932eff53e":"strip data", + "i18n_f9361945f3":"hostname", + "i18n_f967131d9d":"Warehouse name", + "i18n_f976e8fcf4":"Monitor name", + "i18n_f97a4d2591":"Please select which cluster you want to join", + "i18n_f9898595a0":"Note: It is not recommended that the same group be bound to multiple clusters", + "i18n_f98994f7ec":"publishing method", + "i18n_f99ead0a76":"The image name is incorrect and cannot be updated", + "i18n_f9ac4b2aa6":"operator", + "i18n_f9c9f95929":"Java project (java -classpath)", + "i18n_f9cea44f02":"There is currently no Docker in the workspace.", + "i18n_f9f061773e":"If you don't fill it in, use the node to distribute the configured secondary directory", + "i18n_fa2f7a8927":"failure strategy", + "i18n_fa4aa1b93b":"Run project", + "i18n_fa57a7afad":"Container tags, such as: xxxx: latest multiple separated by commas, configure additional environment variables files to support loading .env files in the warehouse directory environment variables such as: xxxx: {'${VERSION}'}", + "i18n_fa624c8420":"After disabling, the user cannot log in to the platform.", + "i18n_fa7f6fccfd":"Project name:", + "i18n_fa7ffa2d21":"Unlock", + "i18n_fa8e673c50":"Edit Workspace", + "i18n_faa1ad5e5c":"agreement", + "i18n_faaa995a8b":"Can be closed", + "i18n_faaadc447b":"serial number", + "i18n_fabc07a4f1":"Please select a monitoring operation", + "i18n_fad1b9fb87":"The new script template needs to be added to the node management.", + "i18n_fb1f3b5125":"Current Workspace Linked Data Statistics", + "i18n_fb3a2241bb":"Status description:", + "i18n_fb5bc565f3":"Failed to parse file:", + "i18n_fb61d4d708":"Do you really want to roll back the build history?", + "i18n_fb7b9876a6":"Please enter a script name", + "i18n_fb852fc6cc":"in progress", + "i18n_fb8fb9cc46":"statistical description", + "i18n_fb91527ce5":"Node availability:", + "i18n_fb9d826b2f":"The command to execute after publishing (non-blocking command), usually the command to start the project, such as: ps -aux | grep java", + "i18n_fba5f4f19a":"DSL environment variables", + "i18n_fbd7ba1d9b":"Last distribution time", + "i18n_fbee13a873":"Total number of workspaces:", + "i18n_fbfa6c18bf":"Allocated", + "i18n_fbfeb76b33":"Left menu bar theme switch", + "i18n_fc06c70960":"Are you sure you want to delete the current image?", + "i18n_fc4e2c6151":"logged in user", + "i18n_fc5fb962da":"Email password or authorization code", + "i18n_fc92e93523":"Effective time", + "i18n_fc954d25ec":"proxy", + "i18n_fcaef5b17a":"Reuse another container network stack", + "i18n_fcb4c2610a":"Notification exception", + "i18n_fcb7a47b70":"Alibaba Cloud Email", + "i18n_fcba60e773":"build", + "i18n_fcbf0d0a55":"You need to install the dependent yarn & & yarn run build first.", + "i18n_fcca8452fe":"The cluster address is mainly used to switch the workspace and automatically jump to the corresponding cluster.", + "i18n_fcef976c7a":"private key content", + "i18n_fd6e80f1e0":"normal", + "i18n_fd7b461411":"Do not empty", + "i18n_fd7e0c997d":"Select file", + "i18n_fd93f7f3d7":"Scripts can be distributed to machine nodes and referenced in DSL projects to the point where multiple projects share the same script", + "i18n_fda92d22d9":"The associated node will automatically identify whether there is a java environment in the server, if there is no Java environment can not quickly install the node", + "i18n_fdba50ca2d":"If the port is exposed to the public network", + "i18n_fdbac93380":"SMTP address: smtp.mxhichina.com, port 465 and open SSL, the username needs to be the same as the email sender, and the password is the login password of the email.", + "i18n_fdbc77bd19":"safety", + "i18n_fdcadf68a5":"SMTP port", + "i18n_fde1b6fb37":"You need to configure the authorization directory for the machine in advance.", + "i18n_fdfd501269":"Java SDK image use: https://mirrors.tuna.tsinghua.edu.cn/supported versions are: 8, 9, 10, 11, 12, 13, 14, 15, 16, 17", + "i18n_fe1b192913":"After the directory is successfully created, you need to manually refresh the tree on the right to display it.", + "i18n_fe231ff92f":"Close page operation guidance and navigation", + "i18n_fe2df04a16":"version", + "i18n_fe32def462":"active", + "i18n_fe7509e0ed":"value", + "i18n_fe828cefd9":"The project folder is the name of the directory where the project is actually stored", + "i18n_fe87269484":"cluster modification time", + "i18n_fea996d31e":"Please fill in the build name", + "i18n_fec6151b49":"Account name", + "i18n_feda0df7ef":"Account email", + "i18n_ff17b9f9cd":"WeCom", + "i18n_ff1fda9e47":"prohibit", + "i18n_ff39c45fbc":"Use the host network stack within the container. Note: Host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.", + "i18n_ff3bdecc5e":"File view (if the account password is customized, this file will not be available)", + "i18n_ff80d2671c":"Refresh in seconds", + "i18n_ff9814bf6b":"trigger type", + "i18n_ff9dffec4d":"search mode", + "i18n_ffa9fd37b5":"Workspace management", + "i18n_ffaf95f0ef":"Startup container, you can see many devices on the host, you can perform mount. You can start the docker container in the docker container.", + "i18n_ffd67549cf":": Range: 1~ 12, also supports case-insensitive aliases: _ ##_jan \",\" feb \",\" mar \",\" apr \",\" may \",\" jun \",\" jul \",\" aug \",\" sep \",\" oct \",\" nov \",\" dec \"", + "i18n_fffd3ce745":"share" +} \ No newline at end of file diff --git a/web-vue/src/i18n/locales/zh_cn.json b/web-vue/src/i18n/locales/zh_cn.json new file mode 100644 index 0000000000..92d9471162 --- /dev/null +++ b/web-vue/src/i18n/locales/zh_cn.json @@ -0,0 +1,2807 @@ +{ + "i18n_0006600738": "加入 Docker 集群", + "i18n_005de9a4eb": "构建历史是用于记录每次构建的信息,可以保留构建产物信息,构建日志。同时还可以快速回滚发布", + "i18n_0079d91f95": "确定要将此数据置顶吗?", + "i18n_007f23e18f": "关闭 TLS 认证", + "i18n_00a070c696": "点击可以复制", + "i18n_00b04e1bf0": "发送包", + "i18n_00d5bdf1c3": "调用次数", + "i18n_00de0ae1da": "文件上传前需要执行的脚本(非阻塞命令)", + "i18n_01081f7817": "请输入允许编辑文件的后缀及文件编码,不设置编码则默认取系统编码,多个使用换行。示例:设置编码:txt{'@'}utf-8, 不设置编码:txt", + "i18n_010865ca50": "真的要停止项目么?", + "i18n_0113fc41fc": "全屏日志", + "i18n_01198a1673": "上传小文件", + "i18n_01226f48fc": "对于每一个子表达式,同样支持以下形式:", + "i18n_0128cdaaa3": "分配类型", + "i18n_01ad26f4a9": "重置触发器 token 信息,重置后之前的触发器 token 将失效", + "i18n_01b4e06f39": "重启", + "i18n_01e94436d1": "原密码", + "i18n_020d17aac6": "发送大小", + "i18n_020f1ecd62": "开始上传", + "i18n_020f31f535": "路径需要配置绝对路径,不支持软链", + "i18n_0215b91d97": "构建序号id需要跟进实际情况替换", + "i18n_0221d43e46": "远程下载Url不为空", + "i18n_0227161b3e": "执行方式", + "i18n_022b6ea624": "您确定要删除当前卷吗?", + "i18n_0253279fb8": "克隆深度", + "i18n_02d46f7e6f": "真的要删除这些构建历史记录么?", + "i18n_02d9819dda": "提示", + "i18n_02db59c146": "禁止访问的 IP 地址", + "i18n_02e35447d4": "下载构建产物,如果按钮不可用表示产物文件不存在,一般是构建没有产生对应的文件或者构建历史相关文件被删除", + "i18n_0306ea1908": "删除镜像", + "i18n_031020489f": "当前工作空间您触发的构建记录", + "i18n_03580275cb": "请选中要重启的项目", + "i18n_0360fffb40": "并开启此开关", + "i18n_036c0dc2aa": "系统取消分发", + "i18n_0373ba5502": "需要您在需要被管理的服务器中安装 agent ,并将 agent 信息新增到系统中", + "i18n_03816381ec": "切换视图", + "i18n_0390e2f548": "参数{count}描述", + "i18n_03a74a9a8a": "日志路径", + "i18n_03c1f7c142": "请填选择构建的仓库", + "i18n_03d9de2834": "项目运维", + "i18n_03dcdf92f5": "隐私变量", + "i18n_03e59bb33c": "紧凑", + "i18n_03f38597a6": "速度", + "i18n_0428b36ab1": "副本", + "i18n_04412d2a22": "操作不能撤回奥", + "i18n_044b38221e": "Java 项目(示例参考,具体还需要根据项目实际情况来决定)", + "i18n_045cd62da3": "型号:", + "i18n_045f89697e": "压缩包进行发布", + "i18n_047109def4": "待处理", + "i18n_04a8742dd7": "插件运行时间", + "i18n_04edc35414": "模板节点", + "i18n_051fa113dd": "方式连接 docker 是通过终端实现,每次操作 docker 相关 api 需要登录一次终端", + "i18n_05510a85b0": "系统中您所有操作日志", + "i18n_059ac641c0": "特权:", + "i18n_05b52ae2db": "{slot1} 用于容器构建选择容器功能(fromTag)", + "i18n_05cfc9af9d": "接收错误", + "i18n_05e6d88e29": "分发节点是指在编辑完脚本后自动将脚本内容同步节点的脚本,一般用户节点分发功能中的 DSL 模式", + "i18n_05e78c26b1": "单个触发器地址中:第一个随机字符串为命令脚本ID,第二个随机字符串为 token", + "i18n_05f6e923af": "执行错误", + "i18n_0647b5fc26": "先停止", + "i18n_066431a665": "请输入证书描述", + "i18n_066f903d75": "操后上移或者下移可能不会达到预期排序", + "i18n_067638bede": "CPU数", + "i18n_067eb0fa04": "如果这里的报警联系人无法选择,说明这里面的管理员没有设置邮箱,在右上角下拉菜单里面的用户资料里可以设置。", + "i18n_0693e17fc1": "有新内容后是否自动滚动到底部", + "i18n_06986031a7": "需要到原始工作空间中去控制节点分发", + "i18n_06e2f88f42": "请输入名称", + "i18n_0703877167": "关闭MFA", + "i18n_0719aa2bb0": "重置密码", + "i18n_0728fee230": "请输入公告标题", + "i18n_072fa90836": "压缩 ", + "i18n_0739b9551d": "端口协议", + "i18n_07683555af": "当前版本号:", + "i18n_0793aa7ba3": "maven sdk 镜像使用:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_07a03567aa": "虚拟内存占用", + "i18n_07a0e44145": "主机名:", + "i18n_07a828310b": "并行度", + "i18n_07a8af8c03": "为当前项目实际的进程ID", + "i18n_07b6bb5e40": "严格执行脚本(构建命令、事件脚本、本地发布脚本、容器构建命令)执行返回状态码必须是 0、否则将构建状态标记为失败", + "i18n_07d2261f82": "默认是当前时间到今年结束", + "i18n_080b914139": "上传包", + "i18n_0836332bf6": "升级协议", + "i18n_083b8a2ec9": "一个物理节点被多个服务端绑定也会产生孤独数据奥", + "i18n_08902526f1": "皮肤:", + "i18n_0895c740a6": "交换内存占用", + "i18n_089a88ecee": "系统时间:", + "i18n_08ab230290": "操作说明", + "i18n_08ac1eace7": "文件上传成功后需要执行的脚本(非阻塞命令)", + "i18n_08b1fa1304": "请输入用户名", + "i18n_08b55fea3c": "管理", + "i18n_0934f7777a": "新标签终端", + "i18n_095e938e2a": "停止", + "i18n_09723d428d": "报警联系人", + "i18n_09d14694e7": "需要 SSH 监控中能获取到 docker 信息", + "i18n_09e7d24952": "实际内存占用率:", + "i18n_0a056b0d5a": "动态文件", + "i18n_0a1d18283e": "构建确认弹窗", + "i18n_0a47f12ef2": "如果孤独数据被工作空间下的其他功能关联,修正后关联的数据将失效对应功能无法查询到关联数据", + "i18n_0a54bd6883": "Gmail 邮箱配置", + "i18n_0a60ac8f02": "是", + "i18n_0a63bf5b41": "软内存限制。", + "i18n_0a9634edf2": "地址通配符,* 表示所有地址都将使用代理", + "i18n_0aa60d1169": "您还未登录过", + "i18n_0aa639865c": "真的要删除机器 SSH 么?", + "i18n_0ac4999a4c": "网卡信息", + "i18n_0ac9e3e675": "绑定成功后将不再显示,强烈建议保存此二维码或者下面的 MFA key", + "i18n_0af04cdc22": "支持两种方式填充:", + "i18n_0af5d9f8e8": "当前区域为系统管理、资产管理中心", + "i18n_0b23d2f584": "差异构建", + "i18n_0b2fab7493": "当前 SSH 的授权目录(文件目录、文件后缀、禁止命令)需要请到 【系统管理】-> 【资产管理】-> 【SSH 管理】-> 操作栏中->关联按钮->对应工作空间->操作栏中->配置按钮", + "i18n_0b3edfaf28": "设置内存限制。", + "i18n_0b58866c3e": "断点/分片单文件下载", + "i18n_0b76afbf5d": "允许执行的 CPU(例如,0-3、0", + "i18n_0b9d5ba772": "请尊重开源协议,不要擅自修改版本信息,否则可能承担法律责任。", + "i18n_0baa0e3fc4": "发布中", + "i18n_0bac3db71c": "重启服务端后失效", + "i18n_0bbc7458b4": "回到首页", + "i18n_0bc45241af": "传入参数有:outGivingId、outGivingName、status、statusMsg、executeTime", + "i18n_0bf9f55e9d": "不能关闭", + "i18n_0bfcab4978": "node sdk 镜像使用:https://registry.npmmirror.com/-/binary/node", + "i18n_0c0633c367": "不能删除默认工作空间", + "i18n_0c1de8295a": "独立", + "i18n_0c1e9a72b7": "将使用微队列来排队构建,避免几乎同时触发构建被中断构建(一般用户仓库合并代码会触发多次请求),队列保存在内存中,重启将丢失", + "i18n_0c1f1cd79b": "不自动重启", + "i18n_0c1fec657f": "秒", + "i18n_0c2487d394": "下拉搜索默认搜索关键词相关的前 10 个,以及已经选择的机器节点", + "i18n_0c256f73b8": "容器名称:", + "i18n_0c4eef1b88": "当为6位时,第一位表示", + "i18n_0c5c8d2d11": "基础信息:", + "i18n_0c7369bbee": "开启SSH访问", + "i18n_0cbf83cc07": "联系我们", + "i18n_0ccaa1c8b2": " :表示匹配这个位置所有的时间", + "i18n_0ce54ecc25": "付费社群", + "i18n_0cf4f0ba82": "真的要保存当前配置吗?如果配置有误,可能无法启动服务需要手动还原奥!!! 保存成功后请及时关注重启状态!!", + "i18n_0cf81d77bb": "请填写仓库地址", + "i18n_0d44f4903a": "真的要释放(删除)当前项目么?", + "i18n_0d467f7889": "# 是否开启日志备份功能", + "i18n_0d48f8e881": "请输入服务地址", + "i18n_0d50838436": "数据目录", + "i18n_0d98c74797": "其他", + "i18n_0da9b12963": "用户数据", + "i18n_0de68f5626": "登录JPOM", + "i18n_0e052223a4": "重启服务端需要重新获取", + "i18n_0e16902c1e": "查看状态", + "i18n_0e1ecdae4a": "完整顺序执行(有执行失败将结束本次)", + "i18n_0e25ab3b51": "证书的允许的 IP 需要和 docker host 一致", + "i18n_0e44ae17ae": "服务端机器网络", + "i18n_0e502fed63": "重启超时,请去服务器查看控制台日志排查问题", + "i18n_0e55a594fd": "监控项目", + "i18n_0e5f01b9be": "关联工作空间ssh", + "i18n_0ea78e4279": "查看日志", + "i18n_0ec9eaf9c3": "更多", + "i18n_0eccc9451d": "# 备份文件保留个数", + "i18n_0ee3ca5e88": "扫码赞赏支持开源项目长期发展", + "i18n_0ef396cbcc": "分发结果", + "i18n_0f004c4cf7": "第三方登录", + "i18n_0f0a5f6107": "正常连接", + "i18n_0f189dbaa4": "没有任何用户", + "i18n_0f4f503547": "请输入版本", + "i18n_0f539ff117": "真的要批量删除选择的镜像吗?已经被容器使用的镜像无法删除!", + "i18n_0f59fe5338": "防火墙端口", + "i18n_0f5fc9f300": "文件管理中心", + "i18n_0f8403d07e": "刷新倒计时", + "i18n_0fca8940a8": "没有节点", + "i18n_0ff425e276": "文件ID", + "i18n_1012e09849": "处理失败", + "i18n_10145884ba": "文件后N行", + "i18n_1014b33d22": "分组名称", + "i18n_101a86bc84": "请输入...", + "i18n_1022c545d1": "插件端启动时自动检查项目如未启动将尝试启动", + "i18n_102dbe1e39": "注意:环境变量存在作用域:当前工作空间或者全局,不能跨工作空间引用", + "i18n_102e8ec6d5": "网络流量信息", + "i18n_1058a0be42": "开启 TLS 认证,证书信息:", + "i18n_1062619d5a": "节点账号密码默认由系统生成:可以通过插件端数据目录下 agent", + "i18n_108d492247": "正则语法参考", + "i18n_10c385b47e": "一键分发同步多个节点的系统配置", + "i18n_10d6dfd112": "显示后N行", + "i18n_10f6fc171a": "SSH 名称", + "i18n_111e786daa": "填写备注仅本次构建生效", + "i18n_1125c4a50b": "真的要删除分发信息么?删除后节点下面的项目也都将删除", + "i18n_113576ce91": "产物目录:", + "i18n_1149274cbd": "用户总数", + "i18n_115cd58b5d": "】备份文件夹么?", + "i18n_1160ab56fd": "构建命令:", + "i18n_116d22f2ab": "项目ID:", + "i18n_11724cd00b": "集群创建时间", + "i18n_117a9cbc8d": "语言:", + "i18n_11957d12e4": "报警中", + "i18n_11e88c95ee": " 查找上一个", + "i18n_121e76bb63": "请选择构建对应的分支", + "i18n_1235b052ff": "节点地址 (192.168.1.100:2123)", + "i18n_1278df0cfc": "关联节点如果服务器存在 java 环境,但是插件端未运行则会显示快速安装按钮", + "i18n_127de26370": "SMTP 地址:【smtp.qq.com】,用户名一般是QQ号码,密码是邮箱授权码,端口默认 587/465", + "i18n_12934d1828": "日志目录是指控制台日志存储目录", + "i18n_12afa77947": "开启缓存构建目录将保留仓库文件,二次构建将 pull 代码, 不开启缓存目录每次构建都将重新拉取仓库代码(较大的项目不建议关闭缓存)", + "i18n_12d2c0aead": "请将此密码复制告知该用户", + "i18n_12dc402a82": "参考数据", + "i18n_130318a2a1": "路由无效,无法跳转", + "i18n_1303e638b5": "修改时间", + "i18n_13627c5c46": "配置ssh", + "i18n_138776a1dc": "默认是在插件端数据目录/{'${projectId}'}/{'${projectId}'}.log", + "i18n_138a676635": "注意", + "i18n_13c76c38b7": "# scriptId 可以引用脚本库中的脚本(G{'@'}xxx)其中 xxx 为脚本库中的脚本标记,前提需要提取将对应脚本同步至对应机器节点", + "i18n_13d10a9b78": "没有资产SSH", + "i18n_13d947ea19": "需要您先新增资产机器再分配机器节点(逻辑节点)到当前工作空间", + "i18n_13f7bb78ef": "默认统计机器中除本地接口(环回或无硬件地址)网卡流量总和", + "i18n_13f931c5d9": "查看任务", + "i18n_1432c7fcdb": "系统公告", + "i18n_143bfbc3a1": "点击重新同步当前工作空间逻辑节点项目信息", + "i18n_143d8d3de5": "否则将删除满足条件的所有数据", + "i18n_148484b985": "实现您需要配置 docker 容器到服务端中来管理,并且分配到当前工作空间中", + "i18n_1498557b2d": "同时只能展开一个菜单", + "i18n_14a25beebb": "10秒一次", + "i18n_14d342362f": "标签", + "i18n_14dcfcc4fa": "还未执行reload", + "i18n_14dd5937e4": "附加环境变量 .env 新增多个使用逗号分隔", + "i18n_14e6d83ff5": "时间:", + "i18n_14ee5b5dc5": "命令文件将在 {'${插件端数据目录}'}/script/xxxx.sh 、bat 执行", + "i18n_14feaa5b3a": "刷新倒计时 ", + "i18n_1535fcfa4c": "发送", + "i18n_156af3b3d1": "菜单配置", + "i18n_1593dc4920": "真的要删除该记录么?删除后构建关联的容器标签将无法使用", + "i18n_159a3a8037": "更新镜像", + "i18n_15c0ba2767": "上传项目文件", + "i18n_15c46f7681": "修改接口 HTTP 状态码为 200 并且响应内容为:success 才能确定操作成功反之均可能失败", + "i18n_15d5fffa6a": "响应结果", + "i18n_15e9238b79": "接收", + "i18n_15f01c43e8": "日志备份列表", + "i18n_15fa91e3ab": "天级别", + "i18n_1603b069c2": "周一", + "i18n_1622dc9b6b": "未知", + "i18n_162e219f6d": "丢失", + "i18n_164cf07e1c": "清空覆盖", + "i18n_16646e46b1": "产物文件大小:", + "i18n_16b5e7b472": "直接构建", + "i18n_16f7fa08db": "吗?", + "i18n_17006d4d51": "是否自动跳转到系统页面", + "i18n_170fc8e27c": "周四", + "i18n_174062da44": "分发方式", + "i18n_1775ff0f26": "建议新增指定时间范围", + "i18n_178ad7e9bc": "参数中的 id 、token 和触发构建一致、buildNumId 构建序号id", + "i18n_17a101c23e": "孤独数据是指机器节点里面存在数据,但是无法和当前系统绑定上关系(关系绑定=节点ID+工作空间ID对应才行),一般情况下不会出现这样的数据", + "i18n_17a74824de": "构建方式", + "i18n_17acd250da": "下移", + "i18n_17b4c9c631": "没有任何节点", + "i18n_17b5e684e5": "需要到 节点管理中的【插件端配置】的授权配置中配置允许编辑的文件后缀", + "i18n_17c06f6a8b": "最后执行时间", + "i18n_17d444b642": "运行方式", + "i18n_1810e84971": "才能使用 SSH 方式连接", + "i18n_1818e9c264": "JVM总内存", + "i18n_1819d0cdda": "如果开启同步到文件管理中心,在构建发布流程将自动执行同步到文件管理中心的操作。", + "i18n_181e1ad17d": "长按可以拖动排序", + "i18n_1857e7024c": "系统版本", + "i18n_185926bf98": "全屏", + "i18n_1862c48f72": "本地状态:", + "i18n_1880b85dc5": "黑白 ambiance", + "i18n_18b0ab4dd2": "机器SSH名", + "i18n_18b34cf50d": "不滚动", + "i18n_18c63459a2": "默认", + "i18n_18c7e2556e": "如果当前构建信息已经在其他页面更新过,需要点击刷新按钮来获取最新的信息,点击刷新后未保存的数据也将丢失", + "i18n_18d49918f5": "账号被锁定", + "i18n_18eb76c8a0": "memory 最小 4M", + "i18n_192496786d": "事件脚本", + "i18n_19675b9d36": "清除代码(仓库目录)为删除服务器中存储仓库目录里面的所有东西,删除后下次构建将重新拉起仓库里面的文件,一般用于解决服务器中文件和远程仓库中文件有冲突时候使用。执行时间取决于源码目录大小和文件数量如超时请耐心等待,或稍后重试", + "i18n_1974fe5349": "绑定成功", + "i18n_197be96301": "待完善", + "i18n_19f974ef6a": "开启差异发布并且开启清空发布时将自动删除项目目录下面有的文件但是构建产物目录下面没有的文件【清空发布差异上传前会先执行删除差异文件再执行上传差异文件】", + "i18n_19fa0be4d2": " 官方文档", + "i18n_19fcb9eb25": "时间", + "i18n_1a44b9e2f7": "同步到其他工作空间", + "i18n_1a55f76ace": "构建命令,构建产物相对路径为:", + "i18n_1a56bb2237": "至少选择一个节点和项目", + "i18n_1a6aa24e76": "执行", + "i18n_1a704f73c2": "请选择一个文件", + "i18n_1a8f90122f": "提示信息 ", + "i18n_1abf39bdb6": "# 将此目录缓存到全局(多个构建可以共享此缓存目录)", + "i18n_1ad696efdc": "构建执行的命令(非阻塞命令),如:mvn clean package、npm run build。支持变量:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_SOURCE_FILE}'}、{'${BUILD_NUMBER_ID}'}、仓库目录下 .env、工作空间变量", + "i18n_1ae2955867": "指定 pom 文件打包 mvn -f xxx/pom.xml clean package", + "i18n_1afdb4a364": "隐藏滚动条。纵向滚动方式提醒:滚轮,横行滚动方式:Shift+滚轮", + "i18n_1b03b0c1ff": "已经分配到工作空间的 Docker 或者集群无非直接删除,需要到分配到的各个工作空间逐一删除后才能删除资产 Docker 或者集群", + "i18n_1b38c0bc86": "备份文件存储目录:", + "i18n_1b5266365f": "原始IP", + "i18n_1b5bcdf115": "会话已经关闭[node-system-log]", + "i18n_1b7cba289a": "数据统计", + "i18n_1b8fff7308": "开启 MFA", + "i18n_1b963fd303": "【推荐】腾讯身份验证码", + "i18n_1b973fc4d1": "分组名称:", + "i18n_1ba141c9ac": "请选择软链的项目", + "i18n_1ba584c974": "配置容器", + "i18n_1baae8183c": "是否解压", + "i18n_1c040e6b87": "一般情况下不建议降级操作", + "i18n_1c10461124": "示例:key,key1 或者 key=value,key1=value1", + "i18n_1c13276448": "当前工作空间关联构建", + "i18n_1c2e9d0c76": "没有任何构建", + "i18n_1c3cf7f5f0": "关联", + "i18n_1c61dfb86f": "挂载点", + "i18n_1c8190b0eb": "请填写项目 DSL 配置内容,可以点击上方切换 tab 查看配置示例", + "i18n_1c83d79715": "执行失败", + "i18n_1c9d3cb687": "用户名ID", + "i18n_1cc82866a4": "分片操作数", + "i18n_1d0269cb77": "已经分配到工作空间的 SSH 无非直接删除,需要到分配到的各个工作空间逐一删除后才能删除资产 SSH", + "i18n_1d263b7efb": "该选项仅本次构建生效", + "i18n_1d38b2b2bc": "请选择项目授权路径", + "i18n_1d53247d61": "请选择逻辑节点", + "i18n_1d650a60a5": "硬盘", + "i18n_1d843d7b45": "此节点暂无项目", + "i18n_1dc518bddb": "项目存储的文件夹", + "i18n_1dc9514548": "不等同于 PING 测试,此处测试成功表示网络一定通畅,此处测试失败网络不一定不通畅", + "i18n_1de9b781bd": "使用容器构建,docker 容器所在的宿主机需要有公网,因为需要远程下载环境依赖的 sdk 和镜像", + "i18n_1e07b9f9ce": "请选择要同步系统配置的机器节点", + "i18n_1e4a59829d": "插件端开机自启", + "i18n_1e5533c401": "配置目录", + "i18n_1e5ca46c26": "排除发布 ANT 表达式,多个使用逗号分隔", + "i18n_1e88a0cfaf": "不发布到 docker 集群", + "i18n_1e93bdad2a": "搜索项目名", + "i18n_1eb378860a": "真的要 Kill 这个进程么?", + "i18n_1eba2d93fc": "禁用原因", + "i18n_1ece1616bf": "如果插件端正常运行但是连接失败请检查端口是否开放,防火墙规则,云服务器的安全组入站规则", + "i18n_1ed46c4a59": "分发名称(项目名称)", + "i18n_1f08329bc4": "搜索命令名称", + "i18n_1f0c93d776": " :每分钟执行", + "i18n_1f0d13a9ad": "服务端分发同步的脚本不能直接删除,需要到服务端去操作", + "i18n_1f1030554f": "总计 {total} 条", + "i18n_1f130d11d1": "SMTP 服务器", + "i18n_1f4c1042ed": "文件夹", + "i18n_1fa23f4daa": "过期时间", + "i18n_1fd02a90c3": "用户", + "i18n_200707a186": "创建后构建方式不支持修改", + "i18n_2025ad11ee": "真的要解绑节点脚本么?", + "i18n_2027743b8d": "系统名称:", + "i18n_204222d167": "网络延迟", + "i18n_2064fc6808": "不显示", + "i18n_207243d77a": "如果要将工作空间分配给其他用户还需要到权限组管理", + "i18n_207d9580c1": "表示周六", + "i18n_209f2b8e91": "请输入登录密码", + "i18n_20a9290498": "您来到系统管理中心", + "i18n_20c8dc0346": "演示账号", + "i18n_20e0b90021": "真的要删除监控么?", + "i18n_20f32e1979": "角色:", + "i18n_211354a780": "内的root只是外部的一个普通用户权限。默认false", + "i18n_21157cbff8": "毫秒", + "i18n_211a60b1d6": "编辑容器的一些基础参数", + "i18n_2141ffaec9": "状态数据是异步获取有一定时间延迟", + "i18n_2168394b82": "文件id,精准搜索", + "i18n_2171d1b07d": "默认参数", + "i18n_2191afee6e": "升级超时,请去服务器查看控制台日志排查问题", + "i18n_21d81c6726": "为当前工作空间中的容器配置标签", + "i18n_21da885538": "可以使用节点脚本:", + "i18n_21dd8f23b4": "开源协议", + "i18n_21e4f10399": "优先判断禁用时段", + "i18n_21efd88b67": "暂无数据", + "i18n_220650a1f5": "配置后将保存到当前构建", + "i18n_2213206d43": "点击延迟可以查看对应节点网络延迟历史数据", + "i18n_222316382d": "关联节点", + "i18n_2223ff647d": "清空发布", + "i18n_2245cf01a3": "您没有权限访问", + "i18n_2246d128cb": "企业微信通知地址", + "i18n_22482533ff": "私钥内容,不填将使用默认的 $HOME/.ssh 目录中的配置。支持配置文件目录:file:/xxxx/xx", + "i18n_224aef211c": "构建信息", + "i18n_224e2ccda8": "配置", + "i18n_2256690a28": "节点ID:", + "i18n_22670d3682": "请选择要使用的脚本", + "i18n_226a6f9cdd": "请检查是否开启 ws 代理", + "i18n_226b091218": "类型", + "i18n_22b03c024d": "二维码", + "i18n_22c799040a": "容器", + "i18n_22cf31df5d": "当前访问IP:", + "i18n_22e4da4998": "表示项目当前未运行", + "i18n_22e888c2df": "到期时间", + "i18n_2300ad28b8": "读写", + "i18n_2314f99795": "检测到新版本 ", + "i18n_231f655e35": "当前程序打包时间:", + "i18n_23231543a4": "修正", + "i18n_2331a990aa": "扫码转账支持开源项目长期发展", + "i18n_233fb56ab2": "在 设置-->安全设置-->私人令牌 中获取", + "i18n_234e967afe": "发布前执行的命令(非阻塞命令),一般是关闭项目命令,支持变量替换:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_2351006eae": "附加环境变量", + "i18n_23559b6453": "# 将容器中的 maven 仓库文件缓存到 docker 卷中", + "i18n_2356fe4af2": "配合脚本模版实现自定义项目管理", + "i18n_2358e1ef49": "所属工作空间: ", + "i18n_235f0b52a1": "发送错误", + "i18n_23b38c8dad": "会话已经关闭[upgrade]", + "i18n_23b444d24c": "快速配置", + "i18n_23eb0e6024": "昵称", + "i18n_242d641eab": "后缀", + "i18n_2432b57515": "备注", + "i18n_24384ba6c1": "确定要重新同步当前节点项目缓存信息吗?", + "i18n_24384dab27": "请输入 value 的值", + "i18n_244d5a0ed8": "构建参数", + "i18n_2456d2c0f8": "如果容器以非零退出代码退出,则重新启动容器。可以指定次数:on-failure:2", + "i18n_2457513054": "周六", + "i18n_2482a598a3": "插件版本号", + "i18n_248c9aa7aa": "构建状态", + "i18n_2493ff1a29": "自定义进程类型", + "i18n_2499b03cc5": "保留产物:", + "i18n_249aba7632": "天", + "i18n_24ad6f3354": "如果垮机器(资产机器)迁移之前机器中的项目数据仅是逻辑删除(项目文件和日志均会保留)", + "i18n_24cc0de832": "执行命令", + "i18n_24d695c8e2": "集群主机名", + "i18n_250688d7c9": "发布失败", + "i18n_250a999bb2": "容器标签,如:xxxx:latest 多个使用逗号隔开", + "i18n_25182fb439": "工作空间菜单", + "i18n_251a89efa9": "查看当前状态", + "i18n_252706a112": "【推荐】微信小程序搜索 数盾OTP", + "i18n_2527efedcd": "用户信息 url", + "i18n_2560e962cf": "请选择分发项目", + "i18n_257dc29ef7": "搜索配置参考", + "i18n_25b6c22d8a": "为避免显示内容太多而造成浏览器卡顿,读取日志最后多少行日志。修改后需要回车才能重新读取,小于 1 则读取所有", + "i18n_25be899f66": "筛选之后本次发布操作只发布筛选项,并且只对本次操作生效", + "i18n_25c6bd712c": "请输入获取的计划运行次数", + "i18n_25f29ebbe6": "脚本日志数:", + "i18n_25f6a95de3": "确定要取消构建 【名称:", + "i18n_2606b9d0d2": "分发机器", + "i18n_260a3234f2": "请选择SSH", + "i18n_2611dd8703": "当目标工作空间不存在对应的节点时候将自动创建一个新的节点(逻辑节点)", + "i18n_26183c99bf": "文件中心", + "i18n_2646b813e8": "登录密码", + "i18n_267bf4bf76": "分发到节点中需要注意跨工作空间重名将被最后一次同步覆盖", + "i18n_2684c4634d": "版本:", + "i18n_26a3378645": "请选择运行方式", + "i18n_26b5bd4947": "加载中...", + "i18n_26bb841878": "新建", + "i18n_26bd746dc3": "真的要清空项目目录和文件么?", + "i18n_26c1f8d83e": "最后操作人", + "i18n_26ca20b161": "来源", + "i18n_26eccfaad1": "镜像:", + "i18n_26f95520a5": "执行命令包含:", + "i18n_26ffe89a7f": "项目名:", + "i18n_27054fefec": "执行脚本传入参数有:startReady、pull、executeCommand、release、done、stop、success", + "i18n_2770db3a99": "加载项目数据中...", + "i18n_2780a6a3cf": "TLS 认证", + "i18n_27b36afd36": "状态码为 0 的操作大部分为没有操作结果或者异步执行", + "i18n_27ba6eb343": "网关:", + "i18n_27ca568be2": "继续", + "i18n_27d0c8772c": "如果误操作会产生冗余数据!!!", + "i18n_27f105b0c3": "请选择要升级的节点", + "i18n_280379cee4": "保存并关闭", + "i18n_282c8cda1f": "如果上报的节点信息包含多个 IP 地址需要用户确认使用具体的 IP 地址信息", + "i18n_288f0c404c": "清空", + "i18n_28b69f9233": "构建镜像的过程不使用缓存", + "i18n_28b988ce6a": "文件类型", + "i18n_28bf369f34": "发布后的文件名是:文件ID.后缀,并非文件真实名称 (可以使用上传后脚本随意修改)", + "i18n_28c1c35cd9": "主节点不能直接剔除", + "i18n_28e0fcdf93": "还没有容器或者未配置标签不可以使用容器构建奥", + "i18n_28e1c746f7": "ssh 名", + "i18n_28e1eec677": "授权路径", + "i18n_28f6e7a67b": "静态文件", + "i18n_29139c2a1a": "文件名", + "i18n_2926598213": "项目日志", + "i18n_293cafbbd3": "裁剪", + "i18n_2953a9bb97": "您需要创建一个账户用以后续登录管理系统,请牢记超级管理员账号密码", + "i18n_295bb704f5": "语言", + "i18n_29b48a76be": "请选择发布方式", + "i18n_29efa328e5": "未分发", + "i18n_2a049f4f5b": "分发失败", + "i18n_2a0bea27c4": "执行域", + "i18n_2a0c4740f1": "文件", + "i18n_2a1d1da97a": "打包测试环境包 mvn clean package -Dmaven.test.skip=true -Ptest", + "i18n_2a24902516": "集群ID:", + "i18n_2a38b6c0ae": "未升级成功:", + "i18n_2a3b06a91a": "虚拟MAC", + "i18n_2a3e7f5c38": "手动", + "i18n_2a6a516f9d": "填写运行命令", + "i18n_2a813bc3eb": "立即下载", + "i18n_2ad3428664": "请选择发布到集群的服务名", + "i18n_2adbfb41e9": "参数如果传入", + "i18n_2ae22500c7": "禁用时段", + "i18n_2b04210d33": "进程号:", + "i18n_2b0623dab9": "独立容器", + "i18n_2b0aa77353": "您确定要启动当前容器吗?", + "i18n_2b0f199da0": "不执行,也不编译测试用例 mvn clean package -Dmaven.test.skip=true", + "i18n_2b1015e902": "参数描述没有实际作用", + "i18n_2b21998b7b": "确定要关闭两步验证吗?关闭后账号安全性将受到影响,关闭后已经存在的 mfa key 将失效", + "i18n_2b36926bc1": "没有任何构建历史", + "i18n_2b4bb321d7": "内容区域主题切换", + "i18n_2b4cf3d74e": "请选择要使用的构建", + "i18n_2b52fa609c": "发生异常", + "i18n_2b607a562a": "逐行执行", + "i18n_2b6bc0f293": "操作", + "i18n_2b788a077e": "等常用用户名,避免被其他用户有意或者无意操作造成登录失败次数过多从而超级管理员账号被异常锁定", + "i18n_2b94686a65": "# 给容器新增环境变量", + "i18n_2ba4c81587": "请输入邮箱地址", + "i18n_2bb1967887": "请找我们授权,否则会有法律风险。", + "i18n_2be2175cd7": "执行容器 标签", + "i18n_2be75b1044": "全局", + "i18n_2bef5b58ab": "不填写则不更新", + "i18n_2c014aeeee": "打包时间", + "i18n_2c5b0e86e6": "用户密码重置成功", + "i18n_2c635c80ec": "发布操作是指,执行完构建命令后将构建产物目录中的文件用不同的方式发布(上传)到对应的地方", + "i18n_2c74d8485f": "下载完成后需要手动选择更新到节点才能完成节点更新奥", + "i18n_2c8109fa0b": "当前目录: ", + "i18n_2c921271d5": "vue 项目(示例参考,具体还需要根据项目实际情况来决定)", + "i18n_2cdbbdabf1": "构建产物目录,相对仓库的路径,如 java 项目的 target/xxx.jar vue 项目的 dist", + "i18n_2cdcfcee15": "功能丰富 专为两步验证码", + "i18n_2ce44aba57": "日志目录", + "i18n_2d05c9d012": "关键词,支持正则", + "i18n_2d2238d216": "账号新增成功", + "i18n_2d3fd578ce": "确定要取批量消选中的构建吗?注意:取消/停止构建不一定能正常关闭所有关联进程", + "i18n_2d455ce5cd": "下载中", + "i18n_2d58b0e650": "选择构建的标签,不选为最新提交", + "i18n_2d7020be7d": "比如常见的 .env 文件", + "i18n_2d711b09bd": "内容", + "i18n_2d842318fb": "周期", + "i18n_2d94b9cf0e": "Dockerfile 构建方式不支持在这里回滚", + "i18n_2d9569bf45": "参数值,新增默认参数后在手动执行脚本时需要填写参数值", + "i18n_2d9e932510": "新增目录", + "i18n_2de0d491d0": "小时", + "i18n_2e0094d663": "真的要删除该集群信息么?1", + "i18n_2e1f215c5d": "自动创建用户", + "i18n_2e505d23f7": "下载导入模板", + "i18n_2e51ca19eb": "如果节点选项是禁用,则表示对应数据有推荐关联节点(低版本项目数据可能出现此情况)", + "i18n_2e740698cf": "集群IP", + "i18n_2ea7e70e87": "命令文件将上传至 {'${user.home}'}/.jpom/xxxx.sh 执行完成将自动删除", + "i18n_2ef1c35be8": "执行的 CPU", + "i18n_2f4aaddde3": "删除", + "i18n_2f5e828ecd": "别名码", + "i18n_2f5e885bc6": "获取单个构建日志地址", + "i18n_2f67a19f9d": "需要选发布到集群中的对应的服务名,需要提前去集群中创建服务", + "i18n_2f6989595f": "管理列表:", + "i18n_2f8d6f1584": "昨天", + "i18n_2f8dc4fb66": "真的要释放分发信息么?释放之后节点下面的项目信息还会保留,如需删除项目还需要到节点管理中操作", + "i18n_2f8fd34058": "脚本模版是存储在服务端中的命令脚本用于在线管理一些脚本命令,如初始化软件环境、管理应用程序等", + "i18n_2f97ed65db": "占用", + "i18n_2fc0d53656": "机器状态(缓存)", + "i18n_2ff65378a4": "真的要删除对应工作空间的 SSH 么?", + "i18n_2fff079bc7": "发布成功", + "i18n_3006a3da65": "系统版本:", + "i18n_300fbf3891": "发布前停止是指在发布文件到项目文件时先将项目关闭,再进行文件替换。避免 windows 环境下出现文件被占用的情况", + "i18n_302ff00ddb": "超级管理员", + "i18n_3032257aa3": "详情信息", + "i18n_30849b2e10": "进程/端口", + "i18n_30aaa13963": "序列号 (SN)", + "i18n_30acd20d6e": "用户ID", + "i18n_30d9d4f5c9": "新增关联", + "i18n_30e6f71a18": "自定义标签通配表达式", + "i18n_30e855a053": "取消分发", + "i18n_30ff009ab3": "# java 镜像源 https://mirrors.tuna.tsinghua.edu.cn/Adoptium/", + "i18n_3103effdfd": "请输入账号名", + "i18n_31070fd376": "手动回滚", + "i18n_310c809904": "绑定到当前工作空间", + "i18n_312e044529": " :范围:0(Sunday)~6(Saturday),7也可以表示周日,同时支持不区分大小写的别名:_##_sun\",\"mon\", \"tue\", \"wed\",\"thu\",\"fri\", \"sat\",", + "i18n_312f45014a": "创建时间:", + "i18n_314f5aca4e": "单个触发器地址中:第一个随机字符串为构建ID,第二个随机字符串为 token", + "i18n_315eacd193": "上移", + "i18n_31691a647c": "{slot1}端口", + "i18n_3174d1022d": "容器构建注意", + "i18n_3181790b4b": "服务端系统配置", + "i18n_318ce9ea8b": "用户密码提示", + "i18n_31aaaaa6ec": "构建ID:", + "i18n_31ac8d3a5d": "线程同步器", + "i18n_31bca0fc93": "加入 beta 计划可以及时获取到最新的功能、一些优化功能、最快修复 bug 的版本,但是 beta版也可能在部分新功能上存在不稳定的情况。您需要根据您业务情况来评估是否可以加入 beta,在使用 beta版过程中遇到问题可以随时反馈给我们,我们会尽快为您解答。", + "i18n_31eb055c9c": "并行度,同一时间升级的容器数量", + "i18n_31ecc0e65b": "项目", + "i18n_32112950da": "批量取消", + "i18n_3241c7c05f": "建议使用服务端脚本分发到脚本:", + "i18n_32493aeef9": "构建中", + "i18n_329e2e0b2e": "指定目录打包 yarn && yarn --cwd xxx build", + "i18n_32a19ce88b": "控制台日志路径", + "i18n_32ac152be1": "更新", + "i18n_32c65d8d74": "标题", + "i18n_32cb0ec70e": "请输入节点名称", + "i18n_32d0576d85": "的令牌", + "i18n_32dcc6f36e": "重启策略:no、always、unless-stopped、on-failure", + "i18n_32e05f01f4": "集群信息", + "i18n_32f882ae24": "匹配零个或多个字符", + "i18n_330363dfc5": "成功", + "i18n_3306c2a7c7": "读取默认", + "i18n_33130f5c46": "操作成功", + "i18n_3322338140": "请选择发布后操作", + "i18n_332ba869d9": "一般用于节点环境一致的情况", + "i18n_334a1b5206": "安装节点", + "i18n_335258331a": "已经读取默认配置文件到编辑器中", + "i18n_33675a9bb3": "集群关联的 docker 信息丢失,不能继续使用管理功能", + "i18n_339097ba2e": "准备分发", + "i18n_33c9e2388e": "项目ID", + "i18n_3402926291": "当前日志文件大小:", + "i18n_346008472d": "匹配包含 异常 的行", + "i18n_3477228591": "镜像", + "i18n_35134b6f94": "查看节点脚本", + "i18n_3517aa30c2": "脚本里面支持的变量有:{'${PROJECT_ID}'}、{'${PROJECT_NAME}'}、{'${PROJECT_PATH}'}", + "i18n_353707f491": "可以到【节点分发】=>【分发授权配置】修改", + "i18n_353c7f29da": "请选择模版节点", + "i18n_35488f5ba8": "请选择节点项目", + "i18n_354a3dcdbd": "30秒一次", + "i18n_3574d38d3e": "剩余内存:", + "i18n_35b89dbc59": "确认要下载最新版本吗?", + "i18n_35cb4b85a9": "【目前只使用匹配到的第一项】", + "i18n_35fbad84cb": "描述根据创建时间升序第一个", + "i18n_3604566503": "请填写容器地址", + "i18n_364bea440e": "请选择要引用的脚本", + "i18n_368ffad051": "{slot1}目录", + "i18n_36b3f3a2f6": "报警标题", + "i18n_36b5d427e4": "请输入工作空间描述", + "i18n_36d00eaa3f": "差异构建:", + "i18n_36d4046bd6": "引用脚本模板", + "i18n_36df970248": "# version 需要在对应镜像源中存在", + "i18n_3711cbf638": "预占资源", + "i18n_37189681ad": "数据Id", + "i18n_373a1efdc0": "请选中要关闭的项目", + "i18n_374cd1f7b7": "创建集群", + "i18n_375118fad1": "物理节点脚本模板数据:", + "i18n_375f853ad6": "硬件信息", + "i18n_3787283bf4": "真的要删除当前文件么?", + "i18n_37b30fc862": "请选择皮肤", + "i18n_37c1eb9b23": "配置文件路径", + "i18n_37f031338a": "上传压缩包并自动解压", + "i18n_37f1931729": "数据目录占用空间:", + "i18n_384f337da1": "同步机制采用", + "i18n_3867e350eb": "环境变量", + "i18n_386edb98a5": "自定义脚本项目(python、nodejs、go、接口探活、es)【推荐】", + "i18n_38a12e7196": "选择证书文件", + "i18n_38aa9dc2a0": "更多配置", + "i18n_38ce27d846": "下一步", + "i18n_38cf16f220": "确定", + "i18n_38da533413": "下面命令将在", + "i18n_3904bfe0db": "设置一个超级管理员账号", + "i18n_3929e500e0": "通常情况为项目迁移工作空间、迁移物理机器等一些操作可能产生孤独数据", + "i18n_396b7d3f91": "文件大小", + "i18n_398ce396cd": "工作空间同步", + "i18n_39b68185f0": "节点地址为插件端的 IP:PORT 插件端端口默认为:2123", + "i18n_39c7644388": "端口号", + "i18n_39e4138e30": "集群创建时间:", + "i18n_39f1374d36": "耗时", + "i18n_3a1052ccfc": "引用环境变量", + "i18n_3a17b7352e": "分钟", + "i18n_3a3778f20c": "任务ID", + "i18n_3a3c5e739b": "批量构建参数", + "i18n_3a3ff2c936": "卷标签", + "i18n_3a536dcd7c": "126邮箱", + "i18n_3a57a51660": "脚本版本:{item}", + "i18n_3a6000f345": "正在运行的线程同步器", + "i18n_3a6970ac26": "文件共享", + "i18n_3a6bc88ce0": "真的要删除文件么?", + "i18n_3a6c2962e1": "密钥算法", + "i18n_3a71e860a7": "未开启当前终端", + "i18n_3a94281b91": "自由脚本是指直接在机器节点中执行任意脚本", + "i18n_3aa69a563b": "节点分发是指,一个项目运行需要在多个节点(服务器)中运行,使用节点分发来统一管理这个项目(可以实现分布式项目管理功能)", + "i18n_3ac34faf6d": "通配符", + "i18n_3adb55fbb5": "迁移工作空间", + "i18n_3ae4c953fe": "当定时任务运行到的时间匹配这些表达式后,任务被启动。", + "i18n_3ae4ddf245": "真的要删除该 Docker 么?删除只会检查本地系统的数据关联,不会删除 docker 容器中数据", + "i18n_3aed2c11e9": "自动", + "i18n_3b14c524f6": "读取次数", + "i18n_3b19b2a75c": "真的要删除脚本么?", + "i18n_3b885fca15": "缓存版本号", + "i18n_3b9418269c": "请填写关联容器标签", + "i18n_3b94c70734": "项目状态", + "i18n_3ba621d736": "处理成功", + "i18n_3baa9f3d72": "批量构建参数还支持指定参数,delay(延迟执行构建,单位秒)branchName(分支名)、branchTagName(标签)、script(构建脚本)、resultDirFile(构建产物)、webhook(通知webhook)", + "i18n_3bc5e602b2": "邮箱", + "i18n_3bcc1c7a20": "最后修改人", + "i18n_3bdab2c607": "10分钟", + "i18n_3bdd08adab": "描述", + "i18n_3bf3c0a8d6": "节点", + "i18n_3bf9c5b8af": " 分组名:", + "i18n_3c014532b1": "构建耗时:", + "i18n_3c070ea334": "如果关联的构建关联的仓库被多个构建绑定(使用)不能迁移", + "i18n_3c48d9b970": "批量构建参数 BODY json: [ { \"id\":\"1\", \"token\":\"a\" } ]", + "i18n_3c586b2cc0": "自定义 host", + "i18n_3c6248b364": "缓存信息", + "i18n_3c6fa6f667": "cron表达式", + "i18n_3c8eada338": "请选择编码方式", + "i18n_3c91490844": "发布操作", + "i18n_3c99ea4ec2": "例如 2,3,6/3中,由于“/”优先级高,因此相当于2,3,(6/3),结果与 2,3,6等价", + "i18n_3c9eeee356": "真的要删除日志文件么?", + "i18n_3cc09369ad": "真的要删除【", + "i18n_3d06693eb5": "资源:", + "i18n_3d0a2df9ec": "参数", + "i18n_3d3b918f49": "执行构建", + "i18n_3d3d3ed34c": "请输入选择关联分组", + "i18n_3d43ff1199": "置顶", + "i18n_3d48c9da09": "授权配置", + "i18n_3d61e4aaf1": "指定标签", + "i18n_3d6acaa5ca": "这个容器没有网络", + "i18n_3d83a07747": "主机 Host", + "i18n_3dc5185d81": "私有", + "i18n_3dd6c10ffd": "上传升级包", + "i18n_3e445d03aa": "文件不存在啦", + "i18n_3e51d1bc9c": "请选择发布的SSH", + "i18n_3e54c81ca2": "接收流量", + "i18n_3e7ef69c98": "监控操作", + "i18n_3e8c9c54ee": "选择分组", + "i18n_3ea6c5e8ec": "分发结束", + "i18n_3eab0eb8a9": "本地脚本", + "i18n_3ed3733078": "终端日志", + "i18n_3edddd85ac": "日", + "i18n_3ee7756087": "请先选择节点", + "i18n_3f016aa454": "镜像标签:", + "i18n_3f18d14961": "两步验证码", + "i18n_3f1d478da4": "服务端脚本、SSH脚本可以使用 G{'@'}(\"xxxx\") 格式来引用,当存在引用时系统会自动替换引用脚本库中的脚本内容", + "i18n_3f2d5bd6cc": "在文件第 2 - 2 行中搜索", + "i18n_3f414ade96": "参数描述,{slot1},仅是用于提示参数的含义", + "i18n_3f553922ae": "】目录和文件么?", + "i18n_3f5af13b4b": "# scriptId 可以是项目路径下脚本文件名或者系统中的脚本模版ID", + "i18n_3f719b3e32": "冲突数", + "i18n_3f78f88499": "打包时间:", + "i18n_3f8b64991f": "解压时候自动剔除压缩包里面多余的文件夹名", + "i18n_3f8cedd1d7": "用于静态文件绑定和读取(不建议配置大目录,避免扫描消耗过多资源)", + "i18n_3fb2e5ec7b": "登录日志", + "i18n_3fb63afb4e": "退出码", + "i18n_3fbdde139c": "确认密码", + "i18n_3fca26a684": "批量触发参数 BODY json: [ { \"id\":\"1\", \"token\":\"a\" } ]", + "i18n_3fea7ca76c": "状态", + "i18n_402d19e50f": "登录", + "i18n_40349f5514": "数:", + "i18n_4055a1ee9c": "通用的字段有:createTimeMillis、modifyTimeMillis", + "i18n_406a2b3538": "何为孤独数据", + "i18n_4089cfb557": "关联分组主要用于资产监控来实现不同服务端执行不同分组下面的资产监控", + "i18n_40aff14380": "镜像ID", + "i18n_40da3fb58b": "新建状态", + "i18n_40f8c95345": "临时文件目录", + "i18n_411672c954": "请输入文件描述", + "i18n_412504968d": "当目标工作空间不存在对应的 SSH 时候将自动创建一个新的 SSH", + "i18n_41298f56a3": "构建失败", + "i18n_413d8ba722": "旧版程序包占有空间:", + "i18n_413f20d47f": "系统 采用 oshi 库来监控系统,在 oshi 中使用 /proc/meminfo 来获取内存使用情况。", + "i18n_41638b0a48": "用于区别文件是否为同一类型,可以针对同类型进行下载管理", + "i18n_417fa2c2be": "参数{index}描述", + "i18n_4188f4101c": "没有docker", + "i18n_41d0ecbabd": "Block IO 权重", + "i18n_41e8e8b993": "深色", + "i18n_41e9f0c9c6": "工作节点", + "i18n_41fdb0c862": "请先上传或者下载新版本", + "i18n_4244830033": "请选择证书文件", + "i18n_424a2ad8f7": "准备", + "i18n_429b8dfb98": "项目分发", + "i18n_42a93314b4": "基础镜像", + "i18n_42b6bd1b2f": "仓库路径", + "i18n_42f766b273": "挂载分区", + "i18n_42fd64c157": "先启动", + "i18n_4310e9ed7d": "请选择项目运行方式", + "i18n_43250dc692": "触发器管理", + "i18n_434d888f6f": "请选择文件中心的文件", + "i18n_434d9bd852": "创建用户后自动关联上对应的权限组", + "i18n_4360e5056b": "加载数据中", + "i18n_436367b066": "项目管理", + "i18n_4371e2b426": "请输入项目名称", + "i18n_43886d7ac3": "新增运行参数", + "i18n_4393b5e25b": "环回", + "i18n_43c61e76e7": "注意:目前对 SSH key 访问 git 仓库地址不支持使用 ssh-keygen -t rsa -C", + "i18n_43d229617a": "待选择", + "i18n_43e534acf9": "宽松", + "i18n_43ebf364ed": "请选择备份类型", + "i18n_4403fca0c0": "清除", + "i18n_44473c1406": "开启缓存构建目录将保留仓库文件,二次构建将 pull 代码, 不开启缓存目录每次构建都将重新拉取仓库代码(较大的项目不建议关闭缓存) 、特别说明如果缓存目录中缺失版本控制相关文件将自动删除后重新拉取代码", + "i18n_4482773688": "请输入权限组名称", + "i18n_44876fc0e7": "如果不可以选择则表示对应的用户没有配置邮箱", + "i18n_449fa9722b": "为了考虑系统安全我们强烈建议超级管理员开启两步验证来确保账号的安全性", + "i18n_44a6891817": "新增构建", + "i18n_44c4aaa1d9": "运行模式", + "i18n_44d13f7017": "限定时间", + "i18n_44ed625b19": "网络异常", + "i18n_44ef546ded": "项目监控 【暂不支持迁移】", + "i18n_44efd179aa": "退出登录", + "i18n_45028ad61d": "证书密码", + "i18n_4524ed750d": "工作空间名", + "i18n_456d29ef8b": "日志", + "i18n_458331a965": "确认要上传文件更新到最新版本吗?", + "i18n_45a4922d3f": "关联数据", + "i18n_45b88fc569": "匹配路径中的零个或多个目录", + "i18n_45f8d5a21d": "真的要删除用户么?", + "i18n_45fbb7e96a": "项目孤独数据", + "i18n_46032a715e": "还没有选择构建方式", + "i18n_4604d50234": "错误信息", + "i18n_46097a1225": "修正孤独数据", + "i18n_46158d0d6e": "禁用监控", + "i18n_461e675921": "当前数据为默认状态,操后上移或者下移可能不会达到预期排序,还需要对相关数据都操作后才能达到预期排序", + "i18n_461ec75a5a": "路径:", + "i18n_461fdd1576": "打包生产环境包 mvn clean package -Dmaven.test.skip=true -Pprod", + "i18n_4637765b0a": "未启用", + "i18n_463e2bed82": "批量更新", + "i18n_4642113bba": "点击仪表盘查看监控历史数据", + "i18n_4645575b77": "工作空间描述", + "i18n_464f3d4ea3": "角色", + "i18n_465260fe80": "年", + "i18n_4696724ed3": "触发器", + "i18n_46a04cdc9c": "文件描述:", + "i18n_46aca09f01": "解绑会检查数据关联性,不会真实请求节点删除项目信息", + "i18n_46ad87708f": "ssh名称", + "i18n_46c8ba7b7f": "如果按钮不可用,请去资产管理 ssh 列表的关联中新增当前工作空间允许管理的授权文件夹", + "i18n_46e3867956": "执行中", + "i18n_46e4265791": "构建 ID", + "i18n_4705b88497": "作用域", + "i18n_47072e451e": "管理节点:", + "i18n_470e9baf32": "允许执行的内存节点", + "i18n_471c6b19cf": "迁移前您检查迁出机器和迁入机器的连接状态和网络状态避免未知错误或者中断造成流程失败产生冗余数据!!!!", + "i18n_4722bc0c56": "终端", + "i18n_473badc394": "发布的节点", + "i18n_4741e596ac": "报警时间", + "i18n_475a349f32": "当前构建还没有生成触发器", + "i18n_475cd76aec": "统计的网卡:", + "i18n_47768ed092": "极不安全", + "i18n_47bb635a5c": "数据可能出现一定时间延迟", + "i18n_47d68cd0f4": "服务", + "i18n_47dd8dbc7d": "搜索项目ID", + "i18n_47e4123886": "新增分发", + "i18n_47ff744ef6": "编辑文件", + "i18n_481ffce5a9": "匹配秒", + "i18n_4826549b41": "命令模版是用于在线管理一些脚本命令,如初始化软件环境、管理应用程序等", + "i18n_48281fd3f0": "真的要删除构建信息么?删除也将同步删除所有的构建历史记录信息", + "i18n_4838a3bd20": "按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页", + "i18n_4871f7722d": "任务更新时间", + "i18n_48735a5187": "剩余空间(未分配)", + "i18n_48a536d0bb": "修改容器配置,重新运行", + "i18n_48d0a09bdd": "浅色", + "i18n_48e79b3340": "】文件么?", + "i18n_48fe457960": "(存在兼容问题,实际使用中需要提前测试) go sdk 镜像使用:https://studygolang.com/dl/golang/go{'${GO_VERSION}'}.linux-{'${ARCH}'}.tar.gz", + "i18n_4956eb6aaa": "负载", + "i18n_49574eee58": "确定要操作吗?", + "i18n_49645e398b": "如果配置错误需要重启服务端并新增命令行参数 --rest:ip_config 将恢复默认配置", + "i18n_497bc3532b": "JVM 参数", + "i18n_497ddf508a": "新建空白文件", + "i18n_498519d1af": "刷新数据", + "i18n_499f058a0b": "退出登录成功", + "i18n_49a9d6c7e6": "通过以下二维码进行一次性捐款赞助,请作者喝一杯咖啡☕️", + "i18n_49d569f255": "请输入要检查的 host", + "i18n_49e56c7b90": "确认修改", + "i18n_4a00d980d5": "简单好用", + "i18n_4a0e9142e7": "钉钉", + "i18n_4a346aae15": "插件版本:", + "i18n_4a4e3b5ae4": "描述:", + "i18n_4a5ab3bc72": "操作:", + "i18n_4a6f3aa451": " :每个点钟的5分执行,00:05,01:05……", + "i18n_4a98bf0c68": "任务详情", + "i18n_4aac559105": "权重", + "i18n_4ab578f3df": "环境变量:", + "i18n_4ad6e58ebc": "机器SSH", + "i18n_4af980516d": "为了您的账号安全系统要求必须开启两步验证来确保账号的安全性", + "i18n_4b027f3979": "提醒", + "i18n_4b0cb10d18": "请输入 SMTP host", + "i18n_4b1835640f": "在 Settings-->Developer settings-->Personal access tokens 中获取", + "i18n_4b386a7209": "获取变量值地址", + "i18n_4b404646f4": "容器标签,如:key1=values1&keyvalue2", + "i18n_4b5e6872ea": "驻留集", + "i18n_4b96762a7e": "最后修改时间", + "i18n_4b9c3271dc": "重置", + "i18n_4ba304e77a": "钉钉账号登录", + "i18n_4bbc09fc55": "在文件第 3 - 20 行中搜索", + "i18n_4c096c51a3": "端口号:", + "i18n_4c0eead6ff": "新增参数", + "i18n_4c28044efc": "确认要将选中的 ", + "i18n_4c69102fe1": "再判断允许时段。配置允许时段后用户只能在对应的时段执行相应功能的操作", + "i18n_4c7c58b208": "请选择节点状态", + "i18n_4c7e4dfd33": "当目标工作空间不存在对应的节点时候将自动创建一个新的docker(逻辑docker)", + "i18n_4c83203419": "跳转到第三方系统中", + "i18n_4c9bb42608": "前缀", + "i18n_4cbc136874": "文件夹:", + "i18n_4cbc5505c7": "差异构建是指构建时候是否判断仓库代码有变动,如果没有变动则不执行构建", + "i18n_4ccbdc5301": "菜单", + "i18n_4cd49caae4": "分发耗时", + "i18n_4ce606413e": "仓库类型", + "i18n_4cfca88db8": "选择分发文件", + "i18n_4d18dcbd15": "真的要还原备份信息么?", + "i18n_4d351f3c91": "禁止 IP", + "i18n_4d49b2a15f": "自动执行:docker", + "i18n_4d775d4cd7": "显示", + "i18n_4d7dc6c5f8": "写", + "i18n_4d85ac1250": "系统管理", + "i18n_4d85c37f0d": "工作空间:", + "i18n_4d9c3a0ed0": "Script 内容", + "i18n_4dc781596b": "中使用了如下开源软件,我们衷心感谢有了他们的开源 Jpom 才能更完善", + "i18n_4df483b9c7": "项目文件 ", + "i18n_4e33dde280": "当前目录:", + "i18n_4e54369108": "文件类型没有触发器功能", + "i18n_4e7e04b15d": "服务名称必填", + "i18n_4ed1662cae": "请选择连接方式", + "i18n_4ee2a8951d": "接口响应 ContentType 均为:text/plain", + "i18n_4ef719810b": "没有任何运行中的任务", + "i18n_4effdeb1ff": "在文件第 1 - 2 行中搜索", + "i18n_4f08d1ad9f": "算法 OID", + "i18n_4f095befc0": "此配置仅对服务端管理生效, 工作空间的 ssh 配置需要单独配置", + "i18n_4f35e80da6": "路径", + "i18n_4f4c28a1fb": "文件内容格式要求:env_name=xxxxx 不满足格式的行将自动忽略", + "i18n_4f50cd2a5e": "紧凑模式", + "i18n_4f52df6e44": "关闭中", + "i18n_4f8a2f0b28": "未运行", + "i18n_4f8ca95e7b": "名", + "i18n_4f9e3db4b8": "选择构建", + "i18n_4fb2400af7": "容器是运行中可以进入终端", + "i18n_4fb95949e5": "开启中", + "i18n_4fdd2213b5": "项目 ID", + "i18n_500789168c": "清空还原将会先删除项目目录中的文件再将对应备份文件恢复至当前目录", + "i18n_5011e53403": "发布集群", + "i18n_503660aa89": "排除:", + "i18n_50411665d7": "保留个数", + "i18n_50453eeb9e": "当前工作空间还没有逻辑节点不能创建节点脚本奥", + "i18n_504c43b70a": "端口/PID", + "i18n_5068552b18": "历史监控图表", + "i18n_50940ed76f": "下载成功", + "i18n_50951f5e74": "请选择分支", + "i18n_50a299c847": "构建名称", + "i18n_50c7929dd9": " 欢迎 ", + "i18n_50d2671541": "确定是同一个脚本", + "i18n_50ed14e70b": "深色 dracula", + "i18n_50f472ee4e": "单位秒,默认 10 秒,最小 3 秒", + "i18n_50f975c08e": "构建产物保留天数,小于等于 0 为跟随全局保留配置。注意自动清理仅会清理记录状态为:(构建结束、发布中、发布失败、发布失败)的数据避免一些异常构建影响保留个数", + "i18n_50fb61ef9d": "脚本名称", + "i18n_50fe3400c7": "真的要删除该执行记录吗?", + "i18n_50fefde769": "是否为压缩包", + "i18n_512e1a7722": "请选择操作者", + "i18n_51341b5024": "服务端分发的脚本", + "i18n_514b320d25": "如何选择构建方式", + "i18n_5169b9af9d": "信息丢失", + "i18n_5177c276a0": "集群不能手动创建,创建需要多个服务端使用通一个数据库,并且配置不同的集群 id 来自动创建集群信息", + "i18n_518df98392": "从尾搜索", + "i18n_5195c0d198": "可以管理{count}个工作空间", + "i18n_51c92e6956": "同步系统配置", + "i18n_51d47ddc69": "回调 url", + "i18n_51d6b830d4": "在线构建目录", + "i18n_52409da520": "联系人", + "i18n_527466ff94": "请求参数", + "i18n_527f7e18f1": "上传前请阅读更新日志里面的说明和注意事项并且更新前", + "i18n_52a8df6678": "】文件夹么?", + "i18n_52b526ab9e": "清空浏览器缓存配置将恢复默认", + "i18n_52b6b488e2": "脚本模版是存储在节点(插件端),执行也都将在节点里面执行,服务端会定时去拉取执行日志,拉取频率为 100 条/分钟", + "i18n_52c6af8174": "请输入客户端密钥 [clientSecret]", + "i18n_52d24791ab": "真的要删除这些文件么?", + "i18n_52eedb4a12": "报警方式", + "i18n_52ef46c618": "不发布:只执行构建流程并且保存构建历史", + "i18n_532495b65b": "副本数", + "i18n_53365c29c8": "下载状态:", + "i18n_534115e981": "信息不完整不能编辑", + "i18n_5349f417e9": "搜关键词", + "i18n_536206b587": "当前机器还未监控到任何数据", + "i18n_537b39a8b5": "必填", + "i18n_53bdd93fd6": "查看脚本库", + "i18n_541e8ce00c": "关于开源软件", + "i18n_542a0e7db4": "同步授权", + "i18n_543296e005": "请输入授权 url [authorizationUri]", + "i18n_543a5aebc8": "真的删除当前变量吗?", + "i18n_543de6ff04": "分发状态消息", + "i18n_54506fe138": "重置选择", + "i18n_5457c2e99f": "# 使用 copy 文件的方式缓存,反之使用软链的形式。copy 文件方式缓存 node_modules 可以避免 npm WARN reify Removing non-directory", + "i18n_547ee197e5": "新建目录", + "i18n_5488c40573": "节点项目", + "i18n_54f271cd41": "脚本模板", + "i18n_5516b3130c": "飞书账号登录", + "i18n_551e46c0ea": " 名称: ", + "i18n_55405ea6ff": "导出", + "i18n_556499017a": "项目文件会存放到", + "i18n_5569a840c8": "请输入IP禁止,多个使用换行,支持配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_55721d321c": "参数描述", + "i18n_55939c108f": "输入文件或者文件夹名", + "i18n_55abea2d61": "服务端", + "i18n_55b2d0904f": "在执行多节点分发时候使用 顺序重启、完整顺序重启 时候需要保证项目能正常重启", + "i18n_55cf956586": "加入集群", + "i18n_55d4a79358": "配置需要声明使用具体的 docker 来执行构建相关操作(建议使用服务端所在服务器中的 docker)", + "i18n_55da97b631": " ,范围0~59,但是第一位不做匹配当为7位时,最后一位表示", + "i18n_55e690333a": "当前工作空间还没有 Docker 集群", + "i18n_55e99f5106": "钉钉通知地址", + "i18n_55f01e138a": "微信赞赏", + "i18n_56071a4fa6": "超时时间", + "i18n_56230405ae": "解绑不会真实请求节点删除脚本信息", + "i18n_562d7476ab": "周日", + "i18n_56469e09f7": "请到【系统管理】-> 【资产管理】-> 【Docker管理】新增Docker,或者将已新增的Docker授权关联、分配到此工作空间", + "i18n_56525d62ac": "扫描", + "i18n_566c67e764": "已经分配到工作空间的机器无非直接删除,需要到分配到的各个工作空间逐一删除后才能删除资产机器", + "i18n_5684fd7d3d": "账号新密码为:", + "i18n_56bb769354": "下载前请阅读更新日志里面的说明和注意事项并且更新前", + "i18n_56d9d84bff": "工作空间中逻辑节点中的项目数量:", + "i18n_570eb1c04f": "硬盘占用率:", + "i18n_5734b2db4e": "读取行数", + "i18n_576669e450": "请选中要启动的项目", + "i18n_5785f004ea": "请勿手动删除数据目录下面文件,如果需要删除需要提前备份或者已经确定对应文件弃用后才能删除", + "i18n_578adf7a12": "请仔细确认后配置,ip配置后立即生效。配置时需要保证当前ip能访问!127.0.0.1 该IP不受访问限制.支持配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_578ca5bcfd": "163邮箱", + "i18n_57978c11d1": "日志弹窗会非全屏打开", + "i18n_579a6d0d92": "命令值", + "i18n_57b7990b45": "当目标工作空间已经存在 SSH 时候将自动同步 SSH 账号、密码、私钥等信息", + "i18n_57c0a41ec6": "当前数据为默认状态", + "i18n_57cadc4cf3": "会使用 PING 检查", + "i18n_5805998e42": "重启策略", + "i18n_5854370b86": "跟踪文件", + "i18n_585ae8592f": "重建容器", + "i18n_5866b4bced": "集群数:", + "i18n_587a63264b": "覆盖还原", + "i18n_588e33b660": "账号如果开启 MFA(两步验证),使用 Oauth2 登录不会验证 MFA(两步验证)", + "i18n_589060f38e": "升级中,请稍候...", + "i18n_5893fa2280": "邮箱账号", + "i18n_58cbd04f02": "SSH 是指,通过 SSH 命令的方式对产物进行发布或者执行多条命令来实现发布(需要到 SSH 中提前去新增)", + "i18n_58e998a751": "删除会检查数据关联性,并且节点不存在项目或者脚本", + "i18n_58f9666705": "大小", + "i18n_590b9ce766": "目前支持都插件有(更多插件尽情期待):", + "i18n_590dbb68cf": "结束时间:", + "i18n_590e5b46a0": "自动备份", + "i18n_592c595891": "开始时间", + "i18n_5936ed11ab": "脚本库用于存储管理通用的脚本,脚本库中的脚本不能直接执行。", + "i18n_593e04dfad": "菜单主题", + "i18n_597b1a5130": "更新状态", + "i18n_59a15a0848": "同步机制采用 IP+PORT+连接方式 确定是同一个服务器", + "i18n_59c316e560": "分发文件", + "i18n_59c75681b4": "通知对象", + "i18n_59d20801e9": "在文件第 17 - 20 行中搜索", + "i18n_5a0346c4b1": "编辑用户", + "i18n_5a1367058c": "返回首页", + "i18n_5a1419b7a2": "数据名称", + "i18n_5a42ea648d": "自建 gitlab 访问地址", + "i18n_5a5368cf9b": "密码错误", + "i18n_5a63277941": "的值有:stop、beforeStop、start、beforeRestart、fileChange", + "i18n_5a7ea53d18": "docker信息", + "i18n_5a8727305e": "请不要优先退出管理节点", + "i18n_5a879a657b": "交换内存", + "i18n_5aabec5c62": "父级ID", + "i18n_5ab90c17a3": "任务结束", + "i18n_5ad7f5a8b2": "结果", + "i18n_5afe5e7ed4": "编辑关联项目", + "i18n_5b1f0fd370": "用于创建节点分发项目、文件中心发布文件", + "i18n_5b3ffc2910": "分发中", + "i18n_5b47861521": "名称:", + "i18n_5baaef6996": "点击重新同步当前工作空间逻辑节点脚本模版信息", + "i18n_5badae1d90": "没有任何脚本", + "i18n_5bb162ecbb": "JVM剩余内存", + "i18n_5bb5b33ae4": "所以这里 我们", + "i18n_5bca8cf7ee": "自定义host, xxx:192.168.0.x", + "i18n_5bcda1b4d7": "会话已经关闭[system-log]", + "i18n_5bd1d267a9": "在 preferences-->Access Tokens 中获取", + "i18n_5c3b53e66c": "修改文件", + "i18n_5c4d3c836f": "需要验证 MFA", + "i18n_5c502af799": "容器名称必填", + "i18n_5c56a88945": "停用", + "i18n_5c89a5353d": "分配节点", + "i18n_5c93055d9c": "一般用于服务器无法连接且已经确定不再使用", + "i18n_5ca6c1b6c7": "请填写集群名称", + "i18n_5cb39287a8": "监控功能", + "i18n_5cc7e8e30a": "修改文件权限", + "i18n_5d07edd921": "请填写集群IP", + "i18n_5d14e91b01": "主要ID", + "i18n_5d368ab0a5": "执行命令将自动替换为 sh 命令文件、并自动加载环境变量:/etc/profile、/etc/bashrc、~/.bashrc、~/.bash_profile", + "i18n_5d414afd86": "从尾搜索、文件前2行、文件后3行", + "i18n_5d459d550a": "处理中", + "i18n_5d488af335": "远程下载文件", + "i18n_5d5fd4170f": "的值有:1", + "i18n_5d6f47d670": "项目为静态文件夹", + "i18n_5d803afb8d": "不能和节点正常通讯", + "i18n_5d817c403e": "没有选择任何数据", + "i18n_5d83794cfa": "节点名称:", + "i18n_5d9c139f38": "内容主题", + "i18n_5dc09dd5bd": "重连 ", + "i18n_5dc1f36a27": "证书描述", + "i18n_5dc78cb700": "构建产物保留个数,小于等于 0 为跟随全局保留配置(如果数值大于 0 将和全局配置对比最小值来参考)。注意自动清理仅会清理记录状态为:(构建结束、发布中、发布失败、发布失败)的数据避免一些异常构建影响保留个数。 将在创建新的构建记录时候检查保留个数", + "i18n_5dc7b04caa": "查看的进程数量", + "i18n_5dff0d31d0": "如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])", + "i18n_5e32f72bbf": "刷新文件表格", + "i18n_5e46f842d8": "监控用户", + "i18n_5e9f2dedca": "是否成功", + "i18n_5ecc709db7": "执行时候默认不加载全部环境变量、需要脚本里面自行加载", + "i18n_5ed197a129": "重置初始化在启动时候传入参数", + "i18n_5ef040a79d": "丢弃包", + "i18n_5ef72bdfce": "命令内容支持工作空间环境变量", + "i18n_5effe31353": "剔除文件夹", + "i18n_5f4c724e61": "请输入任务名", + "i18n_5f5cd1bb1e": "新增关联项目是指,将已经在节点中创建好的项目关联为节点分发项目来实现统一管理", + "i18n_5fafcadc2d": "会话已经关闭[node-script-consloe]", + "i18n_5fbde027e3": "可以引用工作空间的环境变量 变量占位符 {'${xxxx}'} xxxx 为变量名称", + "i18n_5fc6c33832": " 跳至行", + "i18n_5fea80e369": "没有资产DOCKER", + "i18n_5fffcb255d": "插件运行", + "i18n_601426f8f2": "推送到仓库", + "i18n_603dc06c4b": "您访问的页面不存在", + "i18n_60585cf697": " 欢迎", + "i18n_607558dbd4": "项目数", + "i18n_607e7a4f37": "查看", + "i18n_609b5f0a08": "时", + "i18n_60b4c08f5c": "您确定要停止当前容器吗?", + "i18n_6106de3d87": "JDK版本", + "i18n_61341628ab": " :表示列表", + "i18n_6143a714d0": "编码格式", + "i18n_616879745d": "凌晨0点和中午12点", + "i18n_61955b0e4b": "没有项目状态以及控制等功能", + "i18n_61a3ec6656": "介绍", + "i18n_61bfa4e925": "需要在仓库里面 dockerfile,如果多文件夹查看可以指定二级目录如果 springboot-test-jar:springboot-test-jar/Dockerfile", + "i18n_61c0f5345d": "SMTP 地址:【smtp.163.com, smtp.126.com...】,密码是邮箱授权码,端口默认 25,SSL 端口 465", + "i18n_61e7fa1227": "编辑节点", + "i18n_61e84eb5bb": "开始时间:", + "i18n_620489518c": "参数{index}值", + "i18n_620efec150": "更多开源说明", + "i18n_62170d5b0a": "搜索参考", + "i18n_6228294517": "菜单配置只对非超级管理员生效", + "i18n_622d00a119": "执行脚本的路径", + "i18n_624f639f16": "通用邮箱", + "i18n_625aa478e2": "从尾搜索、文件前0行、文件后3行", + "i18n_625fb26b4b": "取消", + "i18n_627c952b5e": "总空间", + "i18n_6292498392": " 查找下一个", + "i18n_629a6ad325": "安全管理", + "i18n_629f3211ca": "修剪类型", + "i18n_631d5b88ab": "请输入项目存放路径授权,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径", + "i18n_632a907224": "重置为重新生成触发地址,重置成功后之前的触发器地址将失效,触发器绑定到生成触发器到操作人上,如果将对应的账号删除触发器将失效", + "i18n_6334eec584": "5秒一次", + "i18n_635391aa5d": "下载产物", + "i18n_637c9a8819": "至少选择1个节点项目", + "i18n_638cddf480": "创建人,全匹配", + "i18n_639fd37242": "目前使用的 docker swarm 集群,需要先创建 swarm 集群才能选择", + "i18n_63b6b36c71": "选择证书", + "i18n_63c9d63eeb": "可以同时展开多个菜单", + "i18n_63dd96a28a": "密码支持引用工作空间变量:", + "i18n_63e975aa63": "安装ID:", + "i18n_640374b7ae": "挂载卷", + "i18n_641796b655": "构建完成", + "i18n_6428be07e9": "配置系统公告", + "i18n_643f39d45f": "非悬空", + "i18n_6446b6c707": "昵称长度为2-10", + "i18n_646a518953": "请输入项目ID", + "i18n_6470685fcd": ":表示匹配这个位置任意的时间(与_##_*\"作用一致)", + "i18n_649231bdee": "文件后缀", + "i18n_64933b1012": "存储选项", + "i18n_6496a5a043": "命令名称", + "i18n_649d7fcb73": "新集群需要手动配置集群管理资产分组、集群访问地址", + "i18n_649d90ab3c": "关闭右侧", + "i18n_649f8046f3": "请选择SSH节点", + "i18n_64c083c0a9": "结果描述", + "i18n_64eee9aafa": "开机时间", + "i18n_652273694e": "主机", + "i18n_65571516e2": "构建备注:", + "i18n_657969aa0f": "编辑 Docker", + "i18n_657f3883e3": "不执行发布流程", + "i18n_65894da683": "发布方式:", + "i18n_65cf4248a8": "不能初始化", + "i18n_65f66dfe97": "清空当前缓冲区内容", + "i18n_66238e0917": "已经存在的账号与外部系统账号不一致时不支持绑定外部系统账号", + "i18n_663393986e": "解绑", + "i18n_6636793319": "真的要删除节点么?删除会检查数据关联性,并且节点不存在项目或者脚本", + "i18n_664c205cc3": "真的要清除仓库隐藏字段信息么?(密码,私钥)", + "i18n_667fa07b52": "个节点升级到", + "i18n_66aafbdb72": "最新构建ID", + "i18n_66ab5e9f24": "新增", + "i18n_66b71b06c6": "上传压缩文件(自动解压)", + "i18n_66c15f2815": "匹配包含数字的行", + "i18n_66e9ea5488": "日志名称", + "i18n_6707667676": "主机名", + "i18n_6709f4548f": "随机生成", + "i18n_67141abed6": "项目授权路径+项目文件夹", + "i18n_67425c29a5": "超时时间(s)", + "i18n_674a284936": "当isMatchSecond为 true 时才会匹配秒部分默认都是关闭的", + "i18n_674e7808b5": "mfa 验证码", + "i18n_679de60f71": "请填写日志项目名称", + "i18n_67aa2d01b9": "工作空间的菜单、环境变量、节点分发授权需要逐一配置", + "i18n_67b667bf98": "部分备份", + "i18n_67e3d3e09c": "批量构建", + "i18n_67e7f9e541": "监控周期", + "i18n_6816da19f3": "关闭其他", + "i18n_6835ed12b9": "环境变量的key", + "i18n_685e5de706": "容器构建", + "i18n_6863e2a7b5": "脚本执行历史", + "i18n_686a19db6a": "自动删除", + "i18n_68a1faf6e2": "批量构建传入其他参数将同步执行修改", + "i18n_68af00bedb": "表格视图才能使用工作空间同步功能", + "i18n_68c55772ca": "请输入授权方的网页应用ID", + "i18n_69056f4792": "部分操作状态码可能为 0", + "i18n_690a3d1a69": "执行容器", + "i18n_691b11e443": "当前工作空间", + "i18n_6928f50eb3": "支持配置系统参数:", + "i18n_69384c9d71": "点击查看历史趋势", + "i18n_693a06987c": "请填写用户账号", + "i18n_6948363f65": "取消定时,不再定时执行(支持 ! 前缀禁用定时执行,如:!0 0/1 * * * ?)", + "i18n_694fc5efa9": "刷新", + "i18n_695344279b": "文件上传id生成失败:", + "i18n_6953a488e3": "选择逻辑节点", + "i18n_697d60299e": "检测到当前已经登录账号", + "i18n_69c3b873c1": "本地构建", + "i18n_69c743de70": "节点的IP", + "i18n_69de8d7f40": "还原", + "i18n_6a359e2ab3": "scriptId也可以引入脚本库中的脚本,需要提前同步至机器节点中", + "i18n_6a49f994b1": "构建过程执行对应的脚本,开始构建,构建完成,开始发布,发布完成,构建异常,发布异常", + "i18n_6a4a0f2b3b": "同步机制采用节点地址确定是同一个服务器(节点)", + "i18n_6a588459d0": "工作空间名称", + "i18n_6a620e3c07": "同步", + "i18n_6a658517f3": "任务日志", + "i18n_6a66d4cdf3": "延迟,容器回滚间隔时间", + "i18n_6a6c857285": "分发节点", + "i18n_6a8402afcb": "解析文件,准备上传中 ", + "i18n_6a8c30bd06": "加载编辑器中", + "i18n_6a922e0fb6": "插件端端口", + "i18n_6a9231c3ba": "函数 args 参数,非必填", + "i18n_6aa7403b18": "如果使用 SSH 方式但是 SSH 无法选择,是表示系统没有监测到 docker 服务", + "i18n_6aab88d6a3": "保存并重启", + "i18n_6ab78fa2c4": "邮箱地址", + "i18n_6ac61b0e74": "建议还原和当前版本一致的文件或者临近版本的文件", + "i18n_6ad02e7a1b": "页面资源加载中....", + "i18n_6adcbc6663": "配置方式:SSH列表->操作栏中->关联按钮->对应工作空间->操作栏中->配置按钮", + "i18n_6af7686e31": "分钟刷新一次", + "i18n_6b0bc6432d": "操作者", + "i18n_6b189bf02d": "容器数:", + "i18n_6b29a6e523": "启动项目", + "i18n_6b2e348a2b": "定时执行", + "i18n_6b46e2bfae": "真的当前工作空间么", + "i18n_6b4fd0ca47": "支持配置发送方:遵循RFC-822标准 发件人可以是以下形式:", + "i18n_6b6d6937d7": "163邮箱 SSL", + "i18n_6bb5ba7438": "限制禁止在在线终端执行的命令", + "i18n_6be30eaad7": "请输入超时时间", + "i18n_6bf1f392c0": "当前状态", + "i18n_6c08692a3a": "密码若没修改可以不用填写", + "i18n_6c14188ba0": "不能下载目录", + "i18n_6c24533675": "请选择一位报警联系人或者填写webhook", + "i18n_6c72e9d9de": "编辑分发项目", + "i18n_6c776e9d91": "项目启动,停止,重启,文件变动都将请求对应的地址,非必填,GET请求", + "i18n_6d5f0fb74b": "镜像构建成功后是否需要推送到远程仓库", + "i18n_6d68bd5458": "全量备份", + "i18n_6d7f0f06be": "请选择发布操作", + "i18n_6d802636ab": "隐私", + "i18n_6da242ea50": "任务Id", + "i18n_6dcf6175d8": "现在就去", + "i18n_6de1ecc549": "查看服务端脚本", + "i18n_6e02ee7aad": "周期的长度,以微秒为单位。", + "i18n_6e2d78a20e": "从尾搜索、文件前20行、文件后3行", + "i18n_6e60d2fc75": "页面启用紧凑模式", + "i18n_6e69656ffb": "文件不能为空", + "i18n_6e70d2fb91": "构建参数,如:key1=value1&key2=value2", + "i18n_6ea1fe6baa": "基础信息", + "i18n_6eb39e706c": "编辑机器", + "i18n_6ef90ec712": "请填写要拉取的镜像名称", + "i18n_6f15f0beea": "两次密码不一致...", + "i18n_6f32b1077d": "请输入工作空间备注,留空使用默认的名称", + "i18n_6f5b238dd2": " SSH、本地命令发布都执行变量替换,系统预留变量有:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_6f6ee88ec4": "支持开源", + "i18n_6f73c7cf47": "保留天数:", + "i18n_6f7ee71e77": "静态目录", + "i18n_6f854129e9": "分组/标签", + "i18n_6f8907351b": "同步节点配置", + "i18n_6f8da7dcca": "节点地址格式为:IP:PORT (示例:192.168.1.100:2123)", + "i18n_6f9193ac80": "启用两步验证", + "i18n_6fa1229ea9": "一键分发同步多个节点的授权配置", + "i18n_6ffa21d235": "分发节点是指将变量同步到对应节点,在节点脚本中也可以使用当前变量", + "i18n_7006410585": "无论返回什么退出代码,始终重新启动容器。", + "i18n_7010264d22": "没有开启任何认证", + "i18n_702430b89d": "页面启用宽松模式", + "i18n_702afc34a0": "差异发布:", + "i18n_7030ff6470": "错误", + "i18n_7035c62fb0": "账号", + "i18n_704f33fc74": "从头搜索、文件前0行、文件后3行", + "i18n_706333387b": "此功能不能保证新增的容器和之前容器参数完全一致请慎重使用。", + "i18n_7088e18ac9": "卷", + "i18n_708c9d6d2a": "请选择", + "i18n_70a6bc1e94": "当前系统已经初始化过啦,不能重复初始化", + "i18n_70b3635aa3": "执行时间", + "i18n_70b5b45591": "快速安装", + "i18n_70b9a2c450": "真的要退出系统么?", + "i18n_710ad08b11": "禁用", + "i18n_7114d41b1d": "超级管理员没有任何限制", + "i18n_712cdd7984": "合作咨询", + "i18n_713c986135": "容器构建会在 docker 中生成相关挂载目录,一般情况不需要人为操作", + "i18n_7156088c6e": "编码方式", + "i18n_71584de972": "非服务器开机自启,如需开机自启建议配置", + "i18n_715ec3b393": "用于快捷同步其他机器节点的配置", + "i18n_7173f80900": "拒绝", + "i18n_71a2c432b0": "编辑变量", + "i18n_71bbc726ac": "跟随系统", + "i18n_71c6871780": "定时任务表达式", + "i18n_71dc8feb59": "未配置", + "i18n_71ee088528": "桥接模式:", + "i18n_7220e4d5f9": "方式", + "i18n_7229ecc631": "次", + "i18n_7293bbb0ff": "总 inode 数", + "i18n_729eebb5ff": "没有对应的SSH", + "i18n_72d14a3890": "请选择用户的权限组", + "i18n_72d46ec2cf": "登录信息过期", + "i18n_72d4ade571": ",仅是用于提示参数的含义", + "i18n_72e7a5d105": "镜像id", + "i18n_72eae3107e": "灰绿 abbott", + "i18n_72ebfe28b0": "定时", + "i18n_7307ca1021": "自动启动", + "i18n_7327966572": "彻底删除", + "i18n_7329a2637c": "集群ID", + "i18n_73485331c2": "文件信息", + "i18n_73578c680e": "数据目录是指程序在运行过程中产生的文件以及数据存储目录", + "i18n_73651ba2db": "批量重启", + "i18n_7370bdf0d2": "脚本日志", + "i18n_738a41f965": "项目名称", + "i18n_73a87230e0": "文件系统类型", + "i18n_73b7b05e6e": " 将脚本分发到对应的机器节点中,对应的机器节点可以引用对应的脚本 ", + "i18n_73b7e8e09e": "在使用 beta 版过程中遇到问题可以随时反馈给我们,我们会尽快为您解答。", + "i18n_73c980987a": "加载容器可用标签中....", + "i18n_73d8160821": "以 yaml/yml 格式配置,scriptId 为项目路径下的脚本文件的相对路径或者脚本模版ID,可以到脚本模版编辑弹窗中查看 scriptId", + "i18n_73ed447971": "强烈建议您使用 TLS 证书", + "i18n_73f798a129": "免费社群", + "i18n_7457228a61": "远程下载地址", + "i18n_74bdccbb5d": "我的工作空间", + "i18n_74c5c188ae": "操作成功接口 HTTP 状态码为 200", + "i18n_74d5f61b9f": "构建触发", + "i18n_74d980d4f4": "在单页列表里面 file 类型项目将自动排序到最后", + "i18n_74dc77d4f7": "容器id", + "i18n_74dd7594fc": "发生报警时候请求", + "i18n_74ea72bbd6": "集群管理", + "i18n_751a79afde": "30分钟", + "i18n_7527da8954": "普通用户", + "i18n_7548ea6316": "点击可以折叠左侧菜单栏", + "i18n_75528c19c7": "自动重启", + "i18n_7561bc005e": "构建过程请求,非必填,GET请求", + "i18n_75769d1ac8": "读", + "i18n_757a730c9e": "无法连接", + "i18n_758edf4666": "从头搜索、文件前2行、文件后3行", + "i18n_75c63f427a": "此选项为一个实验属性实际效果基本无差异", + "i18n_75fc7de737": "路由", + "i18n_7617455241": "文件中如果存在:MemAvailable、MemTotal 这两个字段,那么 oshi 直接使用,所以本系统 中内存占用计算方式:内存占用=(total-available)/total", + "i18n_762e05a901": "差异发布是指对应构建产物和项目文件夹里面的文件是否存在差异,如果存在增量差异那么上传或者覆盖文件。", + "i18n_7650487a87": "地址", + "i18n_76530bff27": "请输入私人令牌", + "i18n_7653297de3": "跳转", + "i18n_765592aa05": "如果未挂载容器数据目录请提前备份数据后再使用此功能。", + "i18n_765d09eea5": "当前文件不可读,需要配置可读文件授权", + "i18n_767fa455bb": "目录", + "i18n_768e843a3e": "类 192", + "i18n_769d88e425": "完成", + "i18n_76aebf3cc6": "日志大小", + "i18n_76ebb2be96": "1分钟", + "i18n_77017a3140": "关联容器标签", + "i18n_770a07d78f": "当目标工作空间不存在对应的 脚本 时候将自动创建一个新的 脚本", + "i18n_771d897d9a": "状态码", + "i18n_77373db7d8": "接收报警消息,非必填,GET请求", + "i18n_7737f088de": "批量重新启动", + "i18n_773b1a5ef6": "请选择语言模式", + "i18n_775fde44cf": "进程端口缓存:", + "i18n_7760785daf": "自由脚本", + "i18n_7764df7ccc": "开启差异发布但不开启清空发布时相当于只做增量和变动更新", + "i18n_77688e95af": "容器重建是指使用已经创建的容器参数重新创建一个相同的容器。", + "i18n_7777a83497": "请输入构建备注,长度小于 240", + "i18n_77834eb6f5": "您使用本系統", + "i18n_7785d9e038": "低版本项目数据未存储节点ID,对应项目数据也将出来在孤独数据中(此类数据不影响使用)", + "i18n_77b9ecc8b1": "备份名称", + "i18n_77c1e73c08": "脚本存放路径:{'${user.home}'}/.jpom/xxxx.sh,执行脚本路径:{'${user.home}'},执行脚本方式:bash {'${user.home}'}/.jpom/xxxx.sh par1 par2", + "i18n_77c262950c": "使用 Access Token 一次导入多个项目", + "i18n_77e100e462": "还没有状态消息", + "i18n_780afeac65": "是否开启", + "i18n_780fb9f3d0": "更新时间:", + "i18n_7824ed010c": "真的取消当前发布任务吗?", + "i18n_7854b52a88": "启用", + "i18n_787fdcca55": "系统配置", + "i18n_788a3afc90": "已失联", + "i18n_78a4b837e3": "能通讯的IP", + "i18n_78b2da536d": "构建过程请求对应的地址,开始构建,构建完成,开始发布,发布完成,构建异常,发布异常", + "i18n_78ba02f56b": "真的要彻底删除分发信息么?删除后节点下面的项目也都将彻底删除,彻底项目会自动删除项目相关文件奥(包含项目日志,日志备份,项目文件)", + "i18n_78caf7115c": "任务名称", + "i18n_78dccb6e97": "所有节点(插件端)", + "i18n_79076b6882": "真的要批量删除这些构建信息么?删除也将同步删除所有的构建历史记录信息,如果中途删除失败将终止删除操作", + "i18n_7912615699": "连接状态", + "i18n_791870de48": "仓库密码", + "i18n_791b6d0e62": "排名按照字母 a-z 排序", + "i18n_79698c57a2": "当前工作空间还没有节点", + "i18n_798f660048": "模版节点", + "i18n_799ac8bf40": "支持变量引用:{'${TASK_ID}'}、{'${FILE_ID}'}、{'${FILE_NAME}'}、{'${FILE_EXT_NAME}'}", + "i18n_79a7072ee1": "令牌 url", + "i18n_79c6b6cff7": "关联分组", + "i18n_79d3abe929": "复制", + "i18n_7a30792e2a": "编辑 SSH", + "i18n_7a3c815b1e": "文件目录", + "i18n_7a4ecc606c": "镜像标签,如:key1=values1&keyvalue2 使用 URL 编码", + "i18n_7a5dd04619": "注意执行相关命令需要所在服务器中存在对应的环境", + "i18n_7a7e25e9eb": "确定要将此数据下移吗?下移操作可能因为列表后续数据没有排序值操作无效!", + "i18n_7a811cc1e5": "复制 ", + "i18n_7a93e0a6ae": "选择企业版本或者购买授权:", + "i18n_7aa81d1573": "请输入文件名称", + "i18n_7aaee3201a": "如果需要删除需要提前备份或者已经确定对应文件弃用后才能删除 !!!!", + "i18n_7afb02ed93": "当前没有可以引用的环境变量", + "i18n_7b2cbfada9": "发布前停止:", + "i18n_7b36b18865": "分区ID", + "i18n_7b61408779": "# 项目文件备份路径", + "i18n_7b8e7d4abc": "真的要删除执行记录么?", + "i18n_7b961e05d0": "表示月的最后一天", + "i18n_7bcbf81120": "接收包", + "i18n_7bcc3f169c": "# 内置变量 ${JPOM_WORKING_DIR} ${JPOM_BUILD_ID}", + "i18n_7bf62f7284": "手动取消分发", + "i18n_7c0ee78130": "构建日志", + "i18n_7c223eb6e9": "发布系统公告", + "i18n_7c9bb61536": "日志项目名称", + "i18n_7cb8d163bb": "变量名称", + "i18n_7cc3bb7068": "不会真实请求节点删除项目信息", + "i18n_7ce511154f": "创建之后不能修改", + "i18n_7d23ca925c": "服务端时间", + "i18n_7d3f2fd640": "在文件第 3 - 2147483647 行中搜索", + "i18n_7ddbe15c84": "网络", + "i18n_7dde69267a": "未绑定集群的分组:", + "i18n_7de5541032": "如果 ssh 没有配置授权目录是不能选择的哟", + "i18n_7dfc7448ec": "真的要删除仓库信息么?", + "i18n_7dfcab648d": "产物", + "i18n_7e000409bb": "容易出现挖矿情况", + "i18n_7e1b283c57": " 添加", + "i18n_7e2b40fc86": "选择节点", + "i18n_7e300e89b1": "分发成功", + "i18n_7e33f94952": ",如果想要切换路径后执行命令则需要", + "i18n_7e359f4b71": "硬盘总量:", + "i18n_7e58312632": "编辑日志搜索", + "i18n_7e866fece6": "请输入两步验证码", + "i18n_7e930b95ef": "发布文件", + "i18n_7e951d56d9": "操作时间", + "i18n_7e9f0d2606": "项目是指,节点中的某一个项目,需要提前在节点中创建项目", + "i18n_7ef30cfd31": "附加环境变量是指读取仓库指定环境变量文件来新增到执行构建运行时", + "i18n_7f0abcf48d": "需要到编辑中去为一个节点绑定一个 ssh信息才能启用该功能", + "i18n_7f3809d36b": "构建结束", + "i18n_7f5bcd975b": "cpu占用", + "i18n_7f7c624a84": "批量操作", + "i18n_7f7ee903da": "发布隐藏文件", + "i18n_7fb5bdb690": "软件致谢", + "i18n_7fb62b3011": "批量删除", + "i18n_7fbc0f9aae": "执行时间开始", + "i18n_7fc88aeeda": "修改密码", + "i18n_800dfdd902": "今天", + "i18n_80198317ff": "并向您的朋友推荐或分享:", + "i18n_8023baf064": "通知状态", + "i18n_80669da961": "CPU占用", + "i18n_807ed6f5a6": "暂无任何数据", + "i18n_8086beecb3": "标签名称:", + "i18n_808c18d2bb": "值为 true 表示项目当前为运行中", + "i18n_809b12d6a0": "请耐心等待暂时不用刷新页面", + "i18n_80cfc33cbe": "确认重置", + "i18n_81301b6813": "打开终端", + "i18n_81485b76d8": "请输入主机地址", + "i18n_814dd5fb7d": "真的要删除备份信息么?", + "i18n_815492fd8d": "旧版程序包占有空间", + "i18n_8160b4be4e": "异常关闭", + "i18n_819767ada1": "用户名", + "i18n_8198e4461a": "项目:", + "i18n_81afd9e713": "队列等待", + "i18n_81c1dff69c": "解决办法", + "i18n_81d7d5cd8a": "命令详细描述", + "i18n_81e4018e9d": "悬空类型", + "i18n_82416714a8": "需要测试的端口", + "i18n_824607be6b": "保留天数", + "i18n_824914133f": "没有任何脚本库", + "i18n_8283f063d7": "项目完整目录", + "i18n_828efdf4e5": "开启MFA数", + "i18n_82915930eb": "并发执行", + "i18n_829706defc": "在线构建(构建关联仓库、构建历史)", + "i18n_829abe5a8d": "分组", + "i18n_82b89bd049": "日志弹窗会全屏打开", + "i18n_82d2c66f47": "批量分配", + "i18n_8306971039": "所属用户", + "i18n_8309cec640": "请选择节点项目,可能是节点中不存在任何项目,需要去节点中创建项目", + "i18n_833249fb92": "当前文件用时", + "i18n_8347a927c0": "修改", + "i18n_835050418f": "确认要上传最新的插件包吗?", + "i18n_83611abd5f": "发布", + "i18n_8363193305": "请输入回调重定向 url [redirectUri]", + "i18n_8388c637f6": "自启动", + "i18n_83aa7f3123": "分发id", + "i18n_83c61f7f9e": "请选择监控用户", + "i18n_83ccef50cd": "当目标工作空间已经存在 脚本 时候将自动同步 脚本内容、默认参数、定时执行、描述", + "i18n_83f25dbaa0": "绑定节点", + "i18n_8400529cfb": "重置自定义的进程名信息", + "i18n_8432a98819": "操作功能", + "i18n_843f05194a": "显示所有", + "i18n_84415a6bb1": "重置下载 token 信息,重置后之前的下载 token 将失效", + "i18n_844296754e": "虚拟内存", + "i18n_84592cd99c": "可以理解为项目打包的目录。 如 Jpom 项目执行(构建命令)", + "i18n_84597bf5bc": "阿里云企业邮箱配置", + "i18n_84632d372f": "点击查看详情", + "i18n_84777ebf8b": "安全提醒", + "i18n_847afa1ff2": "请输入IP授权,多个使用换行,0.0.0.0 是开放所有IP,支持配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_848c07af9b": "管理面板", + "i18n_848e4e21da": "如:--server", + "i18n_8493205602": "开", + "i18n_84aa0038cf": "系统日志", + "i18n_84b28944b7": "超时时间(S)", + "i18n_84d331a137": "秒 (值太小可能会取不到节点状态)", + "i18n_84e12f7434": "会话已经关闭[ssh-terminal]", + "i18n_853d8ab485": "正在构建", + "i18n_85451d2eb5": "请输入变量值", + "i18n_8580ad66b0": "真的要彻底删除项目么?彻底项目会自动删除项目相关文件奥(包含项目日志,日志备份,项目文件)", + "i18n_85be08c99a": "未查询到任何数据", + "i18n_85cfcdd88b": "本地构建是指直接在服务端中的服务器执行构建命令", + "i18n_85da2e5bb1": "重启中,请稍候...", + "i18n_85ec12ccd3": "延迟,容器升级间隔时间", + "i18n_85f347f9d0": "用户限制用户只能对应的工作空间里面操作对应的功能", + "i18n_85fe5099f6": "集群", + "i18n_86048b4fea": "移除", + "i18n_860c00f4f7": "每小时", + "i18n_863a95c914": "真的要保存当前配置吗?如果配置有误,可能无法启动服务需要手动还原奥!!!", + "i18n_867cc1aac4": " :范围:0~23", + "i18n_869b506d66": "独立的项目分发请到分发管理中去修改", + "i18n_869ec83e33": "未使用", + "i18n_86b7eb5e83": "删除前需要将关联数据都删除后才能删除当前工作空间?", + "i18n_86c1eb397d": "切换账号", + "i18n_86cd8dcead": "启动时间", + "i18n_86e9e4dd58": "仓库lastcommit", + "i18n_86f3ec932c": "读取大小", + "i18n_86fb7b5421": "节点账号", + "i18n_8704e7bdb7": "请输入令牌 url [accessTokenUri]", + "i18n_871cc8602a": "二级目录", + "i18n_8724641ba8": "间隔(/) > 区间(-) > 列表(,)", + "i18n_8756efb8f4": "真的要删除当前文件夹么?", + "i18n_87659a4953": "确认要关闭 beta 计划吗?", + "i18n_8780e6b3d1": "文件管理", + "i18n_878aebf9b2": "登录名称", + "i18n_87d50f8e03": "容器ID", + "i18n_87db69bd44": "限制资源", + "i18n_87dec8f11e": "错误的工作空间数据", + "i18n_87e2f5bf75": "追加脚本模板", + "i18n_87eb55155a": "行数:", + "i18n_8813ff5cf8": "如果是在启动服务端后安装并配置的环境变量需要通过终端命令来重启服务端才能生效", + "i18n_883848dd37": "实际内存占用", + "i18n_8844085e15": "凌晨0点", + "i18n_884ea031d3": "请输入变量描述", + "i18n_8887e94cb7": "顺序执行(有执行失败将继续)", + "i18n_888df7a89e": "不推荐", + "i18n_88ab27cfd0": "分组/标签:", + "i18n_88b4b85562": "打包预发布环境 npm i && npm run build:stage", + "i18n_88b79928e7": "证书丢失", + "i18n_88c5680d0d": "管理状态:", + "i18n_88c85a2506": "新增的节点(插件端)将自动", + "i18n_88e6615734": "解绑会检查数据关联性,同时将自动删除节点项目和脚本缓存信息,一般用于服务器无法连接且已经确定不再使用", + "i18n_88f5c7ac4a": "请选择排序字段", + "i18n_8900539e06": "写入大小", + "i18n_89050136f8": "发布后操作", + "i18n_891db2373b": "自动刷新", + "i18n_897d865225": "镜像数:", + "i18n_89944d6ccb": "标签限制为字母数字且长度 1-10", + "i18n_899dbd7b9a": "CPU型号", + "i18n_899fe0c5dd": "节点地址为插件端的 IP:PORT 插件端端口默认为:212", + "i18n_89a40a1a8b": "分发过程请求,非必填,GET请求", + "i18n_89cfb655e0": "容器名标签", + "i18n_89d18c88a3": "请输入文件任务名", + "i18n_89f5ca6928": "支持通配符", + "i18n_8a1767a0d2": "开启此选项后可以正常发布隐藏文件", + "i18n_8a3e316cd7": "不编码", + "i18n_8a414f832f": "集群地址", + "i18n_8a49e2de39": "QQ 邮箱配置", + "i18n_8a4dbe88b8": "点击进入节点管理", + "i18n_8a745296f4": "开机时间:", + "i18n_8aa25f5fbe": "发布类型", + "i18n_8ae2b9915c": "请填写第", + "i18n_8aebf966b2": "集群访问地址", + "i18n_8b1512bf3a": "如果端口", + "i18n_8b2e274414": "上次重载结果", + "i18n_8b3db55fa4": "集群ID:", + "i18n_8b63640eee": "账号被禁用", + "i18n_8b6e758e4c": "悬停到仪表盘上显示具体含义", + "i18n_8b73b025c0": "简单易用,但不支持密钥导出备份", + "i18n_8b83cd1f29": "要拉取的镜像名称", + "i18n_8ba971a184": "私人令牌", + "i18n_8ba977b4b7": "# 限制备份指定文件后缀(支持正则)", + "i18n_8bd3f73502": "节点密码", + "i18n_8be76af198": "163 邮箱配置", + "i18n_8be868ba1b": "类 10", + "i18n_8c0283435b": ":表示连续区间,例如在分上,表示2,3,4,5,6,7,8分", + "i18n_8c24b5e19c": "请使用应用扫码绑定令牌,然后输入验证码确认绑定才生效", + "i18n_8c2da7cce9": "没有任何证书", + "i18n_8c4db236e1": "请输入脚本标记,标记只能是字母或者数字长度需要小于 20 并且全局唯一", + "i18n_8c61c92b4b": "备份类型", + "i18n_8c66392870": "需要使用 ssh-keygen -m PEM -t rsa -b 4096 -C", + "i18n_8c67370ee5": "如果产物同步到文件中心,当前值会共享", + "i18n_8c7c7f3cfa": "服务端脚本", + "i18n_8c7ce1da57": "开启 dockerTag 版本递增后将在每次构建时自动将版本号最后一位数字同步为构建序号ID, 如:当前构建为第 100 次构建 testtag:1.0 -> testtag:1.100,testtag:1.0.release -> testtag:1.100.release。如果没有匹配到数字将忽略递增操作", + "i18n_8c7d19b32a": "允许执行的内存节点 (MEM) (0-3, 0,1)。 仅在 NUMA 系统上有效。", + "i18n_8cae9cb626": "浅色 idea", + "i18n_8ccbbb95a4": "请填写远程URL", + "i18n_8cd628f495": "所有的IP:", + "i18n_8d0fa2ee2d": "请输入端口号", + "i18n_8d1286cd2e": "没有任何分发日志", + "i18n_8d13037eb7": "状态消息:", + "i18n_8d202b890c": "批量触发器地址", + "i18n_8d3d771ab6": "编辑集群", + "i18n_8d5956ca2a": "以 yaml/yml 格式配置,scriptId 为项目路径下的脚本文件的相对路径或者服务端脚本模版ID,可以到服务端脚本模版编辑弹窗中查看 scriptId", + "i18n_8d5c1335b6": "容器名称数字字母,且长度大于1", + "i18n_8d62b202d9": "请选择要使用的文件", + "i18n_8d63ef388e": "暂停", + "i18n_8d6d47fbed": "# 在指定目录执行: ./ 项目目录 /root/ 特定目录 默认在 {'${jpom_agent_data_path}'}/script_run_cache ", + "i18n_8d6f38b4b1": "文件描述", + "i18n_8d90b15eaf": "# 宿主机文件上传到容器 /host:/container:true", + "i18n_8d92fb62a7": "请选择模板节点", + "i18n_8d9a071ee2": "导入", + "i18n_8da42dd738": "秒级别(默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])", + "i18n_8dbe0c2ffa": "占用空间:", + "i18n_8dc09ebe97": "获取", + "i18n_8dc8bbbc20": "文件系统", + "i18n_8de2137776": "集群任务", + "i18n_8e2ed8ae0d": "【独立分发】", + "i18n_8e331a52de": "只允许访问的 IP 地址", + "i18n_8e34aa1a59": "以此机器节点配置为模板", + "i18n_8e389298e4": "导出镜像", + "i18n_8e38d55231": "真的要彻底退出系统么?彻底退出将退出登录和清空浏览器缓存", + "i18n_8e54ddfe24": "启动", + "i18n_8e6184c0d3": "项目可能支持关联如下数据:", + "i18n_8e6a77838a": "请选择要分发到的机器节点", + "i18n_8e872df7da": "注意是整行不能包含空格", + "i18n_8e89763d95": "宿主机ip", + "i18n_8e8bcfbb4f": "确定要修剪对应的信息吗?修剪会自动清理对应的数据", + "i18n_8e9bd127fb": "需要提前为工作空间配置授权目录", + "i18n_8ea4c3f537": "从尾搜索、文件前100行、文件后100行", + "i18n_8ea93ff060": "节点脚本模版是存储在节点中的命令脚本用于在线管理一些脚本命令,如初始化软件环境、管理应用程序等", + "i18n_8ef0f6c275": "关闭 beta 计划", + "i18n_8f0bab9a5a": "在读取的日志文件数", + "i18n_8f0c429b46": "迁移操作不具有事务性质,如果流程被中断或者限制条件不满足可能产生冗余数据!!!!", + "i18n_8f36f2ede7": "工作空间名称:", + "i18n_8f3747c057": "服务名称", + "i18n_8f40b41e89": "过期时间:", + "i18n_8f7a163ee9": "快速安装插件端", + "i18n_8f8f88654f": "暂无节点信息", + "i18n_8fb7785809": "如果在生成私钥的过程中有加密,那么需要把加密密码填充到上面的密码框中", + "i18n_8fbcdbc785": "请输入别名码", + "i18n_8fd9daf8e9": "保证在内网中使用可以忽略 TLS 证书", + "i18n_8fda053c83": "写入次数", + "i18n_8ffded102f": "如果需要定时自动构建则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])", + "i18n_900c70fa5f": "警告", + "i18n_9014d6d289": "备份列表", + "i18n_90154854b6": "请输入host", + "i18n_901de97cdb": "方式请求接口参数传入到请求体 ContentType 请使用:text/plain", + + "i18n_903b25f64e": "未知状态", + "i18n_904615588b": "文件类型没有控制台功能", + "i18n_9057ac9664": "请选择触发类型", + "i18n_9065a208e8": "通过 URL 下载远程文件到项目文件夹,需要到对应的工作空间下授权目录配置中配置允许的 HOST 授权", + "i18n_906f6102a7": "重启成功", + "i18n_9086111cff": "关联工作空间 docker", + "i18n_90b5a467c1": "刷新目录", + "i18n_90c0458a4c": "导入备份", + "i18n_90eac06e61": "宿主机目录", + "i18n_912302cb02": "浏览器", + "i18n_9136e1859a": "Docker镜像", + "i18n_913ef5d129": "执行重启", + "i18n_916cde39c4": "所有参数将拼接成字符串以空格分隔形式执行脚本,需要注意参数顺序和未填写值的参数将自动忽略", + "i18n_916ff9eddd": "请输入昵称", + "i18n_91985e3574": "自动探测", + "i18n_91a10b8776": " 脚本库 ", + "i18n_920f05031b": "状态描述", + "i18n_922b76febd": "运行模式必填", + "i18n_923f8d2688": "发布后命令", + "i18n_9255f9c68f": "会话已经关闭[tail-file]", + "i18n_92636e8c8f": "跳过", + "i18n_9282b1e5da": "企业微信扫码", + "i18n_929e857766": "证书类型", + "i18n_92c6aa6db9": "如果您 SSH 机器中存在 docker 但是系统没有监测到,您需要到【配置管理】-", + "i18n_92dde4c02b": "广告投放", + "i18n_92e3a830ae": "帮助", + "i18n_92f0744426": "容器构建是指使用 docker 容器执行构建,这样可以达到和宿主机环境隔离不用安装依赖环境", + "i18n_92f3fdb65f": "仓库:", + "i18n_92f9a3c474": "切换语言后页面将自动刷新", + "i18n_9300692fac": "标记引用", + "i18n_9302bc7838": "请输入要检查的端口", + "i18n_930882bb0a": "个", + "i18n_9308f22bf6": "单个触发器地址中:第一个随机字符串为脚本ID,第二个随机字符串为 token", + "i18n_930fdcdf90": "配置名 (如:size)", + "i18n_9324290bfe": "如:key1", + "i18n_932b4b7f79": "注意:在每一个子表达式中优先级:", + "i18n_934156d92c": "创建分发项目", + "i18n_9341881037": "确定要取批量构建吗?注意:同时运行多个构建将占用较大的资源,请慎重使用批量构建,如果批量构建的数量超多构建任务队列等待数,构建任务将自动取消", + "i18n_935b06789f": "您还未执行操作", + "i18n_9362e6ddf8": "危险操作!!!", + "i18n_938dd62952": "执行路径", + "i18n_939d5345ad": "提交", + "i18n_93e1df604a": "机器分组", + "i18n_93e894325d": "批量启动", + "i18n_9402665a2c": " 持续搜索(对话框不会自动关闭,按 Enter 查找下一个,按 Shift-Enter 查找上一个)", + "i18n_9412eb8f99": "请填写平台地址", + "i18n_9443399e7d": " ,范围1970~2099,但是第7位不做解析,也不做匹配", + "i18n_94763baf5f": "可以到节点管理中的【插件端配置】=>【授权配置】修改", + "i18n_947d983961": "温馨提示", + "i18n_948171025e": "会话已经关闭[docker-log]", + "i18n_949934d97c": "超大", + "i18n_949a8b7bd2": "列设置", + "i18n_94aa195397": "证书文件", + "i18n_94ca71ae7b": "请选择要使用的证书", + "i18n_94d4fcca1b": "创建账号", + "i18n_952232ca52": "构建历史可能占有较多硬盘空间,建议根据实际情况配置保留个数", + "i18n_953357d914": "忽略校验 state", + "i18n_953ec2172b": "未重启成功:", + "i18n_954fb7fa21": "真的要删除项目么?删除项目不会删除项目相关文件奥,建议先清理项目相关文件再删除项目", + "i18n_956ab8a9f7": "配置文件吗?配置文件一旦创建不能通过管理页面删除的奥?", + "i18n_957c1b1c50": "集群节点", + "i18n_95a43eaa59": "创建人", + "i18n_95b351c862": "编辑", + "i18n_95c5c939e4": "可选择的列表和项目授权目录是一致的,即相同配置", + "i18n_95dbee0207": "远程下载安全HOST", + "i18n_96283fc523": "备份文件不存在", + "i18n_964d939a96": " 长名称:", + "i18n_969098605e": "环境变量是指配置在系统中的一些固定参数值,用于脚本执行时候快速引用。", + "i18n_96b78bfb6a": "请勿手动删除数据目录下面文件 !!!!", + "i18n_96c1c8f4ee": "灰绿 abcdef", + "i18n_96c28c4f17": "加入到哪个集群", + "i18n_96d46bd22e": "手动刷新统计", + "i18n_96e6f43118": "容器 runtime", + "i18n_974be6600d": "密码必须包含数字,字母,字符,且大于6位", + "i18n_977bfe8508": "标签(TAG)", + "i18n_979b7d10b0": "构建中断", + "i18n_97a19328a8": "立即开启", + "i18n_97cb3c4b2e": "工作空间环境变量用于构建命令相关", + "i18n_97d08b02e7": "网络端口测试", + "i18n_97ecc1bbe9": "输出流量", + "i18n_981cbe312b": "至", + "i18n_9829e60a29": "实时版本号", + "i18n_98357846a2": "表格视图才能使用批量操作功能", + "i18n_983f59c9d4": "验证码", + "i18n_9878af9db5": "请到【系统管理】-> 【资产管理】-> 【Docker管理】新增Docker并创建集群,或者将已存在的的 Docker 集群授权关联、分配到此工作空间", + "i18n_9880bd3ba1": "此工具用于检查 cron 表达式是否正确,以及计划运行时间", + "i18n_989f1f2b61": "真的要重启项目么?", + "i18n_98a315c0fc": "授权", + "i18n_98cd2bdc03": "表格视图才能使用同步配置功能", + "i18n_98d69f8b62": "工作空间", + "i18n_98e115d868": "运行中的定时任务", + "i18n_9914219dd1": "从头搜索", + "i18n_9932551cd5": "内存", + "i18n_993a5c7eee": "#配置说明:https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerCreate", + "i18n_99593f7623": "客户端ID", + "i18n_9964d6ed3f": "挂载", + "i18n_996dc32a98": "系统类型", + "i18n_9970ad0746": "主题", + "i18n_9971192b6a": "。如:http", + "i18n_9973159a4d": "匹配一个字符", + "i18n_998b7c48a8": "查看容器", + "i18n_99b3c97515": "小时级别", + "i18n_99cba05f94": "真的要删除 SSH 么?当前 ssh 关联的脚本在删除后均将失效", + "i18n_99d3e5c718": " 开始搜索", + "i18n_99f0996c0a": "请选择日志目录", + "i18n_9a00e13160": "单个触发器地址中:第一个随机字符串为项目ID(服务端),第二个随机字符串为 token", + "i18n_9a0c5b150c": "编辑 命令", + "i18n_9a2ee7044f": "变量值", + "i18n_9a436e2a53": "修剪具有指定标签的对象,多个使用逗号分隔", + "i18n_9a4b872895": "集群操作", + "i18n_9a56bb830e": "用户昵称", + "i18n_9a77f3523e": "镜像 tag", + "i18n_9a7b52fc86": "所有", + "i18n_9a8eb63daf": "配置工作空间权限", + "i18n_9ab433e930": "其他配置", + "i18n_9ac4765895": "一个整数值,表示此容器相对于其他容器的相对 CPU 权重。", + "i18n_9adf43e181": "正在构建数", + "i18n_9ae40638d2": "部署证书", + "i18n_9af372557e": "服务端端口", + "i18n_9aff624153": "监控", + "i18n_9b0bc05511": "在文件第 1 - 100 行中搜索", + "i18n_9b1c5264a0": "上传后", + "i18n_9b280a6d2d": "节点:", + "i18n_9b3e947cc9": "节点状态:", + "i18n_9b5f172ebe": "绑定集群", + "i18n_9b7419bc10": "QQ邮箱", + "i18n_9b74c734e5": "节点账号密码为插件端的账号密码,并非用户账号(管理员)密码", + "i18n_9b78491b25": "请输入授权路径,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径", + "i18n_9b7ada2613": " :范围:1~31,", + "i18n_9b9e426d16": "在读取的日志文件数:", + "i18n_9ba71275d3": ",请耐心等待暂时不用刷新页面,升级成功后会自动刷新", + "i18n_9baca0054e": "修改人", + "i18n_9bbb6b5b75": "真的要删除日志搜索么?", + "i18n_9bd451c4e9": "节点已经存在", + "i18n_9be8ff8367": "支持变量替换:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_9bf4e3c9de": "创建分发项目是指,全新创建一个属于节点分发到项目,创建成功后项目信息将自动同步到对应的节点中,修改节点分发信息也自动同步到对应的节点中", + "i18n_9bf5aa6672": "web socket 错误,请检查是否开启 ws 代理", + "i18n_9c19a424dc": "请输入原密码", + "i18n_9c2a917905": "搜索命令", + "i18n_9c2f1d3f39": "内的root拥有真正的root权限。", + "i18n_9c3a3e1b03": "父级不存在自动删除", + "i18n_9c3a5e1dad": "请到【系统管理】-> 【资产管理】-> 【机器管理】新增节点,或者将已新增的机器授权关联、分配到此工作空间", + "i18n_9c3c05d91b": "节点地址建议使用内网地址", + "i18n_9c55e8e0f3": "允许执行的 CPU(例如,0-3、0,1)。", + "i18n_9c66f7b345": "真的要删除机器么?删除会检查数据关联性", + "i18n_9c84cd926b": "新机器还需要绑定工作空间,因为我们建议将不同集群资源分配到不同的工作空间来管理", + "i18n_9c942ea972": "证书生成方式可以参考文档)来连接 docker 提升安全性", + "i18n_9c99e8bec9": "不填写则发布至项目的根目录", + "i18n_9cac799f2f": "选择分组名", + "i18n_9caecd931b": "字段", + "i18n_9cd0554305": "如果不需要保留较多构建历史信息可以到服务端修改构建相关配置参数", + "i18n_9ce5d5202a": "运行的Jar包:", + "i18n_9d577fe51b": "文件来源", + "i18n_9d5b1303e0": "创建集群会将尝试获取 docker 中集群信息,如果存在集群信息将自动同步集群信息到系统,反之不存在集群信息将自动创建 swarm 集群", + "i18n_9d7d471b77": "请选择节点角色", + "i18n_9d89cbf245": "分发名称", + "i18n_9dd62c9fa8": "终端命令无限制", + "i18n_9ddaa182bd": "插件端时间:", + "i18n_9de72a79fe": "查看文件", + "i18n_9e09315960": "重建", + "i18n_9e2e02ef08": "分发类型", + "i18n_9e4ae8a24f": "钉钉扫码", + "i18n_9e560a4162": "方式生成的 SSH key", + "i18n_9e5ffa068e": "基本信息", + "i18n_9e6b699597": "nanoCPUs 最小 1000000", + "i18n_9e78f02aad": "参数描述,参数描述没有实际作用,仅是用于提示参数的含义", + "i18n_9e96d9c8d3": "系统负载:", + "i18n_9e98fa5c0d": "文件名栏支持右键菜单", + "i18n_9ec961d8cb": "选择构建产物", + "i18n_9ee0deb3c8": "网络开了小差!请重试...:", + "i18n_9ee9d48699": "创建后不支持修改", + "i18n_9f01272a10": " 法律风险", + "i18n_9f0de3800b": "请填写仓库名称", + "i18n_9f52492fbc": "配置详情请参考配置示例", + "i18n_9f6090c819": "传入参数有:buildId、buildName、type、statusMsg、triggerTime", + "i18n_9f6fa346d8": "请输入 SSH 名称", + "i18n_9f70e40e04": "运行时间", + "i18n_9fb12a2d14": "发布后执行的命令(非阻塞命令),一般是启动项目命令 如:ps -aux | grep java, 支持变量替换:{'${ BUILD_ID }'}、{'${ BUILD_NAME }'}、{'${ BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID }'} ", + "i18n_9fb61a9936": "版本递增", + "i18n_9fc2e26bfa": "请选择项目", + "i18n_9fca7c455f": "登录时间", + "i18n_9febf31146": "请选择文件", + "i18n_9ff5504901": "传入环境变量有:buildId、buildName、type、statusMsg、triggerTime、buildNumberId、buildSourceFile", + "i18n_a001a226fd": "更新时间", + "i18n_a03c00714f": "批量关闭", + "i18n_a03ea1e864": "请选择分发到的节点", + "i18n_a04b7a8f5d": "单个触发器请求支持将参数解析为环境变量传入脚本执行,比如传入参数名为 abc=efg 在脚本中引入则为:{'${trigger_abc}'}", + "i18n_a050cbc36d": "您的浏览器版本太低,不支持该功能", + "i18n_a056d9c4b3": "选择脚本", + "i18n_a05c1667ca": "构建历史", + "i18n_a08cbeb238": "并且配置正确的环境变量", + "i18n_a09375d96c": "悬空", + "i18n_a093ae6a6e": "自动续期", + "i18n_a0a111cbbd": "分发项目-", + "i18n_a0a3d583b9": "总内存:", + "i18n_a0b9b4e048": "请输入客户端ID [clientId]", + "i18n_a0d0ebc519": "全局代理", + "i18n_a0e31d89ff": "一般建议 10 秒以上", + "i18n_a0f1bfad78": "数据目录大小包含:临时文件、在线构建文件、数据库文件等", + "i18n_a11cc7a65b": "请输入内容", + "i18n_a13d8ade6a": "关联节点数据是异步获取有一定时间延迟", + "i18n_a14da34559": "资源监控异常", + "i18n_a156349591": " 查看", + "i18n_a1638e78e8": "匹配包含 a 或者 b 的行", + "i18n_a17450a5ff": "选择压缩文件", + "i18n_a17b5ab021": "当前文件已经存在啦", + "i18n_a17bc8d947": "控制台日志只是启动项目输出的日志信息,并非项目日志。可以关闭控制台日志备份功能:", + "i18n_a189314b9e": "不发布", + "i18n_a1a3a7d853": "现成生成", + "i18n_a1b745fba0": "请输入备份名称", + "i18n_a1bd9760fc": "定时任务", + "i18n_a1c4a75c2d": "是一款开源软件您使用这个项目并感觉良好,或是想支持我们继续开发,您可以通过如下方式支持我们:", + "i18n_a1da57ab69": "支付宝转账", + "i18n_a1e24fe1f6": "用户时间", + "i18n_a1f58b7189": "参数{count}值", + "i18n_a1fb7f1606": "脚本管理", + "i18n_a20341341b": "显示前N行", + "i18n_a24d80c8fa": "项目启动,停止,重启,文件变动都将请求对应的地址", + "i18n_a25657422b": "变量名", + "i18n_a2a0f52afe": "不填将使用默认的 $HOME/.ssh 目录中的配置,使用优先级是:id_dsa>id_rsa>identity", + "i18n_a2ae15f8a7": "构建流程", + "i18n_a2e62165dc": "真的要保存当前配置吗?IP 授权请慎重配置奥( 授权是指只允许访问的 IP ),配置后立马生效 如果配置错误将出现无法访问的情况,需要手动恢复奥!!!", + "i18n_a2ebd000e4": "不做任何操作", + "i18n_a3296ef4f6": "全屏终端", + "i18n_a33a2a4a90": "服务端同步的脚本不能在此修改", + "i18n_a34545bd16": "构建参数,如:key1=values1&keyvalue2 使用 URL 编码", + "i18n_a34b91cdd7": "环境变量还可以用于仓库账号密码、ssh密码引用", + "i18n_a34c24719b": "开始执行任务", + "i18n_a35740ae41": "操作提示", + "i18n_a3751dc408": " :12点的每分钟执行", + "i18n_a37c573d7b": "可以是 Unix 时间戳、日期格式的时间戳或 Go 持续时间字符串(例如 10m、1h30m),相对于守护进程机器的时间计算。", + "i18n_a38ed189a2": "上传更新前请阅读更新日志里面的说明和注意事项并且", + "i18n_a39340ec59": "禁止命令", + "i18n_a396da3e22": "当前工作空间还没有项目并且也没有任何节点", + "i18n_a3d0154996": "文件状态", + "i18n_a3f1390bf1": "修改后如果有原始关联数据将失效,需要重新配置关联", + "i18n_a4006e5c1e": "创建备份", + "i18n_a421ec6187": "编辑环境变量", + "i18n_a4266aea79": "真的要删除此服务么?", + "i18n_a436c94494": "飞书扫码", + "i18n_a472019766": "节点Id", + "i18n_a497562c8e": "执行人", + "i18n_a4f5cae8d2": "开启状态", + "i18n_a4f629041c": "路径需要配置绝对路径", + "i18n_a50fbc5a52": "支持指定网卡名称来绑定:", + "i18n_a51cd0898f": "容器名称", + "i18n_a51d8375b7": "选择静态文件", + "i18n_a52a10123f": "如果升级失败需要手动恢复奥", + "i18n_a52aa984cd": "真的要删除权限组么?", + "i18n_a53d137403": "会话已经关闭[free-script]", + "i18n_a5617f0369": "SSH连接信息", + "i18n_a577822cdd": "保存并构建", + "i18n_a59d075d85": "自定义克隆深度,避免大仓库全部克隆", + "i18n_a5d550f258": "间隔时间", + "i18n_a5daa9be44": "上传前请检查包是否完整,否则可能出现更新后无法正常启动的情况!!", + "i18n_a5e9874a96": "请选择发布到哪个 docker 集群", + "i18n_a5f84fd99c": "非隐私", + "i18n_a6269ede6c": "管理节点", + "i18n_a62fa322b4": "证书将打包成 zip 文件上传到对应的文件夹", + "i18n_a637a42173": "选择的构建历史产物已经不存在啦", + "i18n_a63fe7b615": "分配到工作空间后还需要到关联中进行配置对应工作空间才能完美使用奥", + "i18n_a657f46f5b": "周", + "i18n_a66644ff47": "类 172", + "i18n_a66fff7541": "远程下载URL", + "i18n_a6bf763ede": "机器节点", + "i18n_a6fc9e3ae6": "上传文件", + "i18n_a74b62f4bb": "硬盘信息", + "i18n_a75a5a9525": "发布目录,构建产物上传到对应目录", + "i18n_a75b96584d": "服务Id", + "i18n_a75f781415": "服务端菜单", + "i18n_a7699ba731": "上传成功", + "i18n_a76b4f5000": "管理员数", + "i18n_a77cc03013": "如果引用脚本库需要提前将对应脚本分发到机器节点中才能正常使用", + "i18n_a795fa52cd": "彻底退出", + "i18n_a7a9a2156a": "请输入确认密码", + "i18n_a7c8eea801": "尝试自动续签成功", + "i18n_a7ddb00197": "阿里云企业邮箱 SSL", + "i18n_a805615d15": "type 的值有:startReady、pull、executeCommand、release、done、stop、success、error", + "i18n_a810520460": "密码", + "i18n_a823cfa70c": "容器标签", + "i18n_a84a45b352": "升级策略", + "i18n_a8754e3e90": "填写正确的IP地址", + "i18n_a87818b04f": "等待开始", + "i18n_a8920fbfad": "命令路径请修改为您的服务器中的实际路径", + "i18n_a89646d060": "创建工作空间后还需要在对应工作空间中分别管理对应数据", + "i18n_a8f44c3188": "账号是系统特定演示使用的账号", + "i18n_a90cf0796b": "信息:", + "i18n_a912a83e6f": "插件版本", + "i18n_a918bde61d": "您还未构建", + "i18n_a91ce167c1": "文件id", + "i18n_a9463d0f1a": "搜索模式,默认查看文件最后多少行,从头搜索指从指定行往下搜索,从尾搜索指从文件尾往上搜索多少行", + "i18n_a94feac256": "载速度根据网速来确定,如果网络不佳下载会较慢", + "i18n_a952ba273f": "联系时请备注来意", + "i18n_a9795c06c8": "没有SSH", + "i18n_a98233b321": "使用微软全家桶的推荐", + "i18n_a9886f95b6": "确定要删除此脚本库吗?", + "i18n_a9add9b059": "数据存储目录:", + "i18n_a9b50d245b": "不绑定", + "i18n_a9c52ffd40": "严格执行:", + "i18n_a9c999e0bd": "建议在上传后的脚本中对文件进行自定义更名,SSH 上传默认为:{'${FILE_ID}'}.{'${FILE_EXT_NAME}'}", + "i18n_a9de52acb0": "操作方法", + "i18n_a9eed33cfb": "如果版本相差大需要重新初始化数据来保证和当前程序里面字段一致", + "i18n_a9f94dcd57": "部署", + "i18n_aa53a4b93a": "没有任何网络接口信息", + "i18n_aa9236568f": "统计趋势", + "i18n_aabdc3b7c0": "项目路径", + "i18n_aac62bc255": "点击查看日志", + "i18n_aad7450231": "请输入选择绑定的集群", + "i18n_aadf9d7028": "用于下载远程文件来进行节点分发和文件上传", + "i18n_aaeb54633e": "重载", + "i18n_ab006f89e7": "手动删除", + "i18n_ab13dd3381": "自建 Gitlab 账号登录", + "i18n_ab3615a5ad": "下载安装包", + "i18n_ab3725d06b": "会话已经关闭", + "i18n_ab7f78ba4c": "空间ID(全匹配)", + "i18n_ab968d842f": "构建镜像尝试去更新基础镜像的新版本", + "i18n_ab9a0ee5bd": "文件夹路径 需要在仓库里面 dockerfile", + "i18n_ab9c827798": "没有docker集群", + "i18n_abb6b7260b": "如果多选 ssh 下面目录只显示选项中的第一项,但是授权目录需要保证每项都配置对应目录", + "i18n_abba4043d8": "从头搜索、文件前20行、文件后3行", + "i18n_abba4775e1": "命令参数", + "i18n_abd9ee868a": "网络模式:bridge、container:、host、container、none", + "i18n_abdd7ea830": "请输入新密码", + "i18n_abee751418": "容器Id: ", + "i18n_ac00774608": "第", + "i18n_ac0158db83": "任务id", + "i18n_ac2f4259f1": "新版本:", + "i18n_ac408e4b03": "请选择证书类型", + "i18n_ac5f3bfa5b": "选择要监控的项目,file 类型项目不可以监控", + "i18n_ac762710a5": "支持自定义排序字段:sort", + "i18n_ac783bca36": "真的要退出并切换账号登录么?", + "i18n_acb4ce3592": "请选择静态文件中的文件", + "i18n_acd5cb847a": "失败", + "i18n_ace71047a0": "请到【系统管理】-> 【资产管理】-> 【SSH管理】新增SSH,或者将已新增的SSH授权关联、分配到此工作空间", + "i18n_acf14aad3c": "不用挨个配置。配置后会覆盖之前的配置", + "i18n_ad209825b5": "请选择修剪类型", + "i18n_ad311f3211": "请选择仓库", + "i18n_ad35f58fb3": "占用空间", + "i18n_ad4b4a5b3b": "宿主", + "i18n_ad780debbc": "回滚策略", + "i18n_ad8b626496": "真的要删除构建历史记录么?", + "i18n_ad9788b17d": "异常恢复", + "i18n_ad9a677940": "指定 settings 文件打包 mvn -s xxx/settings.xml clean package", + "i18n_adaf94c06b": "执行结果", + "i18n_adbec9b14d": "创建备份信息", + "i18n_adcd1dd701": "返回列表", + "i18n_add91bb395": "逻辑节点", + "i18n_ae0d608495": "是否使用MFA", + "i18n_ae0fd9b9d2": "备份时间", + "i18n_ae12edc5bf": "点击复制文件路径", + "i18n_ae17005c0c": "未加入", + "i18n_ae35be7986": "token,全匹配", + "i18n_ae653ec180": "详细描述", + "i18n_ae6838c0e6": "节点分发", + "i18n_ae809e0295": "后缀,精准搜索", + "i18n_aeade8e979": "未初始化", + "i18n_aeb44d34e6": "一次性捐款赞助", + "i18n_aec7b550e2": "删除工作空间确认", + "i18n_aed1dfbc31": "中", + "i18n_aefd8f9f27": "请选择还原方式", + "i18n_af013dd9dc": "重启成功后会自动刷新", + "i18n_af0df2e295": "需要到 ssh 信息中配置允许编辑的文件后缀", + "i18n_af14cd6893": "请填写构建 DSL 配置内容,可以点击上方切换 tab 查看配置示例", + "i18n_af3a9b6303": "企业微信扫码账号登录", + "i18n_af427d2541": "数据更新时间", + "i18n_af4d18402a": "已经断开连接啦", + "i18n_af51211a73": "页面内容会出现滚动条", + "i18n_af708b659f": "内存:", + "i18n_af7c96d2b9": "同步机制采用容器 host 确定是同一个服务器(docker)", + "i18n_af924a1a14": "下载异常", + "i18n_af98c31607": "物理节点项目数量:", + "i18n_afa8980495": "请输入允许编辑文件的后缀及文件编码,不设置编码则默认取系统编码,示例:设置编码:txt{'@'}utf-8, 不设置编码:txt", + "i18n_afb9fe400b": "使用率:", + "i18n_b04070fe42": "选择代理类型", + "i18n_b04209e785": "关联数据:", + "i18n_b05345caad": "所有者", + "i18n_b07a33c3a8": "请选择分发节点", + "i18n_b0b9df58fd": "SSH节点", + "i18n_b0fa44acbb": "占用率:", + "i18n_b10b082c25": "的值有:stop、beforeStop、start、beforeRestart", + "i18n_b1192f8f8e": "真的取消当前分发吗?", + "i18n_b11b0c93fa": "如果在 Linux 中实际运行内存可能和您直接使用 free -h 命令查询到 free 和 total 字段计算出数值相差过大那么此时就是您当前服务器中的交换内存引起的", + "i18n_b12d003367": "隐私字段", + "i18n_b153126fc2": "请输入工作空间名称", + "i18n_b15689296a": "风险提醒", + "i18n_b15d91274e": "关闭", + "i18n_b166a66d67": "确定要将此数上移吗?", + "i18n_b17299f3fb": "插件端进程ID:", + "i18n_b1785ef01e": "节点名称", + "i18n_b186c667dc": "分发过程请求对应的地址,开始分发,分发完成,分发失败,取消分发", + "i18n_b188393ea7": "发布的SSH", + "i18n_b1a09cee8e": "清空还原", + "i18n_b1dae9bc5c": "管理员", + "i18n_b28836fe97": "分发 ID 等同于项目 ID", + "i18n_b28c17d2a6": " (MEM) (0-3, 0,1)。 仅在 NUMA 系统上有效。", + "i18n_b29fd18c93": "请选择指定发布的项目", + "i18n_b2f296d76a": "5分钟", + "i18n_b30d07c036": "批量关闭启动", + "i18n_b328609814": "管理员拥有:管理服务端的部分权限", + "i18n_b339aa8710": "表格", + "i18n_b33c7279b3": "认证方式", + "i18n_b3401c3657": "容器目录", + "i18n_b341f9a861": "任务时间", + "i18n_b343663a14": "清空发布是指在上传新文件前,会将项目文件夹目录里面的所有文件先删除后再保存新文件", + "i18n_b36e87fe5b": "不执行,但是编译测试用例 mvn clean package -DskipTests", + "i18n_b37b786351": "分组名", + "i18n_b384470769": "同步缓存", + "i18n_b38d6077d6": "登录IP", + "i18n_b38d7db9b0": "下载构建日志,如果按钮不可用表示日志文件不存在,一般是构建历史相关文件被删除", + "i18n_b3913b9bb7": "请输入构建环境变量:xx=abc 多个变量回车换行即可", + "i18n_b399058f25": "强大安全的密码管理付费应用", + "i18n_b39909964f": "请输入邮箱账号", + "i18n_b3b1f709d4": "剔除", + "i18n_b3bda9bf9e": "请选择工作空间", + "i18n_b3ef35a359": "源仓库", + "i18n_b3f9beb536": ":3~18分,每5分钟执行一次,即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……", + "i18n_b3fe677b5f": "失败率", + "i18n_b408105d69": "密码字段和密钥字段在编辑的时候不会返回,如果需要重置或者清空就请点我", + "i18n_b437a4d41d": "也支持 URL 参数格式:test_par=123abc&test_par2=abc21", + "i18n_b44479d4b8": "可用标签", + "i18n_b4750210ef": "集群修改时间:", + "i18n_b499798ec5": "禁用分发节点", + "i18n_b4a8c78284": "选择工作空间", + "i18n_b4c83b0b56": "仓库账号", + "i18n_b4dd6aefde": "会话已经关闭[script-console]", + "i18n_b4e2b132cf": "插件端运行端口默认使用:", + "i18n_b4fc1ac02c": "取消构建", + "i18n_b4fd7afd31": "个性配置", + "i18n_b513f53eb4": "超时时间 单位秒", + "i18n_b515d55aab": "可以通过证书管理中提前上传或者点击后面选择证书去选择/导入证书", + "i18n_b53dedd3e0": "发布前执行的命令(非阻塞命令),一般是关闭项目命令", + "i18n_b55f286cba": "载前请阅读更新日志里面的说明和注意事项并且", + "i18n_b56585aa18": "配置后可以控制想要在某个时间段禁止用户操作某些功能,优先判断禁用时段", + "i18n_b57647c5aa": "真的要解绑脚本关联的节点么?", + "i18n_b57ecea951": "已经运行时间:", + "i18n_b5a1e1f2d1": "触发类型:", + "i18n_b5a6a07e48": "周二", + "i18n_b5b51ff786": "上传 SQL 文件", + "i18n_b5c291805e": "初始化系统", + "i18n_b5c3770699": "控制台", + "i18n_b5c5078a5d": "所有的IPV4列表", + "i18n_b5ce5efa6e": "集群服务", + "i18n_b5d0091ae3": "构建ID", + "i18n_b5d2cf4a76": "当目标工作空间已经存在 脚本 时候将自动同步 脚本内容、默认参数、自动执行、描述", + "i18n_b5fdd886b6": "全屏查看日志", + "i18n_b60352bc4f": "虚拟", + "i18n_b6076a055f": "登录失败", + "i18n_b61a7e3ace": "脚本名称:", + "i18n_b63c057330": "真的要删除操作监控么?", + "i18n_b650acd50b": "恢复默认名称", + "i18n_b6728e74a4": "运行目录:", + "i18n_b6a828205d": "缓存构建", + "i18n_b6afcf9851": "禁止命令是不允许在终端执行的命令,多个逗号隔开。(超级管理员没有任何限制)", + "i18n_b6c9619081": "端口:", + "i18n_b6e8fb4106": "平台登录", + "i18n_b6ee682dac": "插件数:", + "i18n_b714160f52": "分发项目 ID", + "i18n_b71a7e6aab": "本地命令", + "i18n_b7579706a3": "校验", + "i18n_b7c139ed75": "如果项目目录较大或者涉及到深目录,建议关闭扫描避免获取项目目录扫描过长影响性能", + "i18n_b7cfa07d78": "确认绑定", + "i18n_b7df1586a9": "当目标工作空间已经存在节点时候将自动同步 docker 仓库配置信息", + "i18n_b7ea5e506c": "系统信息", + "i18n_b7ec1d09c4": "服务ID", + "i18n_b7f770d80b": "需要先安装依赖 npm i && npm run build", + "i18n_b8545de30e": "请至少选择 1 个节点", + "i18n_b85b213579": "发件人名称", + "i18n_b86224e030": "节点状态", + "i18n_b87c9acca3": "真的要强制退出集群吗?", + "i18n_b8915a4933": "真的关闭当前用户的两步验证么?", + "i18n_b8ac664d98": "勾选数据表", + "i18n_b90a30dd20": "此处不填不会修改密码", + "i18n_b91961bf0b": "传入参数有:projectId、projectName、type、result", + "i18n_b922323119": "镜像标签,如:key1=value1&key2=value2", + "i18n_b939d47e23": "公钥", + "i18n_b953d1a8f1": "不能关闭了", + "i18n_b96b07e2bb": "仅修剪未使用和未标记的镜像", + "i18n_b9a4098131": "触发器地址", + "i18n_b9af769752": "镜像名称必填", + "i18n_b9b176e37a": "请选择脚本", + "i18n_b9bcb4d623": "插件:", + "i18n_b9c1616fd5": "SSH方式连接的 docker 不建议用于容器构建(SSH 方式用于构建非常不稳定)", + "i18n_b9c4cf7483": " 全部替换", + "i18n_b9c52d9a85": "文件名:", + "i18n_ba17b17ba2": "没有任何SSH脚本命令", + "i18n_ba1f68b5dd": "这样使得", + "i18n_ba20f0444c": "强制删除", + "i18n_ba311d8a6a": "脚本", + "i18n_ba3a679655": "以 yaml/yml 格式配置", + "i18n_ba452d57f2": "使用率最大的分区:", + "i18n_ba52103711": "剩余 inode 数", + "i18n_ba619a0942": "默认构建错误将自动忽略隐藏文件", + "i18n_ba6e91fa9e": "权限", + "i18n_ba6ea3d480": "页面全屏,高度 100%。局部区域可以滚动", + "i18n_ba8d1dca4a": "注意:", + "i18n_baafe06808": "安全组规则", + "i18n_bab17dc6b1": "选择进程名", + "i18n_baef58c283": "请输入标签名 字母数字 长度 1-10", + "i18n_baefd3db91": "授权可以直接访问的目录,多个回车换行即可", + "i18n_bb316d9acd": "下载速度根据网速来确定,如果网络不佳下载会较慢", + "i18n_bb4409015b": "机器 ssh 名", + "i18n_bb4740c7a7": "执行 命令", + "i18n_bb5aac6004": "构建产物同步到文件中心保留天数", + "i18n_bb667fdb2a": "未报警", + "i18n_bb7eeae618": "仅统计:", + "i18n_bb8d265c7e": "版本需要大于 18", + "i18n_bb9a581f48": "登录成功,需要验证 MFA", + "i18n_bb9ef827bf": "禁止访问", + "i18n_bba360b084": "真的要删除对应的触发器吗?", + "i18n_bbbaeb32fc": "机器延迟", + "i18n_bbcaac136c": "表中的错误数据吗?", + "i18n_bbd63a893c": "自动检测服务端所在服务器中是否存在 docker,如果存在将自动新增到列表中", + "i18n_bbf2775521": "镜像名称", + "i18n_bc2c23b5d2": "修剪操作会删除相关数据,请谨慎操作。请您再确认本操作后果后再使用", + "i18n_bc2f1beb44": "真的要解锁用户么?", + "i18n_bc4b0fd88a": "网络 Reachable 测试", + "i18n_bc8752e529": "分发项目", + "i18n_bcaf69a038": "请选择一个节点", + "i18n_bcc4f9e5ca": "如", + "i18n_bcf48bf7a8": "授权 url", + "i18n_bcf83722c4": "变量描述", + "i18n_bd0362bed3": "新增分组", + "i18n_bd49bc196c": "编辑项目", + "i18n_bd4e9d0ee2": "原始名:", + "i18n_bd5d9b3e93": "使用哪个 docker 构建,填写 docker 标签( 标签在 docker 编辑页面配置) 默认查询可用的第一个,如果tag 查询出多个将依次构建", + "i18n_bd6c436195": "请输入脚本描述", + "i18n_bd7043cae3": "远程下载", + "i18n_bd7c8c96bc": "手动上传", + "i18n_bda44edeb5": "不能操作", + "i18n_bdc1fdde6c": "beta计划:", + "i18n_bdd4cddd22": "将还原【", + "i18n_bdd87b63a6": "微信二维码", + "i18n_bdd9d38d7e": "列宽", + "i18n_be166de983": "软链的项目", + "i18n_be1956b246": "深色2 blackboard", + "i18n_be2109e5b1": "确定要重置用户密码吗?", + "i18n_be24e5ffbe": "Java 项目(java -jar xxx)", + "i18n_be28f10eb6": "请选择发布的一级目录和填写二级目录", + "i18n_be381ac957": "请选择要使用的仓库", + "i18n_be3a4d368e": "分发中、2:分发结束、3:已取消、4:分发失败", + "i18n_be4b9241ec": "默认状态码为 200 表示执行成功", + "i18n_be5b6463cf": "语法参考", + "i18n_be5fbbe34c": "保存", + "i18n_beafc90157": "分发到机器节点中的脚本库在节点脚本支持使用 G{'@'}(\"xxxx\")格式来引用,当存在引用时系统会自动替换引用脚本库中的脚本内容", + "i18n_bebcd7388f": "加载构建数据中", + "i18n_bec98b4d6a": "状态:", + "i18n_becc848a54": "私钥文件绝对路径(绝对路径前面新增 file", + "i18n_bef1065085": "Chrome 扩展", + "i18n_bf0e1e0c16": "输入仓库名称或者仓库路径进行{slot1}", + "i18n_bf77165638": "您确定要重启当前容器吗?", + "i18n_bf7da0bf02": "新密码", + "i18n_bf91239ad7": "命令描述", + "i18n_bf93517805": "下列配置信息仅在当前浏览器生效", + "i18n_bf94b97d1a": "修改时间:", + "i18n_bfacfcd978": "将取消自动加载环境变量", + "i18n_bfc04cfda7": "分支", + "i18n_bfda12336c": "搜索查看", + "i18n_bfe68d5844": "链接", + "i18n_bfe8fab5cd": "需要配置授权目录(授权才能正常使用发布),授权目录主要是用于确定可以发布到哪些目录中", + "i18n_bfed4943c5": "参数值", + "i18n_c00fb0217d": "请填写用户昵称", + "i18n_c03465ca03": "禁用数量", + "i18n_c0996d0a94": " :每周一和周二的11:59执行", + "i18n_c0a9e33e29": "请选择构建对应的分支,必选", + "i18n_c0d19bbfb3": "请输入 key 的值", + "i18n_c0d38f475f": "软内存", + "i18n_c0d5d68f5f": "忽略", + "i18n_c0e498a259": "点击图标查看关联的所有任务", + "i18n_c0f4a31865": "逻辑删除", + "i18n_c11eb9deff": "文件MD5", + "i18n_c12ba6ff43": "我们有权利追诉破坏开源并因此获利的团队个人的全部违法所得,也欢迎给我们提供侵权线索。", + "i18n_c163613a0d": "如果当前集群还存在可能出现数据不一致问题奥", + "i18n_c1690fcca5": "导入证书", + "i18n_c16ab7c424": "】 吗?注意:取消/停止构建不一定能正常关闭所有关联进程", + "i18n_c1786d9e11": "节点地址", + "i18n_c17aefeebf": "系统名:", + "i18n_c18455fbe3": "授权信息错误", + "i18n_c195df6308": "异常", + "i18n_c1af35d001": "构建产物", + "i18n_c1b72e7ded": "为变量名称", + "i18n_c23fbf156b": "未选择ssh", + "i18n_c26e6aaabb": "擅自修改或者删除版权信息有法律风险", + "i18n_c2add44a1d": "一些例子:", + "i18n_c2b2f87aca": "脚本孤独数据", + "i18n_c2ee58c247": "构建命令", + "i18n_c2f11fde3a": "初始化系统账户", + "i18n_c31ea1e3c4": "没有任何操作日志", + "i18n_c325ddecb1": "CPU 周期的长度,以微秒为单位。", + "i18n_c32e7adb20": "请输入远程下载安全HOST,回车支持输入多个路径,示例 https://www.test.com 等", + "i18n_c34175dbef": "控制台日志备份路径: ", + "i18n_c3490e81bf": "重启创建之前会自动将之前的容器删除掉", + "i18n_c34f1dc2b9": "参数中的 id 、token 和触发构建一致", + "i18n_c3512a3d09": "请选选择类型", + "i18n_c35c1a1330": "排序值", + "i18n_c360e994db": "排序", + "i18n_c36ab9a223": "为 docker bridge 上的容器创建一个新的网络堆栈", + "i18n_c37ac7f024": "清除代码", + "i18n_c3aeddb10d": "当前工作空间还没有逻辑节点不能创建节点分发奥", + "i18n_c3f28b34bb": "集群名称", + "i18n_c446efd80d": "编辑 Script", + "i18n_c4535759ee": "系统提示", + "i18n_c46938460b": "系统使用 docker http 接口实现和 docker 通讯和管理,但是默认", + "i18n_c469afafe0": "您确定要删除当前容器吗?", + "i18n_c494fbec77": "手动新增", + "i18n_c4a61acace": "命令?", + "i18n_c4b5d36ff0": "节点状态会自动识别服务器中是否存在 java 环境,如果没有 Java 环境不能快速安装节点", + "i18n_c4cfe11e54": "如果上传的压缩文件是否自动解压 支持的压缩包类型有 tar", + "i18n_c4e0c6b6fe": "筛选项目", + "i18n_c4e2cd2266": "还需要对相关数据都操作后才能达到预期排序", + "i18n_c5099cadcd": "插件数", + "i18n_c53021f06d": "填写【xxx", + "i18n_c530a094f9": "构建方式:", + "i18n_c538b1db4a": "软链项目(类似于项目副本使用相关路径的文件)", + "i18n_c583b707ba": "个参数的描述", + "i18n_c5a2c23d89": "非全屏", + "i18n_c5aae76124": "绑定 SSH", + "i18n_c5bbaed670": "状态码错误", + "i18n_c5c3583bfc": "发送验证码", + "i18n_c5c69827c5": "等网络端口限制", + "i18n_c5de93f9c7": "需要手动确认", + "i18n_c5e7257212": "当前节点ID:", + "i18n_c5f9a96133": "系统默认将对 demo 账号限制很多权限。非演示场景不建议使用 demo 账号", + "i18n_c600eda869": "命令文件将在 {'${数据目录}'}/script/xxxx.sh、bat 执行", + "i18n_c618659cea": "自定义分支通配表达式", + "i18n_c6209653e4": "SMTP 服务器域名", + "i18n_c68dc88c51": "请输入监控名称", + "i18n_c6a3ebf3c4": "接收大小", + "i18n_c6e4cddba0": "请选择监控的功能", + "i18n_c6f1c6e062": "目录不能编辑", + "i18n_c6f6a9b234": "迁移到其他工作空间", + "i18n_c704d971d6": "请填写构建和产物", + "i18n_c7099dabf6": "正在上传文件", + "i18n_c71a67ab03": "分发后", + "i18n_c75b14a04e": "监控频率可以到服务端配置文件中修改", + "i18n_c75d0beca8": "开启页面操作引导、导航", + "i18n_c7689f4c9a": "这里构建命令最终会在服务器上执行。如果有多行命令那么将", + "i18n_c76cfefe72": "端口", + "i18n_c7c4e4632f": ",更新期间允许的失败率", + "i18n_c7e0803a17": "密码只会出现一次,关闭窗口后无法再次查看密码", + "i18n_c806d0fa38": "压缩包", + "i18n_c83752739f": "支持的字段可以通过接口返回的查看", + "i18n_c840c88b7c": "真的要清空 【", + "i18n_c84ddfe8a6": "执行日志", + "i18n_c8633b4b77": "通过私人令牌导入仓库", + "i18n_c87bd94cd7": "镜像Id: ", + "i18n_c889b9f67d": "新增关联项目", + "i18n_c89e9681c7": "临时文件占用空间", + "i18n_c8a2447aa9": "加入", + "i18n_c8b2aabc07": "文件中如果不存在:MemAvailable,那么 MemAvailable = MemFree+Active(file)+Inactive(file)+SReclaimable,所以本系统 中内存占用计算方式:内存占用=(total-(MemFree+Active(file)+Inactive(file)+SReclaimable))/total", + "i18n_c8c452749e": "选择 SQL 文件", + "i18n_c8c45e8467": "请根据自身项目启动时间来配置", + "i18n_c8c6e37071": "温馨提醒", + "i18n_c8ce4b36cb": "重命名", + "i18n_c90a1f37ce": "节点id", + "i18n_c96b442dfb": "通用邮箱 SSL", + "i18n_c96f47ec1b": "异步请求不能保证有序性", + "i18n_c972010694": "产物目录", + "i18n_c9744f45e7": "否", + "i18n_c97e6e823a": "重新启动容器,除非它已被停止", + "i18n_c983743f56": "总内存", + "i18n_c996a472f7": "每天0/12点刷新一次", + "i18n_c99a2f7ed8": "启动命令", + "i18n_c9b0f8e8c8": "真的要删除", + "i18n_c9b79a2b4f": "全局代理配置后将对服务端的网络生效,代理实现方式:ProxySelector", + "i18n_c9daf4ad6b": "多线程", + "i18n_ca32cdfd59": "内存使用率:", + "i18n_ca69dad8fc": "流程执行完脚本后,输出的内容最后一行必须为:running", + "i18n_ca774ec5b4": "上次构建基于 commitId:", + "i18n_caa9b5cd94": "需要将标签值配置到构建 DSL 中的", + "i18n_cab7517cb4": "节点地址:", + "i18n_cabdf0cd45": "选择产物", + "i18n_cac26240b5": "容器日志", + "i18n_cac6ff1d82": "批量获取构建状态地址", + "i18n_cad01fe13c": "黑白 ambiance-mobile", + "i18n_caf335a345": "java信息", + "i18n_cb09b98416": "个性配置区", + "i18n_cb156269db": "单位秒,最小值 1 秒", + "i18n_cb25f04b46": "在文件最后 3 行中搜索", + "i18n_cb28aee4f0": "节点分发【暂不支持迁移】", + "i18n_cb46672712": "日志阅读 【暂不支持迁移】", + "i18n_cb93a1f4a5": "、root、manager", + "i18n_cb951984f2": "已存在", + "i18n_cb9b3ec760": "批量触发参数 BODY json: [ { \"id\":\"1\", \"token\":\"a\",\"action\":\"status\" } ]", + "i18n_cbc44b5663": "执行时间结束", + "i18n_cbcc87b3d4": "、名称、版权等", + "i18n_cbce8e96cf": "证书信息", + "i18n_cbdc4f58f6": "请输入机器的名称", + "i18n_cbdcabad50": "释放", + "i18n_cbee7333e4": "本地命令是指,在服务端本地执行多条命令来实现发布", + "i18n_cc3a8457ea": "节点状态是异步获取有一定时间延迟", + "i18n_cc42dd3170": "开启", + "i18n_cc51f34aa4": "编辑服务", + "i18n_cc5dccd757": "工作空间中逻辑节点中脚本模版数量:", + "i18n_cc617428f7": "隐私变量是指一些密码字段或者关键密钥等重要信息,隐私字段只能修改不能查看(编辑弹窗中无法看到对应值)。 隐私字段一旦创建后将不能切换为非隐私字段", + "i18n_cc637e17a0": "产物:", + "i18n_cc92cf1e25": "请填写产物目录", + "i18n_cc9a708364": "状态码错误 ", + "i18n_cca4454cf8": "请输入公告内容", + "i18n_ccb2fdd838": "切换工作空间", + "i18n_ccb91317c5": "命令内容", + "i18n_ccea973fc7": "当前节点地址:", + "i18n_cd1aedc667": "在 设置 --> 应用 --> 生成令牌", + "i18n_cd649f76d4": "时间范围", + "i18n_cd998f12fa": "当目标工作空间已经存在节点时候将自动同步节点授权信息、代理配置信息", + "i18n_cda84be2f6": "操作日志", + "i18n_cdc478d90c": "系统名", + "i18n_ce043fac7d": "当前工作空间还没有SSH", + "i18n_ce07501354": "点击数字查看运行中的任务", + "i18n_ce1ecd8a5b": "还有更多相关依赖开源组件", + "i18n_ce23a42b47": "任务名", + "i18n_ce40cd6390": "关联脚本", + "i18n_ce559ba296": "运行命令", + "i18n_ce7e6e0ea9": "当前配置仅对选择的工作空间生效,其他工作空间需要另行配置", + "i18n_ce84c416f9": "请输入用户信息 url [userInfoUri]", + "i18n_ced3d28cd1": "不扫描", + "i18n_ceee1db95a": "容器端口", + "i18n_ceffe5d643": "两步验证应用", + "i18n_cf38e8f9fd": "当前为节点分发的授权路径配置", + "i18n_cfa72dd73a": "请输入要检查的 cron 表达式", + "i18n_cfb00269fd": "执行脚本", + "i18n_cfbb3341d5": "当前登录的账号是:", + "i18n_cfd482e5ef": "暂无数据,请先新增节点项目数据", + "i18n_cfeea27648": "创建文件 /xxx/xxx/xxx", + "i18n_cfeff30d2c": "目录:", + "i18n_d00b485b26": "回滚", + "i18n_d0132b0170": "还原过程中不能操作哦...", + "i18n_d02a9a85df": "请选择报警联系人", + "i18n_d047d84986": "个 / 共", + "i18n_d0874922f0": "绑定指定目录可以在线管理,同时构建 ssh 发布目录也需要在此配置", + "i18n_d0a864909b": "方式生成公私钥", + "i18n_d0b2958432": "版本号", + "i18n_d0b7462bdc": "此编辑仅能编辑当前 SSH 在此工作空间的名称信息", + "i18n_d0be2fcd05": ":表示间隔时间,例如在分上,表示每两分钟,同样*可以使用数字列表代替,逗号分隔", + "i18n_d0c06a0df1": "请输入验证码", + "i18n_d0c879f900": "上传前", + "i18n_d0eddb45e2": "私钥", + "i18n_d0f53484dc": "脚本ID:", + "i18n_d1498d9dbf": "构建备注", + "i18n_d159466d0a": "关联数据名", + "i18n_d175a854a6": "构建目录", + "i18n_d17eac5b5e": "我要加入", + "i18n_d18d658415": "脚本ID", + "i18n_d19bae9fe0": "插件端安装并启动成功后将主动上报节点信息,如果上报的 IP+PROP 能正常通讯将新增节点信息", + "i18n_d1aa9c2da9": "子网掩码:", + "i18n_d1b8eaaa9e": "需要输入验证码,确认绑定后才生效奥", + "i18n_d1f0fac71d": "没有选择节点不能保存脚本", + "i18n_d1f56b0a7e": "传入参数有:monitorId、monitorName、nodeId、nodeName、projectId、projectName、title、content、runStatus", + "i18n_d263a9207f": "支持 html 格式", + "i18n_d27cf91998": "参考地址:", + "i18n_d2cac1245d": "是否将【", + "i18n_d2e2560089": "文件名称", + "i18n_d2f484ff7e": "。如果输出最后一行不是预期格式项目状态将是未运行", + "i18n_d2f4a1550a": "没有任何节点脚本", + "i18n_d301fdfc20": "开启自动创建用户后第一次登录仅自动创建账号,还需要管理员手动分配权限组", + "i18n_d30b8b0e43": "未构建", + "i18n_d31d625029": "加入 beta 计划可以及时获取到最新的功能、一些优化功能、最快修复 bug 的版本,但是 beta 版也可能在部分新功能上存在不稳定的情况。", + "i18n_d324f8b5c9": " 替换", + "i18n_d35a9990f4": "已无更多节点项目,请先创建项目", + "i18n_d373338541": "支持IP 地址、域名、主机名", + "i18n_d3ded43cee": "查看构建日志", + "i18n_d3e480c8c0": "失败次数", + "i18n_d3fb6a7c83": ",2 秒后将自动跳转到登录页面", + "i18n_d40b511510": "证书", + "i18n_d438e83c16": "项目分组", + "i18n_d4744ce461": "还没有配置权限组,不能创建用户", + "i18n_d47ea92b3a": "编辑证书", + "i18n_d4aea8d7e6": "执行次数", + "i18n_d4e03f60a9": "插件端启动的时候检查项目状态,如果项目状态是未运行则尝试执行启动项目", + "i18n_d5269713c7": "表示构建产物为文件夹时将打包为", + "i18n_d57796d6ac": " :范围:0~59", + "i18n_d584e1493b": "搜索ssh名称", + "i18n_d58a55bcee": "关", + "i18n_d5a73b0c7f": "上传", + "i18n_d5c2351c0e": "请选择可以执行的星期", + "i18n_d5c68a926e": "执行顺序", + "i18n_d5d46dd79b": "分钟级别", + "i18n_d615ea8e30": "选择升级文件", + "i18n_d61af4e686": "断点/分片别名下载", + "i18n_d61b8fde35": "切换集群", + "i18n_d64cf79bd4": "确认要加入 beta 计划吗?", + "i18n_d65d977f1d": "填写运行参数", + "i18n_d679aea3aa": "运行中", + "i18n_d6937acda5": "项目存储的文件夹,jar 包存放的文件夹", + "i18n_d6a5b67779": "系统进程", + "i18n_d6cdafe552": "会话已经关闭[project-console]", + "i18n_d6eab4107a": "Java 项目(java -jar Springboot war)【不推荐】", + "i18n_d72471c540": "浏览器标识", + "i18n_d731dc9325": "时间戳:", + "i18n_d7471c0261": "请选择执行节点", + "i18n_d75c02d050": "停止项目", + "i18n_d7ac764d3a": "分发间隔时间 (顺序重启、完整顺序重启)方式才生效", + "i18n_d7ba18c360": "分发节点是指在编辑完脚本后自动将脚本内容同步节点的脚本中", + "i18n_d7bebd0e5e": "状态操作请到控制台中控制", + "i18n_d7c077c6f6": "命令日志", + "i18n_d7cc44bc02": "用户资料", + "i18n_d7d11654a7": "不存在", + "i18n_d7ec2d3fea": "名称", + "i18n_d7ee59f327": "私钥,不填将使用默认的 $HOME/.ssh 目录中的配置。支持配置文件目录:file:", + "i18n_d7ef19d05b": "还没有配置模板节点", + "i18n_d81bb206a8": "无", + "i18n_d82ab35b27": "文件前N行", + "i18n_d82b19148f": "请选择要同步授权的机器节点", + "i18n_d83aae15b5": "在线构建文件主要保存,仓库文件,构建历史产物等。不支持主动清除,如果文件占用过大可以配置保留规则和对单个构建配置是否保存仓库、产物文件等", + "i18n_d84323ba8d": "仓库自动迁移后可能会重复存在请手动解决", + "i18n_d87940854f": "计划次数", + "i18n_d87f215d9a": "卡片", + "i18n_d88651584f": "剩余空间", + "i18n_d8a36a8a25": "编辑 Docker 集群", + "i18n_d8bf90b42b": "其他用户可以配置权限解除限制", + "i18n_d8c7e04c8e": "信息", + "i18n_d8db440b83": "您需要根据您业务情况来评估是否可以加入 beta。", + "i18n_d911cffcd5": "下载地址", + "i18n_d921c4a0b6": "真的要删除“", + "i18n_d926e2f58e": "取消任务", + "i18n_d937a135b9": "浅色 eclipse", + "i18n_d94167ab19": "网络端口", + "i18n_d9435aa802": "解析模式", + "i18n_d9531a5ac3": "还没有配置权限组不能创建用户奥", + "i18n_d9569a5d3b": "多IP可以使用", + "i18n_d9657e2b5f": "请输入项目文件夹", + "i18n_d9ac9228e8": "创建", + "i18n_d9c28e376c": "功能管理", + "i18n_da1abf0865": "验证码 6 为纯数字", + "i18n_da1cb76e87": "请输入脚本内容", + "i18n_da317c3682": "【推荐】使用快速安装方式导入机器并自动新增逻辑节点", + "i18n_da4495b1b4": "邮箱:", + "i18n_da509a213f": "工作空间用于隔离数据,工作空间下面可以有不同数据,不同权限,不同菜单等来实现权限控制", + "i18n_da671a4d16": "微信:", + "i18n_da79c2ec32": "配置示例", + "i18n_da89135649": "企业服务", + "i18n_da8cb77838": "在线升级", + "i18n_da99dbfe1f": "分发状态", + "i18n_dab864ab72": "快速绑定", + "i18n_dabdc368f5": "请选择分配类型", + "i18n_dacc2e0e62": "硬件硬盘", + "i18n_dadd4907c2": "】目录,", + "i18n_daf783c8cd": "分", + "i18n_db06c78d1e": "测试", + "i18n_db094db837": "修改变量值地址", + "i18n_db2d99ed33": "还未配置集群地址,不能切换集群", + "i18n_db4470d98d": "报警状态", + "i18n_db4b998fbd": "Oauth2 使用提示", + "i18n_db5cafdc67": "真的要解绑节点么?", + "i18n_db686f0328": "公钥,不填将使用默认的 $HOME/.ssh 目录中的配置。支持配置文件目录:file:", + "i18n_db709d591b": "不同步", + "i18n_db732ecb48": "延迟", + "i18n_db81a464ba": "执行构建时会生成一个容器来执行,构建结束后会自动删除对应的容器", + "i18n_db9296212a": "定时构建", + "i18n_dba16b1b92": "构建事件", + "i18n_dbad1e89f7": "两步验证", + "i18n_dbb166cf29": "服务id", + "i18n_dbb2df00cf": "发布目录", + "i18n_dbba7e107a": "发布项目", + "i18n_dbc0b66ca4": "个结果", + "i18n_dc0d06f9c7": "请填写发布的二级目录", + "i18n_dc2961a26f": "节点名:", + "i18n_dc2c61a605": "自建 Gitlab", + "i18n_dc32f465da": "容器地址 tcp", + "i18n_dc3356300f": "如果要配置 SSH 请到【系统管理】-> 【资产管理】-> 【SSH 管理】中去配置。", + "i18n_dc39b183ea": "确定要忽略绑定两步验证吗?强烈建议超级管理员开启两步验证来保证账号安全性", + "i18n_dcc846e420": "批量构建全部参数举例", + "i18n_dcd72e6014": "获取单个构建状态地址", + "i18n_dce5379cb9": "隐藏", + "i18n_dcf14deb0e": "代理地址 (127.0.0.1:8888)", + "i18n_dd1d14efd6": "查看脚本", + "i18n_dd23fdf796": "编辑过程中可以切换节点但是要注意数据是否匹配", + "i18n_dd4e55c39c": "未开始", + "i18n_dd95bf2d45": "正常登录", + "i18n_ddc7d28b7b": "变量", + "i18n_dddf944f5f": "重置页面操作引导、导航成功", + "i18n_ddf0c97bce": "请注意备份数据防止数据丢失!!", + "i18n_ddf7d2a5ce": "命令", + "i18n_de17fc0b78": "硬盘最大的使用率:", + "i18n_de3394b14e": "没有资产机器", + "i18n_de4cf8bdfa": "付费加入我们的技术交流群优先解答您所有疑问", + "i18n_de5dadc480": "pull日志", + "i18n_de6bc95d3b": "清空当前目录文件", + "i18n_de78b73dab": "单个触发器地址", + "i18n_debdfce084": "请输入集群名称", + "i18n_decef97c7c": "服务端IP授权配置", + "i18n_deea5221aa": "标记", + "i18n_df011658c3": "范围", + "i18n_df1da2dc59": "容器在一个 CPU 周期内可以获得的 CPU 时间的微秒。", + "i18n_df3833270b": "地址:", + "i18n_df39e42127": "自动执行", + "i18n_df59a2804d": "禁止扫描", + "i18n_df5f80946d": "# node 镜像源 https://registry.npmmirror.com/-/binary/node/", + "i18n_df9497ea98": "存在", + "i18n_df9d1fedc5": "节点分发是指,一个项目部署在多个节点中使用节点分发一步完成多个节点中的项目发布操作", + "i18n_dfb8d511c7": "用户名称", + "i18n_dfcc9e3c45": "分发后操作", + "i18n_e039ffccc8": "】此文件还原到项目目录?", + "i18n_e049546ff3": "【系统配置目录】中修改", + "i18n_e06497b0fb": "查看当前可用容器", + "i18n_e06caa0060": "文件修改时间", + "i18n_e074f6b6af": "SMTP 服务器端口", + "i18n_e07cbb381c": "没有任何仓库", + "i18n_e09d0d8c41": "不建议使用常用名称如", + "i18n_e0a0e26031": "使用当前镜像创建一个容器", + "i18n_e0ae638e73": "保留产物是指对在构建完成后是否保留构建产物相关文件,用于回滚", + "i18n_e0ba3b9145": "节点脚本", + "i18n_e0ce74fcac": "自动滚动", + "i18n_e0d6976b48": "请选择集群关联分组", + "i18n_e0ea800e34": "打包正式环境 npm i && npm run build:prod", + "i18n_e0ec07be7d": "客户端密钥", + "i18n_e0f937d57f": "临时token", + "i18n_e0fcbca309": "工作空间ID:", + "i18n_e15f22df2d": "真的要清除构建信息么?", + "i18n_e166aa424d": "关于系统", + "i18n_e17a6882b6": "脚本标记", + "i18n_e19cc5ed70": "同步节点授权", + "i18n_e1c965efff": "请选择状态", + "i18n_e1fefde80f": "节点账号密码默认由系统生成:可以通过插件端数据目录下 agent_authorize.json 文件查看(如果自定义配置了账号密码将没有此文件)", + "i18n_e222f4b9ad": "执行前需要检查命令中的地址在对应的服务器中是否可以访问,如果无法访问将不能自动绑定节点,", + "i18n_e257dd2607": "请选择SSH连接信息", + "i18n_e26dcacfb1": " 检查 ", + "i18n_e2adcc679a": "单位 ns 秒", + "i18n_e2b0f27424": "项目ID仅会在机器节点中限制唯一,不同工作空间(相同的工作空间)下是允许相同的项目ID", + "i18n_e2be9bab6b": "复制下面任意一条命令到还未安装插件端的服务器中去执行,执行前需要放行", + "i18n_e2f942759e": "会话异常", + "i18n_e30a93415b": "使用私人令牌,可以在你不输入账号密码的情况下对你账号内的仓库进行管理,你可以在创建令牌时指定令牌所拥有的权限。", + "i18n_e319a2a526": "重置为重新生成触发地址,重置成功后之前的触发器地址将失效,构建触发器绑定到生成触发器到操作人上,如果将对应的账号删除触发器将失效", + "i18n_e31ca72849": "上传压缩文件", + "i18n_e354969500": "令牌导入", + "i18n_e362bc0e8a": "路径:{source}(宿主机) => {destination}(容器)", + "i18n_e39de3376e": "分配", + "i18n_e39f4a69f4": "脚本数", + "i18n_e39ffe99e9": "请输入密码", + "i18n_e3cf0abd35": "邮箱验证码", + "i18n_e3e85de50c": "请选择构建方式", + "i18n_e3ead2bd0d": "常见构建命令示例", + "i18n_e3ee3ca673": "不追加脚本模板", + "i18n_e4013f8b81": "机器名称", + "i18n_e414392917": "集群信息:", + "i18n_e42b99d599": "月", + "i18n_e43359ca06": "请选择 SSH节点", + "i18n_e44f59f2d9": "发布前命令", + "i18n_e475e0c655": "证书共享", + "i18n_e48a715738": "新建文件", + "i18n_e4b51d5cd0": "运行状态", + "i18n_e4bea943de": "仓库地址", + "i18n_e4bf491a0d": "正在下载,请稍等...", + "i18n_e4d0ebcd58": "选择集群", + "i18n_e5098786d3": "args 参数", + "i18n_e54029e15b": "退出集群", + "i18n_e54c5ecb54": "编辑构建", + "i18n_e5915f5dbb": "(存在兼容问题,实际使用中需要提前测试) python3 sdk 镜像使用:https://repo.huaweicloud.com/python/{'${PYTHON3_VERSION}'}/Python-{'${PYTHON3_VERSION}'}.tar.xz", + "i18n_e5a63852fd": "节点密码,请查看节点启动输出的信息", + "i18n_e5ae5b36db": "关键词高亮,支持正则(正则可能影响性能请酌情使用)", + "i18n_e5f71fc31e": "搜索", + "i18n_e60389f6d6": "当前前端打包时间:", + "i18n_e60725e762": "周三", + "i18n_e63fb95deb": "队列数", + "i18n_e64d788d11": "升级成功", + "i18n_e6551a2295": "引用工作空间环境变量可以方便后面多处使用相同的密码统一修改", + "i18n_e6bf31e8e6": "长期token", + "i18n_e6cde5a4bc": "没有检查到最新版", + "i18n_e6e453d730": "请输入变量名称", + "i18n_e6e5f26c69": "QQ邮箱 SSL", + "i18n_e703c7367c": "当前状态:", + "i18n_e710da3487": "用时", + "i18n_e72a0ba45a": "用户组", + "i18n_e72f2b8806": "输入仓库名称或者仓库路径进行搜索", + "i18n_e747635151": "Script 名称", + "i18n_e76e6a13dd": "不引用环境变量", + "i18n_e78e4b2dc4": "级别", + "i18n_e7d83a24ba": "成功次数", + "i18n_e7e8d4c1fb": "断点/分片下载", + "i18n_e7ffc33d05": "上传后执行", + "i18n_e8073b3843": "请选择用户权限组", + "i18n_e825ec7800": "协议类型", + "i18n_e8321f5a61": "发布方式:", + "i18n_e83a256e4f": "确认", + "i18n_e84b981eb4": "配置值 (如:5g)", + "i18n_e8505e27f4": "升级前请阅读更新日志里面的说明和注意事项并且", + "i18n_e8e3bfbbfe": "确认关闭", + "i18n_e8f07c2186": "如果未填写将解析压缩包里面的 txt", + "i18n_e9290eaaae": "关闭左侧", + "i18n_e930e7890f": "表达式类似于Linux的crontab表达式,表达式使用空格分成5个部分,按顺序依次为:", + "i18n_e95f9f6b6e": "SSL 连接", + "i18n_e96705ead1": "如果按钮不可用则表示当前节点已经关闭啦,需要去编辑中启用", + "i18n_e976b537f1": "缓存监控", + "i18n_e97a16a6d7": " :每两分钟执行", + "i18n_e9bd4484a7": "发送方邮箱账号", + "i18n_e9c2cb1326": "次要ID", + "i18n_e9e9373c6f": "执行任务中", + "i18n_e9ea1e7c02": "文件保存天数,默认 3650 天", + "i18n_e9ec2b0bee": "并等待上一个项目启动完成才能关闭下一个项目", + "i18n_e9f2c62e54": ",新增默认参数后在手动执行脚本时需要填写参数值", + "i18n_ea15ae2b7f": "选项", + "i18n_ea3c5c0d25": "临时文件占用空间:", + "i18n_ea58a20cda": "机器DOCKER", + "i18n_ea7fbabfa1": "请输入账户名", + "i18n_ea89a319ec": "# 宿主机目录和容器目录挂载 /host:/container:ro", + "i18n_ea8a79546f": "请输入发布的文件id", + "i18n_ea9f824647": "拉取仓库超时时间,单位秒", + "i18n_eaa5d7cb9b": "过期天数", + "i18n_eadd05ba6a": "中等", + "i18n_eaf987eea0": "权重(相对权重)。", + "i18n_eb164b696d": "排除发布", + "i18n_eb5bab1c31": "非必填", + "i18n_eb79cea638": "周五", + "i18n_eb7f9ceb71": "脚本库:", + "i18n_eb969648aa": "请提前备份数据再操作奥", + "i18n_ebc2a1956b": "编辑监控", + "i18n_ebc96f0a5d": "总内存(内存 + 交换)。 设置为 -1 以禁用交换。", + "i18n_ec1f13ff6d": "总数:", + "i18n_ec219f99ee": "执行结束", + "i18n_ec22193ed1": "请选择分组", + "i18n_ec537c957a": "{slot1}机目录", + "i18n_ec6e39a177": "确认要下载更新最新版本吗?", + "i18n_ec7ef29bdf": "请输入静态,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径", + "i18n_ec989813ed": "状态信息:", + "i18n_eca37cb072": "创建时间", + "i18n_ecdf9093d0": "同时展开多个", + "i18n_ecff77a8d4": "使用", + "i18n_ed145eba38": "硬盘占用", + "i18n_ed19a6eb6f": "在线构建文件占用空间", + "i18n_ed367abd1a": "修改用户资料", + "i18n_ed39deafd8": "编辑仓库", + "i18n_ed40308fe9": "# maven 镜像源 https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_ed6a8ee039": " :表示多个定时表达式", + "i18n_ed8ea20fe6": "安装ID", + "i18n_edb4275dcd": "gmail邮箱", + "i18n_edb881412a": "注意:为了避免不必要的事件执行脚本,选择的脚本的备注中包含需要实现的事件参数关键词,如果需要执行 success 事件,那么选择的脚本的备注中需要包含 success 关键词", + "i18n_edc1185b8e": "尝试自动续签失败", + "i18n_edd716f524": "请选择发布的一级目录", + "i18n_ede2c450d1": "没有任何登录日志", + "i18n_ee19907fad": "# 基础镜像 目前仅支持 ubuntu-latest", + "i18n_ee4fac2f3c": "为了避免部分节点不能及时响应造成监控阻塞,节点统计超时时间不受节点超时配置影响将采用默认超时时间(10秒)", + "i18n_ee6ce96abb": "s 秒", + "i18n_ee8ecb9ee0": "优先级", + "i18n_ee9a51488f": "请输入发布目录", + "i18n_eeb6908870": "上一步", + "i18n_eec342f34e": "默认账号为:jpomAgent", + "i18n_eee6510292": "配置授权目录", + "i18n_eee83a9211": "资源", + "i18n_eeef8ced69": "解绑会检查数据关联性,同时将自动删除节点项目和脚本缓存信息", + "i18n_eef3653e9a": "jvm{slot1},{slot2}.如:-Xms512m -Xmx512m", + "i18n_eef4dfe786": "Java 项目(java -Djava.ext.dirs=lib -cp conf:run.jar $MAIN_CLASS)", + "i18n_ef016ab402": "确认创建该", + "i18n_ef28d3bff2": "页面内容自动撑开出现屏幕滚动条", + "i18n_ef651d15b0": "创建之后不能修改,分发 ID 等同于项目 ID", + "i18n_ef734bf850": "更多说明", + "i18n_ef7e3377a0": "允许时段", + "i18n_ef800ed466": "程序运行的 main 类(jar 模式运行可以不填)", + "i18n_ef8525efce": "分配到其他工作空间", + "i18n_ef9c90d393": "没有任何的脚本库", + "i18n_efae7764ac": "账号登录", + "i18n_efafd0cbd4": "密码(6-18位数字、字母、符号组合)", + "i18n_efb88b3927": "系统运行时间", + "i18n_efd32e870d": "插件构建时间", + "i18n_efe71e9bec": "真的要解绑当前节点分发么?", + "i18n_efe9d26148": "真的要删除该证书么,删除会将证书文件一并删除奥?", + "i18n_f038f48ce5": "编辑脚本", + "i18n_f04a289502": "svn ssh 必填登录用户", + "i18n_f05e3ec44d": "禁止访问,当前IP限制访问", + "i18n_f06f95f8e6": "孤独数据", + "i18n_f087eb347c": "构建命令示例", + "i18n_f08afd1f82": "已选择", + "i18n_f0a1428f65": "账号支持引用工作空间变量:", + "i18n_f0aba63ae7": "颁发者", + "i18n_f0db5d58cb": "开启两步验证使账号更安全", + "i18n_f0eb685a84": "文件来兼容您的机器", + "i18n_f105c1d31d": "最后执行人", + "i18n_f113c10ade": "排空", + "i18n_f139c5cf32": "输入新名称", + "i18n_f175274df0": "登录名称,账号,创建之后不能修改", + "i18n_f1a2a46f52": "# 使用哪个 docker 构建,填写 docker 标签 默认查询可用的第一个,如果 tag 查询出多个也选择第一个结果", + "i18n_f1b2828c75": "安装插件端", + "i18n_f1d8533c7f": "请输入证书信息或者选择证书信息,证书信息填写规则:序列号:证书类型", + "i18n_f1fdaffdf0": "后台构建", + "i18n_f240f9d69c": "分支名称:", + "i18n_f26225bde6": "详情", + "i18n_f26ef91424": "下载", + "i18n_f282058f75": "静态文件项目(前端、日志等)", + "i18n_f2d05944ad": "创建 Docker 集群", + "i18n_f30f1859ba": "如果您基于 Jpom 二次开发修改了", + "i18n_f332f2c8df": "网关", + "i18n_f3365fbf4d": "未获取到 Docker 或者禁用监控", + "i18n_f33db5e0b2": "点击刷新构建信息", + "i18n_f37f8407ec": "文件ID:", + "i18n_f3947e6581": "开源不等同于免费", + "i18n_f3e93355ee": "重启项目", + "i18n_f425f59044": "系统版本:", + "i18n_f49dfdace4": "权限组", + "i18n_f4b7c18635": "密码长度为6-20", + "i18n_f4baf7c6c0": "未启动", + "i18n_f4bbbaf882": "分支/标签", + "i18n_f4dd45fca9": "请输入远程地址", + "i18n_f4edba3c9d": "未知的表格类型", + "i18n_f4fb0cbecf": "还没有任何结果", + "i18n_f5399c620e": "真的要在该集群剔除此节点么?", + "i18n_f562f75c64": "服务地址", + "i18n_f56c1d014e": "执行成功", + "i18n_f5c3795be5": "官方", + "i18n_f5d0b69533": "完整的私钥内容 如", + "i18n_f5d14ee3f8": "磁盘占用", + "i18n_f5f65044ea": "容器安装的服务端不能使用本地构建(因为本地构建依赖启动服务端本地的环境,容器方式安装不便于管理本地依赖插件)", + "i18n_f63345630c": "# 将容器中的 node_modules 文件缓存到 docker 卷中", + "i18n_f63870fdb0": "请填写容器名称", + "i18n_f652d8cca7": "尝试自动续签...", + "i18n_f66335b5bf": "错误信息:", + "i18n_f66847edb4": "网页应用ID", + "i18n_f668c8c881": "集群名称:", + "i18n_f685377a22": "脚本库 ", + "i18n_f68f9b1d1b": "最后心跳时间", + "i18n_f6d6ab219d": "更新完成后确实成功的时间", + "i18n_f6d96c1c8c": "为了兼容Quartz表达式,同时支持6位和7位表达式,其中:", + "i18n_f6dee0f3ff": "分发 ID", + "i18n_f712d3d040": "备注示例:", + "i18n_f71316d0dd": "替换引用", + "i18n_f71a30c1b9": "数据目录占用空间", + "i18n_f7596f3159": "如果需要在其他工作空间需要提前切换生成命令", + "i18n_f76540a92e": "准备中", + "i18n_f782779e8b": "结束时间", + "i18n_f7b9764f0f": "项目启动,停止,重启都将请求对应的地址", + "i18n_f7e8d887d6": "工作空间环境变量", + "i18n_f7f340d946": "真的要清除 SSH 隐藏字段信息么?(密码,私钥)", + "i18n_f8460626f0": "节点账号,请查看节点启动输出的信息", + "i18n_f86324a429": "使用 ANT 表达式来实现在过滤指定目录来实现发布排除指定目录", + "i18n_f89cc4807e": "授权路径是指项目文件存放到服务中的文件夹", + "i18n_f89fa9b6c6": "选择仓库", + "i18n_f8a613d247": "请选择节点", + "i18n_f8b3165e0d": "当前项目被禁用", + "i18n_f8f20c1d1e": "修剪在此时间戳之前创建的对象 例如:24h", + "i18n_f8f456eb9a": "类型项目特有的 type:reload、restart", + "i18n_f932eff53e": "条数据", + "i18n_f9361945f3": "主机名 hostname", + "i18n_f967131d9d": "仓库名称", + "i18n_f976e8fcf4": "监控名称", + "i18n_f97a4d2591": "请选择要加入到哪个集群", + "i18n_f9898595a0": "注意:同一个分组不建议被多个集群绑定", + "i18n_f98994f7ec": "发布方式", + "i18n_f99ead0a76": "镜像名称不正确 不能更新", + "i18n_f9ac4b2aa6": "操作人", + "i18n_f9c9f95929": "Java 项目(java -classpath)", + "i18n_f9cea44f02": "当前工作空间还没有 Docker", + "i18n_f9f061773e": "不填写则使用节点分发配置的二级目录", + "i18n_fa2f7a8927": "失败策略", + "i18n_fa4aa1b93b": "运行项目", + "i18n_fa57a7afad": "容器标签,如:xxxx:latest 多个使用逗号隔开, 配置附加环境变量文件支持加载仓库目录下 .env 文件环境变量 如: xxxx:{'${VERSION}'}", + "i18n_fa624c8420": "禁用后该用户不能登录平台", + "i18n_fa7f6fccfd": "项目名称:", + "i18n_fa7ffa2d21": "解锁", + "i18n_fa8e673c50": "编辑工作空间", + "i18n_faa1ad5e5c": "协议", + "i18n_faaa995a8b": "可以关闭", + "i18n_faaadc447b": "序号", + "i18n_fabc07a4f1": "请选择监控操作", + "i18n_fad1b9fb87": "新增脚本模版需要到节点管理中去新增", + "i18n_fb1f3b5125": "当前工作空间关联数据统计", + "i18n_fb3a2241bb": "状态描述:", + "i18n_fb5bc565f3": "解析文件失败:", + "i18n_fb61d4d708": "真的要回滚该构建历史记录么?", + "i18n_fb7b9876a6": "请输入脚本名称", + "i18n_fb852fc6cc": "进行中", + "i18n_fb8fb9cc46": "统计说明", + "i18n_fb91527ce5": "节点可用性:", + "i18n_fb9d826b2f": "发布后执行的命令(非阻塞命令),一般是启动项目命令 如:ps -aux | grep java", + "i18n_fba5f4f19a": "DSL环境变量", + "i18n_fbd7ba1d9b": "最后分发时间", + "i18n_fbee13a873": "工作空间总数:", + "i18n_fbfa6c18bf": "已分配", + "i18n_fbfeb76b33": "左边菜单栏主题切换", + "i18n_fc06c70960": "您确定要删除当前镜像吗?", + "i18n_fc4e2c6151": "登录用户", + "i18n_fc5fb962da": "邮箱密码或者授权码", + "i18n_fc92e93523": "生效时间", + "i18n_fc954d25ec": "代理", + "i18n_fcaef5b17a": "重用另一个容器网络堆栈", + "i18n_fcb4c2610a": "通知异常", + "i18n_fcb7a47b70": "阿里云企业邮箱", + "i18n_fcba60e773": "构建", + "i18n_fcbf0d0a55": "需要先安装依赖 yarn && yarn run build", + "i18n_fcca8452fe": "集群地址主要用于切换工作空间自动跳转到对应的集群", + "i18n_fcef976c7a": "私钥内容", + "i18n_fd6e80f1e0": "正常", + "i18n_fd7b461411": "不清空", + "i18n_fd7e0c997d": "选择文件", + "i18n_fd93f7f3d7": "可以将脚本分发到机器节点中在 DSL 项目中引用,达到多个项目共用相同脚本", + "i18n_fda92d22d9": "关联节点会自动识别服务器中是否存在 java 环境,如果没有 Java 环境不能快速安装节点", + "i18n_fdba50ca2d": "如果端口暴露到公网很", + "i18n_fdbac93380": "SMTP 地址:smtp.mxhichina.com,端口使用 465 并且开启 SSL,用户名需要和邮件发送人一致,密码为邮箱的登录密码", + "i18n_fdbc77bd19": "安全", + "i18n_fdcadf68a5": "SMTP 端口", + "i18n_fde1b6fb37": "需要提前为机器配置授权目录", + "i18n_fdfd501269": "java sdk 镜像使用:https://mirrors.tuna.tsinghua.edu.cn/ 支持版本有:8, 9, 10, 11, 12, 13, 14, 15, 16, 17", + "i18n_fe1b192913": "目录创建成功后需要手动刷新右边树才能显示出来哟", + "i18n_fe231ff92f": "关闭页面操作引导、导航", + "i18n_fe2df04a16": "版本", + "i18n_fe32def462": "活跃", + "i18n_fe7509e0ed": "值", + "i18n_fe828cefd9": "项目文件夹是项目实际存放的目录名称", + "i18n_fe87269484": "集群修改时间", + "i18n_fea996d31e": "请填写构建名称", + "i18n_fec6151b49": "账户名称", + "i18n_feda0df7ef": "账号邮箱", + "i18n_ff17b9f9cd": "企业微信", + "i18n_ff1fda9e47": "禁止", + "i18n_ff39c45fbc": "使用容器内的主机网络堆栈。 注意:主机模式赋予容器对本地系统服务(如 D-bus)的完全访问权限,因此被认为是不安全的。", + "i18n_ff3bdecc5e": "文件查看(如果自定义配置了账号密码将没有此文件)", + "i18n_ff80d2671c": "秒后刷新", + "i18n_ff9814bf6b": "触发类型", + "i18n_ff9dffec4d": "搜索模式", + "i18n_ffa9fd37b5": "工作空间管理", + "i18n_ffaf95f0ef": "启动的容器 可以看到很多host上的设备 可以执行mount。 可以在docker容器中启动docker容器。", + "i18n_ffd67549cf": ":范围:1~12,同时支持不区分大小写的别名:_##_jan\",\"feb\", \"mar\", \"apr\", \"may\",\"jun\", \"jul\", \"aug\",\"sep\",\"oct\", \"nov\", \"dec\"", + "i18n_fffd3ce745": "共享" +} diff --git a/web-vue/src/i18n/locales/zh_hk.json b/web-vue/src/i18n/locales/zh_hk.json new file mode 100644 index 0000000000..abfcf5cab9 --- /dev/null +++ b/web-vue/src/i18n/locales/zh_hk.json @@ -0,0 +1,2806 @@ +{ + "i18n_0006600738":"加入 Docker 集羣", + "i18n_005de9a4eb":"構建歷史是用於記錄每次構建的信息,可以保留構建產物信息,構建日誌。同時還可以快速回滾發佈", + "i18n_0079d91f95":"確定要將此數據置頂嗎?", + "i18n_007f23e18f":"關閉 TLS 認證", + "i18n_00a070c696":"點擊可以複製", + "i18n_00b04e1bf0":"發送包", + "i18n_00d5bdf1c3":"調用次數", + "i18n_00de0ae1da":"文件上傳前需要執行的腳本(非阻塞命令)", + "i18n_01081f7817":"請輸入允許編輯文件的後綴及文件編碼,不設置編碼則默認取系統編碼,多個使用換行。示例:設置編碼:txt{'@'}utf-8, 不設置編碼:txt", + "i18n_010865ca50":"真的要停止項目麼?", + "i18n_0113fc41fc":"全屏日誌", + "i18n_01198a1673":"上傳小文件", + "i18n_01226f48fc":"對於每一個子表達式,同樣支持以下形式:", + "i18n_0128cdaaa3":"分配類型", + "i18n_01ad26f4a9":"重置觸發器 token 信息,重置後之前的觸發器 token 將失效", + "i18n_01b4e06f39":"重啟", + "i18n_01e94436d1":"原密碼", + "i18n_020d17aac6":"發送大小", + "i18n_020f1ecd62":"開始上傳", + "i18n_020f31f535":"路徑需要配置絕對路徑,不支持軟鏈", + "i18n_0215b91d97":"構建序號id需要跟進實際情況替換", + "i18n_0221d43e46":"遠程下載Url不為空", + "i18n_0227161b3e":"執行方式", + "i18n_022b6ea624":"您確定要刪除當前卷嗎?", + "i18n_0253279fb8":"克隆深度", + "i18n_02d46f7e6f":"真的要刪除這些構建歷史記錄麼?", + "i18n_02d9819dda":"提示", + "i18n_02db59c146":"禁止訪問的 IP 地址", + "i18n_02e35447d4":"下載構建產物,如果按鈕不可用表示產物文件不存在,一般是構建沒有產生對應的文件或者構建歷史相關文件被刪除", + "i18n_0306ea1908":"刪除鏡像", + "i18n_031020489f":"當前工作空間您觸發的構建記錄", + "i18n_03580275cb":"請選中要重啟的項目", + "i18n_0360fffb40":"並開啟此開關", + "i18n_036c0dc2aa":"系統取消分發", + "i18n_0373ba5502":"需要您在需要被管理的服務器中安裝 agent ,並將 agent 信息新增到系統中", + "i18n_03816381ec":"切換視圖", + "i18n_0390e2f548":"參數{count}描述", + "i18n_03a74a9a8a":"日誌路徑", + "i18n_03c1f7c142":"請填選擇構建的倉庫", + "i18n_03d9de2834":"項目運維", + "i18n_03dcdf92f5":"隱私變量", + "i18n_03e59bb33c":"緊湊", + "i18n_03f38597a6":"速度", + "i18n_0428b36ab1":"副本", + "i18n_04412d2a22":"操作不能撤回奧", + "i18n_044b38221e":"Java 項目(示例參考,具體還需要根據項目實際情況來決定)", + "i18n_045cd62da3":"型號:", + "i18n_045f89697e":"壓縮包進行發佈", + "i18n_047109def4":"待處理", + "i18n_04a8742dd7":"插件運行時間", + "i18n_04edc35414":"模板節點", + "i18n_051fa113dd":"方式連接 docker 是通過終端實現,每次操作 docker 相關 api 需要登錄一次終端", + "i18n_05510a85b0":"系統中您所有操作日誌", + "i18n_059ac641c0":"特權:", + "i18n_05b52ae2db":"{slot1} 用於容器構建選擇容器功能(fromTag)", + "i18n_05cfc9af9d":"接收錯誤", + "i18n_05e6d88e29":"分發節點是指在編輯完腳本後自動將腳本內容同步節點的腳本,一般用户節點分發功能中的 DSL 模式", + "i18n_05e78c26b1":"單個觸發器地址中:第一個隨機字符串為命令腳本ID,第二個隨機字符串為 token", + "i18n_05f6e923af":"執行錯誤", + "i18n_0647b5fc26":"先停止", + "i18n_066431a665":"請輸入證書描述", + "i18n_066f903d75":"操後上移或者下移可能不會達到預期排序", + "i18n_067638bede":"CPU數", + "i18n_067eb0fa04":"如果這裏的報警聯繫人無法選擇,説明這裏面的管理員沒有設置郵箱,在右上角下拉菜單裏面的用户資料裏可以設置。", + "i18n_0693e17fc1":"有新內容後是否自動滾動到底部", + "i18n_06986031a7":"需要到原始工作空間中去控制節點分發", + "i18n_06e2f88f42":"請輸入名稱", + "i18n_0703877167":"關閉MFA", + "i18n_0719aa2bb0":"重置密碼", + "i18n_0728fee230":"請輸入公吿標題", + "i18n_072fa90836":"壓縮 ", + "i18n_0739b9551d":"端口協議", + "i18n_07683555af":"當前版本號:", + "i18n_0793aa7ba3":"maven sdk 鏡像使用:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_07a03567aa":"虛擬內存佔用", + "i18n_07a0e44145":"主機名:", + "i18n_07a828310b":"並行度", + "i18n_07a8af8c03":"為當前項目實際的進程ID", + "i18n_07b6bb5e40":"嚴格執行腳本(構建命令、事件腳本、本地發佈腳本、容器構建命令)執行返回狀態碼必須是 0、否則將構建狀態標記為失敗", + "i18n_07d2261f82":"默認是當前時間到今年結束", + "i18n_080b914139":"上傳包", + "i18n_0836332bf6":"升級協議", + "i18n_083b8a2ec9":"一個物理節點被多個服務端綁定也會產生孤獨數據奧", + "i18n_08902526f1":"皮膚:", + "i18n_0895c740a6":"交換內存佔用", + "i18n_089a88ecee":"系統時間:", + "i18n_08ab230290":"操作説明", + "i18n_08ac1eace7":"文件上傳成功後需要執行的腳本(非阻塞命令)", + "i18n_08b1fa1304":"請輸入用户名", + "i18n_08b55fea3c":"管理", + "i18n_0934f7777a":"新標籤終端", + "i18n_095e938e2a":"停止", + "i18n_09723d428d":"報警聯繫人", + "i18n_09d14694e7":"需要 SSH 監控中能獲取到 docker 信息", + "i18n_09e7d24952":"實際內存佔用率:", + "i18n_0a056b0d5a":"動態文件", + "i18n_0a1d18283e":"構建確認彈窗", + "i18n_0a47f12ef2":"如果孤獨數據被工作空間下的其他功能關聯,修正後關聯的數據將失效對應功能無法查詢到關聯數據", + "i18n_0a54bd6883":"Gmail 郵箱配置", + "i18n_0a60ac8f02":"是", + "i18n_0a63bf5b41":"軟內存限制。", + "i18n_0a9634edf2":"地址通配符,* 表示所有地址都將使用代理", + "i18n_0aa60d1169":"您還未登錄過", + "i18n_0aa639865c":"真的要刪除機器 SSH 麼?", + "i18n_0ac4999a4c":"網卡信息", + "i18n_0ac9e3e675":"綁定成功後將不再顯示,強烈建議保存此二維碼或者下面的 MFA key", + "i18n_0af04cdc22":"支持兩種方式填充:", + "i18n_0af5d9f8e8":"當前區域為系統管理、資產管理中心", + "i18n_0b23d2f584":"差異構建", + "i18n_0b2fab7493":"當前 SSH 的授權目錄(文件目錄、文件後綴、禁止命令)需要請到 【系統管理】-> 【資產管理】-> 【SSH 管理】-> 操作欄中->關聯按鈕->對應工作空間->操作欄中->配置按鈕", + "i18n_0b3edfaf28":"設置內存限制。", + "i18n_0b58866c3e":"斷點/分片單文件下載", + "i18n_0b76afbf5d":"允許執行的 CPU(例如,0-3、0", + "i18n_0b9d5ba772":"請尊重開源協議,不要擅自修改版本信息,否則可能承擔法律責任。", + "i18n_0baa0e3fc4":"發佈中", + "i18n_0bac3db71c":"重啟服務端後失效", + "i18n_0bbc7458b4":"回到首頁", + "i18n_0bc45241af":"傳入參數有:outGivingId、outGivingName、status、statusMsg、executeTime", + "i18n_0bf9f55e9d":"不能關閉", + "i18n_0bfcab4978":"node sdk 鏡像使用:https://registry.npmmirror.com/-/binary/node", + "i18n_0c0633c367":"不能刪除默認工作空間", + "i18n_0c1de8295a":"獨立", + "i18n_0c1e9a72b7":"將使用微隊列來排隊構建,避免幾乎同時觸發構建被中斷構建(一般用户倉庫合併代碼會觸發多次請求),隊列保存在內存中,重啟將丟失", + "i18n_0c1f1cd79b":"不自動重啟", + "i18n_0c1fec657f":"秒", + "i18n_0c2487d394":"下拉搜索默認搜索關鍵詞相關的前 10 個,以及已經選擇的機器節點", + "i18n_0c256f73b8":"容器名稱:", + "i18n_0c4eef1b88":"當為6位時,第一位表示", + "i18n_0c5c8d2d11":"基礎信息:", + "i18n_0c7369bbee":"開啟SSH訪問", + "i18n_0cbf83cc07":"聯繫我們", + "i18n_0ccaa1c8b2":" :表示匹配這個位置所有的時間", + "i18n_0ce54ecc25":"付費社羣", + "i18n_0cf4f0ba82":"真的要保存當前配置嗎?如果配置有誤,可能無法啟動服務需要手動還原奧!!! 保存成功後請及時關注重啟狀態!!", + "i18n_0cf81d77bb":"請填寫倉庫地址", + "i18n_0d44f4903a":"真的要釋放(刪除)當前項目麼?", + "i18n_0d467f7889":"# 是否開啟日誌備份功能", + "i18n_0d48f8e881":"請輸入服務地址", + "i18n_0d50838436":"數據目錄", + "i18n_0d98c74797":"其他", + "i18n_0da9b12963":"用户數據", + "i18n_0de68f5626":"登錄JPOM", + "i18n_0e052223a4":"重啟服務端需要重新獲取", + "i18n_0e16902c1e":"查看狀態", + "i18n_0e1ecdae4a":"完整順序執行(有執行失敗將結束本次)", + "i18n_0e25ab3b51":"證書的允許的 IP 需要和 docker host 一致", + "i18n_0e44ae17ae":"服務端機器網絡", + "i18n_0e502fed63":"重啟超時,請去服務器查看控制枱日誌排查問題", + "i18n_0e55a594fd":"監控項目", + "i18n_0e5f01b9be":"關聯工作空間ssh", + "i18n_0ea78e4279":"查看日誌", + "i18n_0ec9eaf9c3":"更多", + "i18n_0eccc9451d":"# 備份文件保留個數", + "i18n_0ee3ca5e88":"掃碼讚賞支持開源項目長期發展", + "i18n_0ef396cbcc":"分發結果", + "i18n_0f004c4cf7":"第三方登錄", + "i18n_0f0a5f6107":"正常連接", + "i18n_0f189dbaa4":"沒有任何用户", + "i18n_0f4f503547":"請輸入版本", + "i18n_0f539ff117":"真的要批量刪除選擇的鏡像嗎?已經被容器使用的鏡像無法刪除!", + "i18n_0f59fe5338":"防火牆端口", + "i18n_0f5fc9f300":"文件管理中心", + "i18n_0f8403d07e":"刷新倒計時", + "i18n_0fca8940a8":"沒有節點", + "i18n_0ff425e276":"文件ID", + "i18n_1012e09849":"處理失敗", + "i18n_10145884ba":"文件後N行", + "i18n_1014b33d22":"分組名稱", + "i18n_101a86bc84":"請輸入...", + "i18n_1022c545d1":"插件端啟動時自動檢查項目如未啟動將嘗試啟動", + "i18n_102dbe1e39":"注意:環境變量存在作用域:當前工作空間或者全局,不能跨工作空間引用", + "i18n_102e8ec6d5":"網絡流量信息", + "i18n_1058a0be42":"開啟 TLS 認證,證書信息:", + "i18n_1062619d5a":"節點賬號密碼默認由系統生成:可以通過插件端數據目錄下 agent", + "i18n_108d492247":"正則語法參考", + "i18n_10c385b47e":"一鍵分發同步多個節點的系統配置", + "i18n_10d6dfd112":"顯示後N行", + "i18n_10f6fc171a":"SSH 名稱", + "i18n_111e786daa":"填寫備註僅本次構建生效", + "i18n_1125c4a50b":"真的要刪除分發信息麼?刪除後節點下面的項目也都將刪除", + "i18n_113576ce91":"產物目錄:", + "i18n_1149274cbd":"用户總數", + "i18n_115cd58b5d":"】備份文件夾麼?", + "i18n_1160ab56fd":"構建命令:", + "i18n_116d22f2ab":"項目ID:", + "i18n_11724cd00b":"集羣創建時間", + "i18n_117a9cbc8d":"語言:", + "i18n_11957d12e4":"報警中", + "i18n_11e88c95ee":" 查找上一個", + "i18n_121e76bb63":"請選擇構建對應的分支", + "i18n_1235b052ff":"節點地址 (192.168.1.100:2123)", + "i18n_1278df0cfc":"關聯節點如果服務器存在 java 環境,但是插件端未運行則會顯示快速安裝按鈕", + "i18n_127de26370":"SMTP 地址:【smtp.qq.com】,用户名一般是QQ號碼,密碼是郵箱授權碼,端口默認 587/465", + "i18n_12934d1828":"日誌目錄是指控制枱日誌存儲目錄", + "i18n_12afa77947":"開啟緩存構建目錄將保留倉庫文件,二次構建將 pull 代碼, 不開啟緩存目錄每次構建都將重新拉取倉庫代碼(較大的項目不建議關閉緩存)", + "i18n_12d2c0aead":"請將此密碼複製吿知該用户", + "i18n_12dc402a82":"參考數據", + "i18n_130318a2a1":"路由無效,無法跳轉", + "i18n_1303e638b5":"修改時間", + "i18n_13627c5c46":"配置ssh", + "i18n_138776a1dc":"默認是在插件端數據目錄/{'${projectId}'}/{'${projectId}'}.log", + "i18n_138a676635":"注意", + "i18n_13c76c38b7":"# scriptId 可以引用腳本庫中的腳本(G{'@'}xxx)其中 xxx 為腳本庫中的腳本標記,前提需要提取將對應腳本同步至對應機器節點", + "i18n_13d10a9b78":"沒有資產SSH", + "i18n_13d947ea19":"需要您先新增資產機器再分配機器節點(邏輯節點)到當前工作空間", + "i18n_13f7bb78ef":"默認統計機器中除本地接口(環回或無硬件地址)網卡流量總和", + "i18n_13f931c5d9":"查看任務", + "i18n_1432c7fcdb":"系統公吿", + "i18n_143bfbc3a1":"點擊重新同步當前工作空間邏輯節點項目信息", + "i18n_143d8d3de5":"否則將刪除滿足條件的所有數據", + "i18n_148484b985":"實現您需要配置 docker 容器到服務端中來管理,並且分配到當前工作空間中", + "i18n_1498557b2d":"同時只能展開一個菜單", + "i18n_14a25beebb":"10秒一次", + "i18n_14d342362f":"標籤", + "i18n_14dcfcc4fa":"還未執行reload", + "i18n_14dd5937e4":"附加環境變量 .env 新增多個使用逗號分隔", + "i18n_14e6d83ff5":"時間:", + "i18n_14ee5b5dc5":"命令文件將在 {'${插件端數據目錄}'}/script/xxxx.sh 、bat 執行", + "i18n_14feaa5b3a":"刷新倒計時 ", + "i18n_1535fcfa4c":"發送", + "i18n_156af3b3d1":"菜單配置", + "i18n_1593dc4920":"真的要刪除該記錄麼?刪除後構建關聯的容器標籤將無法使用", + "i18n_159a3a8037":"更新鏡像", + "i18n_15c0ba2767":"上傳項目文件", + "i18n_15c46f7681":"修改接口 HTTP 狀態碼為 200 並且響應內容為:success 才能確定操作成功反之均可能失敗", + "i18n_15d5fffa6a":"響應結果", + "i18n_15e9238b79":"接收", + "i18n_15f01c43e8":"日誌備份列表", + "i18n_15fa91e3ab":"天級別", + "i18n_1603b069c2":"週一", + "i18n_1622dc9b6b":"未知", + "i18n_162e219f6d":"丟失", + "i18n_164cf07e1c":"清空覆蓋", + "i18n_16646e46b1":"產物文件大小:", + "i18n_16b5e7b472":"直接構建", + "i18n_16f7fa08db":"嗎?", + "i18n_17006d4d51":"是否自動跳轉到系統頁面", + "i18n_170fc8e27c":"週四", + "i18n_174062da44":"分發方式", + "i18n_1775ff0f26":"建議新增指定時間範圍", + "i18n_178ad7e9bc":"參數中的 id 、token 和觸發構建一致、buildNumId 構建序號id", + "i18n_17a101c23e":"孤獨數據是指機器節點裏面存在數據,但是無法和當前系統綁定上關係(關係綁定=節點ID+工作空間ID對應才行),一般情況下不會出現這樣的數據", + "i18n_17a74824de":"構建方式", + "i18n_17acd250da":"下移", + "i18n_17b4c9c631":"沒有任何節點", + "i18n_17b5e684e5":"需要到 節點管理中的【插件端配置】的授權配置中配置允許編輯的文件後綴", + "i18n_17c06f6a8b":"最後執行時間", + "i18n_17d444b642":"運行方式", + "i18n_1810e84971":"才能使用 SSH 方式連接", + "i18n_1818e9c264":"JVM總內存", + "i18n_1819d0cdda":"如果開啟同步到文件管理中心,在構建發佈流程將自動執行同步到文件管理中心的操作。", + "i18n_181e1ad17d":"長按可以拖動排序", + "i18n_1857e7024c":"系統版本", + "i18n_185926bf98":"全屏", + "i18n_1862c48f72":"本地狀態:", + "i18n_1880b85dc5":"黑白 ambiance", + "i18n_18b0ab4dd2":"機器SSH名", + "i18n_18b34cf50d":"不滾動", + "i18n_18c63459a2":"默認", + "i18n_18c7e2556e":"如果當前構建信息已經在其他頁面更新過,需要點擊刷新按鈕來獲取最新的信息,點擊刷新後未保存的數據也將丟失", + "i18n_18d49918f5":"賬號被鎖定", + "i18n_18eb76c8a0":"memory 最小 4M", + "i18n_192496786d":"事件腳本", + "i18n_19675b9d36":"清除代碼(倉庫目錄)為刪除服務器中存儲倉庫目錄裏面的所有東西,刪除後下次構建將重新拉起倉庫裏面的文件,一般用於解決服務器中文件和遠程倉庫中文件有衝突時候使用。執行時間取決於源碼目錄大小和文件數量如超時請耐心等待,或稍後重試", + "i18n_1974fe5349":"綁定成功", + "i18n_197be96301":"待完善", + "i18n_19f974ef6a":"開啟差異發佈並且開啟清空發佈時將自動刪除項目目錄下面有的文件但是構建產物目錄下面沒有的文件【清空發佈差異上傳前會先執行刪除差異文件再執行上傳差異文件】", + "i18n_19fa0be4d2":" 官方文檔", + "i18n_19fcb9eb25":"時間", + "i18n_1a44b9e2f7":"同步到其他工作空間", + "i18n_1a55f76ace":"構建命令,構建產物相對路徑為:", + "i18n_1a56bb2237":"至少選擇一個節點和項目", + "i18n_1a6aa24e76":"執行", + "i18n_1a704f73c2":"請選擇一個文件", + "i18n_1a8f90122f":"提示信息 ", + "i18n_1abf39bdb6":"# 將此目錄緩存到全局(多個構建可以共享此緩存目錄)", + "i18n_1ad696efdc":"構建執行的命令(非阻塞命令),如:mvn clean package、npm run build。支持變量:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_SOURCE_FILE}'}、{'${BUILD_NUMBER_ID}'}、倉庫目錄下 .env、工作空間變量", + "i18n_1ae2955867":"指定 pom 文件打包 mvn -f xxx/pom.xml clean package", + "i18n_1afdb4a364":"隱藏滾動條。縱向滾動方式提醒:滾輪,橫行滾動方式:Shift+滾輪", + "i18n_1b03b0c1ff":"已經分配到工作空間的 Docker 或者集羣無非直接刪除,需要到分配到的各個工作空間逐一刪除後才能刪除資產 Docker 或者集羣", + "i18n_1b38c0bc86":"備份文件存儲目錄:", + "i18n_1b5266365f":"原始IP", + "i18n_1b5bcdf115":"會話已經關閉[node-system-log]", + "i18n_1b7cba289a":"數據統計", + "i18n_1b8fff7308":"開啟 MFA", + "i18n_1b963fd303":"【推薦】騰訊身份驗證碼", + "i18n_1b973fc4d1":"分組名稱:", + "i18n_1ba141c9ac":"請選擇軟鏈的項目", + "i18n_1ba584c974":"配置容器", + "i18n_1baae8183c":"是否解壓", + "i18n_1c040e6b87":"一般情況下不建議降級操作", + "i18n_1c10461124":"示例:key,key1 或者 key=value,key1=value1", + "i18n_1c13276448":"當前工作空間關聯構建", + "i18n_1c2e9d0c76":"沒有任何構建", + "i18n_1c3cf7f5f0":"關聯", + "i18n_1c61dfb86f":"掛載點", + "i18n_1c8190b0eb":"請填寫項目 DSL 配置內容,可以點擊上方切換 tab 查看配置示例", + "i18n_1c83d79715":"執行失敗", + "i18n_1c9d3cb687":"用户名ID", + "i18n_1cc82866a4":"分片操作數", + "i18n_1d0269cb77":"已經分配到工作空間的 SSH 無非直接刪除,需要到分配到的各個工作空間逐一刪除後才能刪除資產 SSH", + "i18n_1d263b7efb":"該選項僅本次構建生效", + "i18n_1d38b2b2bc":"請選擇項目授權路徑", + "i18n_1d53247d61":"請選擇邏輯節點", + "i18n_1d650a60a5":"硬盤", + "i18n_1d843d7b45":"此節點暫無項目", + "i18n_1dc518bddb":"項目存儲的文件夾", + "i18n_1dc9514548":"不等同於 PING 測試,此處測試成功表示網絡一定通暢,此處測試失敗網絡不一定不通暢", + "i18n_1de9b781bd":"使用容器構建,docker 容器所在的宿主機需要有公網,因為需要遠程下載環境依賴的 sdk 和鏡像", + "i18n_1e07b9f9ce":"請選擇要同步系統配置的機器節點", + "i18n_1e4a59829d":"插件端開機自啟", + "i18n_1e5533c401":"配置目錄", + "i18n_1e5ca46c26":"排除發佈 ANT 表達式,多個使用逗號分隔", + "i18n_1e88a0cfaf":"不發佈到 docker 集羣", + "i18n_1e93bdad2a":"搜索項目名", + "i18n_1eb378860a":"真的要 Kill 這個進程麼?", + "i18n_1eba2d93fc":"禁用原因", + "i18n_1ece1616bf":"如果插件端正常運行但是連接失敗請檢查端口是否開放,防火牆規則,雲服務器的安全組入站規則", + "i18n_1ed46c4a59":"分發名稱(項目名稱)", + "i18n_1f08329bc4":"搜索命令名稱", + "i18n_1f0c93d776":" :每分鐘執行", + "i18n_1f0d13a9ad":"服務端分發同步的腳本不能直接刪除,需要到服務端去操作", + "i18n_1f1030554f":"總計 {total} 條", + "i18n_1f130d11d1":"SMTP 服務器", + "i18n_1f4c1042ed":"文件夾", + "i18n_1fa23f4daa":"過期時間", + "i18n_1fd02a90c3":"用户", + "i18n_200707a186":"創建後構建方式不支持修改", + "i18n_2025ad11ee":"真的要解綁節點腳本麼?", + "i18n_2027743b8d":"系統名稱:", + "i18n_204222d167":"網絡延遲", + "i18n_2064fc6808":"不顯示", + "i18n_207243d77a":"如果要將工作空間分配給其他用户還需要到權限組管理", + "i18n_207d9580c1":"表示週六", + "i18n_209f2b8e91":"請輸入登錄密碼", + "i18n_20a9290498":"您來到系統管理中心", + "i18n_20c8dc0346":"演示賬號", + "i18n_20e0b90021":"真的要刪除監控麼?", + "i18n_20f32e1979":"角色:", + "i18n_211354a780":"內的root只是外部的一個普通用户權限。默認false", + "i18n_21157cbff8":"毫秒", + "i18n_211a60b1d6":"編輯容器的一些基礎參數", + "i18n_2141ffaec9":"狀態數據是異步獲取有一定時間延遲", + "i18n_2168394b82":"文件id,精準搜索", + "i18n_2171d1b07d":"默認參數", + "i18n_2191afee6e":"升級超時,請去服務器查看控制枱日誌排查問題", + "i18n_21d81c6726":"為當前工作空間中的容器配置標籤", + "i18n_21da885538":"可以使用節點腳本:", + "i18n_21dd8f23b4":"開源協議", + "i18n_21e4f10399":"優先判斷禁用時段", + "i18n_21efd88b67":"暫無數據", + "i18n_220650a1f5":"配置後將保存到當前構建", + "i18n_2213206d43":"點擊延遲可以查看對應節點網絡延遲歷史數據", + "i18n_222316382d":"關聯節點", + "i18n_2223ff647d":"清空發佈", + "i18n_2245cf01a3":"您沒有權限訪問", + "i18n_2246d128cb":"企業微信通知地址", + "i18n_22482533ff":"私鑰內容,不填將使用默認的 $HOME/.ssh 目錄中的配置。支持配置文件目錄:file:/xxxx/xx", + "i18n_224aef211c":"構建信息", + "i18n_224e2ccda8":"配置", + "i18n_2256690a28":"節點ID:", + "i18n_22670d3682":"請選擇要使用的腳本", + "i18n_226a6f9cdd":"請檢查是否開啟 ws 代理", + "i18n_226b091218":"類型", + "i18n_22b03c024d":"二維碼", + "i18n_22c799040a":"容器", + "i18n_22cf31df5d":"當前訪問IP:", + "i18n_22e4da4998":"表示項目當前未運行", + "i18n_22e888c2df":"到期時間", + "i18n_2300ad28b8":"讀寫", + "i18n_2314f99795":"檢測到新版本 ", + "i18n_231f655e35":"當前程序打包時間:", + "i18n_23231543a4":"修正", + "i18n_2331a990aa":"掃碼轉賬支持開源項目長期發展", + "i18n_233fb56ab2":"在 設置-->安全設置-->私人令牌 中獲取", + "i18n_234e967afe":"發佈前執行的命令(非阻塞命令),一般是關閉項目命令,支持變量替換:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_2351006eae":"附加環境變量", + "i18n_23559b6453":"# 將容器中的 maven 倉庫文件緩存到 docker 卷中", + "i18n_2356fe4af2":"配合腳本模版實現自定義項目管理", + "i18n_2358e1ef49":"所屬工作空間: ", + "i18n_235f0b52a1":"發送錯誤", + "i18n_23b38c8dad":"會話已經關閉[upgrade]", + "i18n_23b444d24c":"快速配置", + "i18n_23eb0e6024":"暱稱", + "i18n_242d641eab":"後綴", + "i18n_2432b57515":"備註", + "i18n_24384ba6c1":"確定要重新同步當前節點項目緩存信息嗎?", + "i18n_24384dab27":"請輸入 value 的值", + "i18n_244d5a0ed8":"構建參數", + "i18n_2456d2c0f8":"如果容器以非零退出代碼退出,則重新啟動容器。可以指定次數:on-failure:2", + "i18n_2457513054":"週六", + "i18n_2482a598a3":"插件版本號", + "i18n_248c9aa7aa":"構建狀態", + "i18n_2493ff1a29":"自定義進程類型", + "i18n_2499b03cc5":"保留產物:", + "i18n_249aba7632":"天", + "i18n_24ad6f3354":"如果垮機器(資產機器)遷移之前機器中的項目數據僅是邏輯刪除(項目文件和日誌均會保留)", + "i18n_24cc0de832":"執行命令", + "i18n_24d695c8e2":"集羣主機名", + "i18n_250688d7c9":"發佈失敗", + "i18n_250a999bb2":"容器標籤,如:xxxx:latest 多個使用逗號隔開", + "i18n_25182fb439":"工作空間菜單", + "i18n_251a89efa9":"查看當前狀態", + "i18n_252706a112":"【推薦】微信小程序搜索 數盾OTP", + "i18n_2527efedcd":"用户信息 url", + "i18n_2560e962cf":"請選擇分發項目", + "i18n_257dc29ef7":"搜索配置參考", + "i18n_25b6c22d8a":"為避免顯示內容太多而造成瀏覽器卡頓,讀取日誌最後多少行日誌。修改後需要回車才能重新讀取,小於 1 則讀取所有", + "i18n_25be899f66":"篩選之後本次發佈操作只發布篩選項,並且只對本次操作生效", + "i18n_25c6bd712c":"請輸入獲取的計劃運行次數", + "i18n_25f29ebbe6":"腳本日誌數:", + "i18n_25f6a95de3":"確定要取消構建 【名稱:", + "i18n_2606b9d0d2":"分發機器", + "i18n_260a3234f2":"請選擇SSH", + "i18n_2611dd8703":"當目標工作空間不存在對應的節點時候將自動創建一個新的節點(邏輯節點)", + "i18n_26183c99bf":"文件中心", + "i18n_2646b813e8":"登錄密碼", + "i18n_267bf4bf76":"分發到節點中需要注意跨工作空間重名將被最後一次同步覆蓋", + "i18n_2684c4634d":"版本:", + "i18n_26a3378645":"請選擇運行方式", + "i18n_26b5bd4947":"加載中...", + "i18n_26bb841878":"新建", + "i18n_26bd746dc3":"真的要清空項目目錄和文件麼?", + "i18n_26c1f8d83e":"最後操作人", + "i18n_26ca20b161":"來源", + "i18n_26eccfaad1":"鏡像:", + "i18n_26f95520a5":"執行命令包含:", + "i18n_26ffe89a7f":"項目名:", + "i18n_27054fefec":"執行腳本傳入參數有:startReady、pull、executeCommand、release、done、stop、success", + "i18n_2770db3a99":"加載項目數據中...", + "i18n_2780a6a3cf":"TLS 認證", + "i18n_27b36afd36":"狀態碼為 0 的操作大部分為沒有操作結果或者異步執行", + "i18n_27ba6eb343":"網關:", + "i18n_27ca568be2":"繼續", + "i18n_27d0c8772c":"如果誤操作會產生宂餘數據!!!", + "i18n_27f105b0c3":"請選擇要升級的節點", + "i18n_280379cee4":"保存並關閉", + "i18n_282c8cda1f":"如果上報的節點信息包含多個 IP 地址需要用户確認使用具體的 IP 地址信息", + "i18n_288f0c404c":"清空", + "i18n_28b69f9233":"構建鏡像的過程不使用緩存", + "i18n_28b988ce6a":"文件類型", + "i18n_28bf369f34":"發佈後的文件名是:文件ID.後綴,並非文件真實名稱 (可以使用上傳後腳本隨意修改)", + "i18n_28c1c35cd9":"主節點不能直接剔除", + "i18n_28e0fcdf93":"還沒有容器或者未配置標籤不可以使用容器構建奧", + "i18n_28e1c746f7":"ssh 名", + "i18n_28e1eec677":"授權路徑", + "i18n_28f6e7a67b":"靜態文件", + "i18n_29139c2a1a":"文件名", + "i18n_2926598213":"項目日誌", + "i18n_293cafbbd3":"裁剪", + "i18n_2953a9bb97":"您需要創建一個賬户用以後續登錄管理系統,請牢記超級管理員賬號密碼", + "i18n_295bb704f5":"語言", + "i18n_29b48a76be":"請選擇發佈方式", + "i18n_29efa328e5":"未分發", + "i18n_2a049f4f5b":"分發失敗", + "i18n_2a0bea27c4":"執行域", + "i18n_2a0c4740f1":"文件", + "i18n_2a1d1da97a":"打包測試環境包 mvn clean package -Dmaven.test.skip=true -Ptest", + "i18n_2a24902516":"集羣ID:", + "i18n_2a38b6c0ae":"未升級成功:", + "i18n_2a3b06a91a":"虛擬MAC", + "i18n_2a3e7f5c38":"手動", + "i18n_2a6a516f9d":"填寫運行命令", + "i18n_2a813bc3eb":"立即下載", + "i18n_2ad3428664":"請選擇發佈到集羣的服務名", + "i18n_2adbfb41e9":"參數如果傳入", + "i18n_2ae22500c7":"禁用時段", + "i18n_2b04210d33":"進程號:", + "i18n_2b0623dab9":"獨立容器", + "i18n_2b0aa77353":"您確定要啟動當前容器嗎?", + "i18n_2b0f199da0":"不執行,也不編譯測試用例 mvn clean package -Dmaven.test.skip=true", + "i18n_2b1015e902":"參數描述沒有實際作用", + "i18n_2b21998b7b":"確定要關閉兩步驗證嗎?關閉後賬號安全性將受到影響,關閉後已經存在的 mfa key 將失效", + "i18n_2b36926bc1":"沒有任何構建歷史", + "i18n_2b4bb321d7":"內容區域主題切換", + "i18n_2b4cf3d74e":"請選擇要使用的構建", + "i18n_2b52fa609c":"發生異常", + "i18n_2b607a562a":"逐行執行", + "i18n_2b6bc0f293":"操作", + "i18n_2b788a077e":"等常用用户名,避免被其他用户有意或者無意操作造成登錄失敗次數過多從而超級管理員賬號被異常鎖定", + "i18n_2b94686a65":"# 給容器新增環境變量", + "i18n_2ba4c81587":"請輸入郵箱地址", + "i18n_2bb1967887":"請找我們授權,否則會有法律風險。", + "i18n_2be2175cd7":"執行容器 標籤", + "i18n_2be75b1044":"全局", + "i18n_2bef5b58ab":"不填寫則不更新", + "i18n_2c014aeeee":"打包時間", + "i18n_2c5b0e86e6":"用户密碼重置成功", + "i18n_2c635c80ec":"發佈操作是指,執行完構建命令後將構建產物目錄中的文件用不同的方式發佈(上傳)到對應的地方", + "i18n_2c74d8485f":"下載完成後需要手動選擇更新到節點才能完成節點更新奧", + "i18n_2c8109fa0b":"當前目錄: ", + "i18n_2c921271d5":"vue 項目(示例參考,具體還需要根據項目實際情況來決定)", + "i18n_2cdbbdabf1":"構建產物目錄,相對倉庫的路徑,如 java 項目的 target/xxx.jar vue 項目的 dist", + "i18n_2cdcfcee15":"功能豐富 專為兩步驗證碼", + "i18n_2ce44aba57":"日誌目錄", + "i18n_2d05c9d012":"關鍵詞,支持正則", + "i18n_2d2238d216":"賬號新增成功", + "i18n_2d3fd578ce":"確定要取批量消選中的構建嗎?注意:取消/停止構建不一定能正常關閉所有關聯進程", + "i18n_2d455ce5cd":"下載中", + "i18n_2d58b0e650":"選擇構建的標籤,不選為最新提交", + "i18n_2d7020be7d":"比如常見的 .env 文件", + "i18n_2d711b09bd":"內容", + "i18n_2d842318fb":"週期", + "i18n_2d94b9cf0e":"Dockerfile 構建方式不支持在這裏回滾", + "i18n_2d9569bf45":"參數值,新增默認參數後在手動執行腳本時需要填寫參數值", + "i18n_2d9e932510":"新增目錄", + "i18n_2de0d491d0":"小時", + "i18n_2e0094d663":"真的要刪除該集羣信息麼?1", + "i18n_2e1f215c5d":"自動創建用户", + "i18n_2e505d23f7":"下載導入模板", + "i18n_2e51ca19eb":"如果節點選項是禁用,則表示對應數據有推薦關聯節點(低版本項目數據可能出現此情況)", + "i18n_2e740698cf":"集羣IP", + "i18n_2ea7e70e87":"命令文件將上傳至 {'${user.home}'}/.jpom/xxxx.sh 執行完成將自動刪除", + "i18n_2ef1c35be8":"執行的 CPU", + "i18n_2f4aaddde3":"刪除", + "i18n_2f5e828ecd":"別名碼", + "i18n_2f5e885bc6":"獲取單個構建日誌地址", + "i18n_2f67a19f9d":"需要選發佈到集羣中的對應的服務名,需要提前去集羣中創建服務", + "i18n_2f6989595f":"管理列表:", + "i18n_2f8d6f1584":"昨天", + "i18n_2f8dc4fb66":"真的要釋放分發信息麼?釋放之後節點下面的項目信息還會保留,如需刪除項目還需要到節點管理中操作", + "i18n_2f8fd34058":"腳本模版是存儲在服務端中的命令腳本用於在線管理一些腳本命令,如初始化軟件環境、管理應用程序等", + "i18n_2f97ed65db":"佔用", + "i18n_2fc0d53656":"機器狀態(緩存)", + "i18n_2ff65378a4":"真的要刪除對應工作空間的 SSH 麼?", + "i18n_2fff079bc7":"發佈成功", + "i18n_3006a3da65":"系統版本:", + "i18n_300fbf3891":"發佈前停止是指在發佈文件到項目文件時先將項目關閉,再進行文件替換。避免 windows 環境下出現文件被佔用的情況", + "i18n_302ff00ddb":"超級管理員", + "i18n_3032257aa3":"詳情信息", + "i18n_30849b2e10":"進程/端口", + "i18n_30aaa13963":"序列號 (SN)", + "i18n_30acd20d6e":"用户ID", + "i18n_30d9d4f5c9":"新增關聯", + "i18n_30e6f71a18":"自定義標籤通配表達式", + "i18n_30e855a053":"取消分發", + "i18n_30ff009ab3":"# java 鏡像源 https://mirrors.tuna.tsinghua.edu.cn/Adoptium/", + "i18n_3103effdfd":"請輸入賬號名", + "i18n_31070fd376":"手動回滾", + "i18n_310c809904":"綁定到當前工作空間", + "i18n_312e044529":" :範圍:0(Sunday)~6(Saturday),7也可以表示週日,同時支持不區分大小寫的別名:_##_sun\",\"mon\", \"tue\", \"wed\",\"thu\",\"fri\", \"sat\",", + "i18n_312f45014a":"創建時間:", + "i18n_314f5aca4e":"單個觸發器地址中:第一個隨機字符串為構建ID,第二個隨機字符串為 token", + "i18n_315eacd193":"上移", + "i18n_31691a647c":"{slot1}端口", + "i18n_3174d1022d":"容器構建注意", + "i18n_3181790b4b":"服務端系統配置", + "i18n_318ce9ea8b":"用户密碼提示", + "i18n_31aaaaa6ec":"構建ID:", + "i18n_31ac8d3a5d":"線程同步器", + "i18n_31bca0fc93":"加入 beta 計劃可以及時獲取到最新的功能、一些優化功能、最快修復 bug 的版本,但是 beta版也可能在部分新功能上存在不穩定的情況。您需要根據您業務情況來評估是否可以加入 beta,在使用 beta版過程中遇到問題可以隨時反饋給我們,我們會盡快為您解答。", + "i18n_31eb055c9c":"並行度,同一時間升級的容器數量", + "i18n_31ecc0e65b":"項目", + "i18n_32112950da":"批量取消", + "i18n_3241c7c05f":"建議使用服務端腳本分發到腳本:", + "i18n_32493aeef9":"構建中", + "i18n_329e2e0b2e":"指定目錄打包 yarn && yarn --cwd xxx build", + "i18n_32a19ce88b":"控制枱日誌路徑", + "i18n_32ac152be1":"更新", + "i18n_32c65d8d74":"標題", + "i18n_32cb0ec70e":"請輸入節點名稱", + "i18n_32d0576d85":"的令牌", + "i18n_32dcc6f36e":"重啟策略:no、always、unless-stopped、on-failure", + "i18n_32e05f01f4":"集羣信息", + "i18n_32f882ae24":"匹配零個或多個字符", + "i18n_330363dfc5":"成功", + "i18n_3306c2a7c7":"讀取默認", + "i18n_33130f5c46":"操作成功", + "i18n_3322338140":"請選擇發佈後操作", + "i18n_332ba869d9":"一般用於節點環境一致的情況", + "i18n_334a1b5206":"安裝節點", + "i18n_335258331a":"已經讀取默認配置文件到編輯器中", + "i18n_33675a9bb3":"集羣關聯的 docker 信息丟失,不能繼續使用管理功能", + "i18n_339097ba2e":"準備分發", + "i18n_33c9e2388e":"項目ID", + "i18n_3402926291":"當前日誌文件大小:", + "i18n_346008472d":"匹配包含 異常 的行", + "i18n_3477228591":"鏡像", + "i18n_35134b6f94":"查看節點腳本", + "i18n_3517aa30c2":"腳本里面支持的變量有:{'${PROJECT_ID}'}、{'${PROJECT_NAME}'}、{'${PROJECT_PATH}'}", + "i18n_353707f491":"可以到【節點分發】=>【分發授權配置】修改", + "i18n_353c7f29da":"請選擇模版節點", + "i18n_35488f5ba8":"請選擇節點項目", + "i18n_354a3dcdbd":"30秒一次", + "i18n_3574d38d3e":"剩餘內存:", + "i18n_35b89dbc59":"確認要下載最新版本嗎?", + "i18n_35cb4b85a9":"【目前只使用匹配到的第一項】", + "i18n_35fbad84cb":"描述根據創建時間升序第一個", + "i18n_3604566503":"請填寫容器地址", + "i18n_364bea440e":"請選擇要引用的腳本", + "i18n_368ffad051":"{slot1}目錄", + "i18n_36b3f3a2f6":"報警標題", + "i18n_36b5d427e4":"請輸入工作空間描述", + "i18n_36d00eaa3f":"差異構建:", + "i18n_36d4046bd6":"引用腳本模板", + "i18n_36df970248":"# version 需要在對應鏡像源中存在", + "i18n_3711cbf638":"預佔資源", + "i18n_37189681ad":"數據Id", + "i18n_373a1efdc0":"請選中要關閉的項目", + "i18n_374cd1f7b7":"創建集羣", + "i18n_375118fad1":"物理節點腳本模板數據:", + "i18n_375f853ad6":"硬件信息", + "i18n_3787283bf4":"真的要刪除當前文件麼?", + "i18n_37b30fc862":"請選擇皮膚", + "i18n_37c1eb9b23":"配置文件路徑", + "i18n_37f031338a":"上傳壓縮包並自動解壓", + "i18n_37f1931729":"數據目錄佔用空間:", + "i18n_384f337da1":"同步機制採用", + "i18n_3867e350eb":"環境變量", + "i18n_386edb98a5":"自定義腳本項目(python、nodejs、go、接口探活、es)【推薦】", + "i18n_38a12e7196":"選擇證書文件", + "i18n_38aa9dc2a0":"更多配置", + "i18n_38ce27d846":"下一步", + "i18n_38cf16f220":"確定", + "i18n_38da533413":"下面命令將在", + "i18n_3904bfe0db":"設置一個超級管理員賬號", + "i18n_3929e500e0":"通常情況為項目遷移工作空間、遷移物理機器等一些操作可能產生孤獨數據", + "i18n_396b7d3f91":"文件大小", + "i18n_398ce396cd":"工作空間同步", + "i18n_39b68185f0":"節點地址為插件端的 IP:PORT 插件端端口默認為:2123", + "i18n_39c7644388":"端口號", + "i18n_39e4138e30":"集羣創建時間:", + "i18n_39f1374d36":"耗時", + "i18n_3a1052ccfc":"引用環境變量", + "i18n_3a17b7352e":"分鐘", + "i18n_3a3778f20c":"任務ID", + "i18n_3a3c5e739b":"批量構建參數", + "i18n_3a3ff2c936":"卷標籤", + "i18n_3a536dcd7c":"126郵箱", + "i18n_3a57a51660":"腳本版本:{item}", + "i18n_3a6000f345":"正在運行的線程同步器", + "i18n_3a6970ac26":"文件共享", + "i18n_3a6bc88ce0":"真的要刪除文件麼?", + "i18n_3a6c2962e1":"密鑰算法", + "i18n_3a71e860a7":"未開啟當前終端", + "i18n_3a94281b91":"自由腳本是指直接在機器節點中執行任意腳本", + "i18n_3aa69a563b":"節點分發是指,一個項目運行需要在多個節點(服務器)中運行,使用節點分發來統一管理這個項目(可以實現分佈式項目管理功能)", + "i18n_3ac34faf6d":"通配符", + "i18n_3adb55fbb5":"遷移工作空間", + "i18n_3ae4c953fe":"當定時任務運行到的時間匹配這些表達式後,任務被啟動。", + "i18n_3ae4ddf245":"真的要刪除該 Docker 麼?刪除只會檢查本地系統的數據關聯,不會刪除 docker 容器中數據", + "i18n_3aed2c11e9":"自動", + "i18n_3b14c524f6":"讀取次數", + "i18n_3b19b2a75c":"真的要刪除腳本麼?", + "i18n_3b885fca15":"緩存版本號", + "i18n_3b9418269c":"請填寫關聯容器標籤", + "i18n_3b94c70734":"項目狀態", + "i18n_3ba621d736":"處理成功", + "i18n_3baa9f3d72":"批量構建參數還支持指定參數,delay(延遲執行構建,單位秒)branchName(分支名)、branchTagName(標籤)、script(構建腳本)、resultDirFile(構建產物)、webhook(通知webhook)", + "i18n_3bc5e602b2":"郵箱", + "i18n_3bcc1c7a20":"最後修改人", + "i18n_3bdab2c607":"10分鐘", + "i18n_3bdd08adab":"描述", + "i18n_3bf3c0a8d6":"節點", + "i18n_3bf9c5b8af":" 分組名:", + "i18n_3c014532b1":"構建耗時:", + "i18n_3c070ea334":"如果關聯的構建關聯的倉庫被多個構建綁定(使用)不能遷移", + "i18n_3c48d9b970":"批量構建參數 BODY json: [ { \"id\":\"1\", \"token\":\"a\" } ]", + "i18n_3c586b2cc0":"自定義 host", + "i18n_3c6248b364":"緩存信息", + "i18n_3c6fa6f667":"cron表達式", + "i18n_3c8eada338":"請選擇編碼方式", + "i18n_3c91490844":"發佈操作", + "i18n_3c99ea4ec2":"例如 2,3,6/3中,由於“/”優先級高,因此相當於2,3,(6/3),結果與 2,3,6等價", + "i18n_3c9eeee356":"真的要刪除日誌文件麼?", + "i18n_3cc09369ad":"真的要刪除【", + "i18n_3d06693eb5":"資源:", + "i18n_3d0a2df9ec":"參數", + "i18n_3d3b918f49":"執行構建", + "i18n_3d3d3ed34c":"請輸入選擇關聯分組", + "i18n_3d43ff1199":"置頂", + "i18n_3d48c9da09":"授權配置", + "i18n_3d61e4aaf1":"指定標籤", + "i18n_3d6acaa5ca":"這個容器沒有網絡", + "i18n_3d83a07747":"主機 Host", + "i18n_3dc5185d81":"私有", + "i18n_3dd6c10ffd":"上傳升級包", + "i18n_3e445d03aa":"文件不存在啦", + "i18n_3e51d1bc9c":"請選擇發佈的SSH", + "i18n_3e54c81ca2":"接收流量", + "i18n_3e7ef69c98":"監控操作", + "i18n_3e8c9c54ee":"選擇分組", + "i18n_3ea6c5e8ec":"分發結束", + "i18n_3eab0eb8a9":"本地腳本", + "i18n_3ed3733078":"終端日誌", + "i18n_3edddd85ac":"日", + "i18n_3ee7756087":"請先選擇節點", + "i18n_3f016aa454":"鏡像標籤:", + "i18n_3f18d14961":"兩步驗證碼", + "i18n_3f1d478da4":"服務端腳本、SSH腳本可以使用 G{'@'}(\"xxxx\") 格式來引用,當存在引用時系統會自動替換引用腳本庫中的腳本內容", + "i18n_3f2d5bd6cc":"在文件第 2 - 2 行中搜索", + "i18n_3f414ade96":"參數描述,{slot1},僅是用於提示參數的含義", + "i18n_3f553922ae":"】目錄和文件麼?", + "i18n_3f5af13b4b":"# scriptId 可以是項目路徑下腳本文件名或者系統中的腳本模版ID", + "i18n_3f719b3e32":"衝突數", + "i18n_3f78f88499":"打包時間:", + "i18n_3f8b64991f":"解壓時候自動剔除壓縮包裏面多餘的文件夾名", + "i18n_3f8cedd1d7":"用於靜態文件綁定和讀取(不建議配置大目錄,避免掃描消耗過多資源)", + "i18n_3fb2e5ec7b":"登錄日誌", + "i18n_3fb63afb4e":"退出碼", + "i18n_3fbdde139c":"確認密碼", + "i18n_3fca26a684":"批量觸發參數 BODY json: [ { \"id\":\"1\", \"token\":\"a\" } ]", + "i18n_3fea7ca76c":"狀態", + "i18n_402d19e50f":"登錄", + "i18n_40349f5514":"數:", + "i18n_4055a1ee9c":"通用的字段有:createTimeMillis、modifyTimeMillis", + "i18n_406a2b3538":"何為孤獨數據", + "i18n_4089cfb557":"關聯分組主要用於資產監控來實現不同服務端執行不同分組下面的資產監控", + "i18n_40aff14380":"鏡像ID", + "i18n_40da3fb58b":"新建狀態", + "i18n_40f8c95345":"臨時文件目錄", + "i18n_411672c954":"請輸入文件描述", + "i18n_412504968d":"當目標工作空間不存在對應的 SSH 時候將自動創建一個新的 SSH", + "i18n_41298f56a3":"構建失敗", + "i18n_413d8ba722":"舊版程序包占有空間:", + "i18n_413f20d47f":"系統 採用 oshi 庫來監控系統,在 oshi 中使用 /proc/meminfo 來獲取內存使用情況。", + "i18n_41638b0a48":"用於區別文件是否為同一類型,可以針對同類型進行下載管理", + "i18n_417fa2c2be":"參數{index}描述", + "i18n_4188f4101c":"沒有docker", + "i18n_41d0ecbabd":"Block IO 權重", + "i18n_41e8e8b993":"深色", + "i18n_41e9f0c9c6":"工作節點", + "i18n_41fdb0c862":"請先上傳或者下載新版本", + "i18n_4244830033":"請選擇證書文件", + "i18n_424a2ad8f7":"準備", + "i18n_429b8dfb98":"項目分發", + "i18n_42a93314b4":"基礎鏡像", + "i18n_42b6bd1b2f":"倉庫路徑", + "i18n_42f766b273":"掛載分區", + "i18n_42fd64c157":"先啟動", + "i18n_4310e9ed7d":"請選擇項目運行方式", + "i18n_43250dc692":"觸發器管理", + "i18n_434d888f6f":"請選擇文件中心的文件", + "i18n_434d9bd852":"創建用户後自動關聯上對應的權限組", + "i18n_4360e5056b":"加載數據中", + "i18n_436367b066":"項目管理", + "i18n_4371e2b426":"請輸入項目名稱", + "i18n_43886d7ac3":"新增運行參數", + "i18n_4393b5e25b":"環回", + "i18n_43c61e76e7":"注意:目前對 SSH key 訪問 git 倉庫地址不支持使用 ssh-keygen -t rsa -C", + "i18n_43d229617a":"待選擇", + "i18n_43e534acf9":"寬鬆", + "i18n_43ebf364ed":"請選擇備份類型", + "i18n_4403fca0c0":"清除", + "i18n_44473c1406":"開啟緩存構建目錄將保留倉庫文件,二次構建將 pull 代碼, 不開啟緩存目錄每次構建都將重新拉取倉庫代碼(較大的項目不建議關閉緩存) 、特別説明如果緩存目錄中缺失版本控制相關文件將自動刪除後重新拉取代碼", + "i18n_4482773688":"請輸入權限組名稱", + "i18n_44876fc0e7":"如果不可以選擇則表示對應的用户沒有配置郵箱", + "i18n_449fa9722b":"為了考慮系統安全我們強烈建議超級管理員開啟兩步驗證來確保賬號的安全性", + "i18n_44a6891817":"新增構建", + "i18n_44c4aaa1d9":"運行模式", + "i18n_44d13f7017":"限定時間", + "i18n_44ed625b19":"網絡異常", + "i18n_44ef546ded":"項目監控 【暫不支持遷移】", + "i18n_44efd179aa":"退出登錄", + "i18n_45028ad61d":"證書密碼", + "i18n_4524ed750d":"工作空間名", + "i18n_456d29ef8b":"日誌", + "i18n_458331a965":"確認要上傳文件更新到最新版本嗎?", + "i18n_45a4922d3f":"關聯數據", + "i18n_45b88fc569":"匹配路徑中的零個或多個目錄", + "i18n_45f8d5a21d":"真的要刪除用户麼?", + "i18n_45fbb7e96a":"項目孤獨數據", + "i18n_46032a715e":"還沒有選擇構建方式", + "i18n_4604d50234":"錯誤信息", + "i18n_46097a1225":"修正孤獨數據", + "i18n_46158d0d6e":"禁用監控", + "i18n_461e675921":"當前數據為默認狀態,操後上移或者下移可能不會達到預期排序,還需要對相關數據都操作後才能達到預期排序", + "i18n_461ec75a5a":"路徑:", + "i18n_461fdd1576":"打包生產環境包 mvn clean package -Dmaven.test.skip=true -Pprod", + "i18n_4637765b0a":"未啟用", + "i18n_463e2bed82":"批量更新", + "i18n_4642113bba":"點擊儀表盤查看監控歷史數據", + "i18n_4645575b77":"工作空間描述", + "i18n_464f3d4ea3":"角色", + "i18n_465260fe80":"年", + "i18n_4696724ed3":"觸發器", + "i18n_46a04cdc9c":"文件描述:", + "i18n_46aca09f01":"解綁會檢查數據關聯性,不會真實請求節點刪除項目信息", + "i18n_46ad87708f":"ssh名稱", + "i18n_46c8ba7b7f":"如果按鈕不可用,請去資產管理 ssh 列表的關聯中新增當前工作空間允許管理的授權文件夾", + "i18n_46e3867956":"執行中", + "i18n_46e4265791":"構建 ID", + "i18n_4705b88497":"作用域", + "i18n_47072e451e":"管理節點:", + "i18n_470e9baf32":"允許執行的內存節點", + "i18n_471c6b19cf":"遷移前您檢查遷出機器和遷入機器的連接狀態和網絡狀態避免未知錯誤或者中斷造成流程失敗產生宂餘數據!!!!", + "i18n_4722bc0c56":"終端", + "i18n_473badc394":"發佈的節點", + "i18n_4741e596ac":"報警時間", + "i18n_475a349f32":"當前構建還沒有生成觸發器", + "i18n_475cd76aec":"統計的網卡:", + "i18n_47768ed092":"極不安全", + "i18n_47bb635a5c":"數據可能出現一定時間延遲", + "i18n_47d68cd0f4":"服務", + "i18n_47dd8dbc7d":"搜索項目ID", + "i18n_47e4123886":"新增分發", + "i18n_47ff744ef6":"編輯文件", + "i18n_481ffce5a9":"匹配秒", + "i18n_4826549b41":"命令模版是用於在線管理一些腳本命令,如初始化軟件環境、管理應用程序等", + "i18n_48281fd3f0":"真的要刪除構建信息麼?刪除也將同步刪除所有的構建歷史記錄信息", + "i18n_4838a3bd20":"按住 Ctr 或者 Alt/Option 鍵點擊按鈕快速回到第一頁", + "i18n_4871f7722d":"任務更新時間", + "i18n_48735a5187":"剩餘空間(未分配)", + "i18n_48a536d0bb":"修改容器配置,重新運行", + "i18n_48d0a09bdd":"淺色", + "i18n_48e79b3340":"】文件麼?", + "i18n_48fe457960":"(存在兼容問題,實際使用中需要提前測試) go sdk 鏡像使用:https://studygolang.com/dl/golang/go{'${GO_VERSION}'}.linux-{'${ARCH}'}.tar.gz", + "i18n_4956eb6aaa":"負載", + "i18n_49574eee58":"確定要操作嗎?", + "i18n_49645e398b":"如果配置錯誤需要重啟服務端並新增命令行參數 --rest:ip_config 將恢復默認配置", + "i18n_497bc3532b":"JVM 參數", + "i18n_497ddf508a":"新建空白文件", + "i18n_498519d1af":"刷新數據", + "i18n_499f058a0b":"退出登錄成功", + "i18n_49a9d6c7e6":"通過以下二維碼進行一次性捐款贊助,請作者喝一杯咖啡☕️", + "i18n_49d569f255":"請輸入要檢查的 host", + "i18n_49e56c7b90":"確認修改", + "i18n_4a00d980d5":"簡單好用", + "i18n_4a0e9142e7":"釘釘", + "i18n_4a346aae15":"插件版本:", + "i18n_4a4e3b5ae4":"描述:", + "i18n_4a5ab3bc72":"操作:", + "i18n_4a6f3aa451":" :每個點鐘的5分執行,00:05,01:05……", + "i18n_4a98bf0c68":"任務詳情", + "i18n_4aac559105":"權重", + "i18n_4ab578f3df":"環境變量:", + "i18n_4ad6e58ebc":"機器SSH", + "i18n_4af980516d":"為了您的賬號安全系統要求必須開啟兩步驗證來確保賬號的安全性", + "i18n_4b027f3979":"提醒", + "i18n_4b0cb10d18":"請輸入 SMTP host", + "i18n_4b1835640f":"在 Settings-->Developer settings-->Personal access tokens 中獲取", + "i18n_4b386a7209":"獲取變量值地址", + "i18n_4b404646f4":"容器標籤,如:key1=values1&keyvalue2", + "i18n_4b5e6872ea":"駐留集", + "i18n_4b96762a7e":"最後修改時間", + "i18n_4b9c3271dc":"重置", + "i18n_4ba304e77a":"釘釘賬號登錄", + "i18n_4bbc09fc55":"在文件第 3 - 20 行中搜索", + "i18n_4c096c51a3":"端口號:", + "i18n_4c0eead6ff":"新增參數", + "i18n_4c28044efc":"確認要將選中的 ", + "i18n_4c69102fe1":"再判斷允許時段。配置允許時段後用户只能在對應的時段執行相應功能的操作", + "i18n_4c7c58b208":"請選擇節點狀態", + "i18n_4c7e4dfd33":"當目標工作空間不存在對應的節點時候將自動創建一個新的docker(邏輯docker)", + "i18n_4c83203419":"跳轉到第三方系統中", + "i18n_4c9bb42608":"前綴", + "i18n_4cbc136874":"文件夾:", + "i18n_4cbc5505c7":"差異構建是指構建時候是否判斷倉庫代碼有變動,如果沒有變動則不執行構建", + "i18n_4ccbdc5301":"菜單", + "i18n_4cd49caae4":"分發耗時", + "i18n_4ce606413e":"倉庫類型", + "i18n_4cfca88db8":"選擇分發文件", + "i18n_4d18dcbd15":"真的要還原備份信息麼?", + "i18n_4d351f3c91":"禁止 IP", + "i18n_4d49b2a15f":"自動執行:docker", + "i18n_4d775d4cd7":"顯示", + "i18n_4d7dc6c5f8":"寫", + "i18n_4d85ac1250":"系統管理", + "i18n_4d85c37f0d":"工作空間:", + "i18n_4d9c3a0ed0":"Script 內容", + "i18n_4dc781596b":"中使用瞭如下開源軟件,我們衷心感謝有了他們的開源 Jpom 才能更完善", + "i18n_4df483b9c7":"項目文件 ", + "i18n_4e33dde280":"當前目錄:", + "i18n_4e54369108":"文件類型沒有觸發器功能", + "i18n_4e7e04b15d":"服務名稱必填", + "i18n_4ed1662cae":"請選擇連接方式", + "i18n_4ee2a8951d":"接口響應 ContentType 均為:text/plain", + "i18n_4ef719810b":"沒有任何運行中的任務", + "i18n_4effdeb1ff":"在文件第 1 - 2 行中搜索", + "i18n_4f08d1ad9f":"算法 OID", + "i18n_4f095befc0":"此配置僅對服務端管理生效, 工作空間的 ssh 配置需要單獨配置", + "i18n_4f35e80da6":"路徑", + "i18n_4f4c28a1fb":"文件內容格式要求:env_name=xxxxx 不滿足格式的行將自動忽略", + "i18n_4f50cd2a5e":"緊湊模式", + "i18n_4f52df6e44":"關閉中", + "i18n_4f8a2f0b28":"未運行", + "i18n_4f8ca95e7b":"名", + "i18n_4f9e3db4b8":"選擇構建", + "i18n_4fb2400af7":"容器是運行中可以進入終端", + "i18n_4fb95949e5":"開啟中", + "i18n_4fdd2213b5":"項目 ID", + "i18n_500789168c":"清空還原將會先刪除項目目錄中的文件再將對應備份文件恢復至當前目錄", + "i18n_5011e53403":"發佈集羣", + "i18n_503660aa89":"排除:", + "i18n_50411665d7":"保留個數", + "i18n_50453eeb9e":"當前工作空間還沒有邏輯節點不能創建節點腳本奧", + "i18n_504c43b70a":"端口/PID", + "i18n_5068552b18":"歷史監控圖表", + "i18n_50940ed76f":"下載成功", + "i18n_50951f5e74":"請選擇分支", + "i18n_50a299c847":"構建名稱", + "i18n_50c7929dd9":" 歡迎 ", + "i18n_50d2671541":"確定是同一個腳本", + "i18n_50ed14e70b":"深色 dracula", + "i18n_50f472ee4e":"單位秒,默認 10 秒,最小 3 秒", + "i18n_50f975c08e":"構建產物保留天數,小於等於 0 為跟隨全局保留配置。注意自動清理僅會清理記錄狀態為:(構建結束、發佈中、發佈失敗、發佈失敗)的數據避免一些異常構建影響保留個數", + "i18n_50fb61ef9d":"腳本名稱", + "i18n_50fe3400c7":"真的要刪除該執行記錄嗎?", + "i18n_50fefde769":"是否為壓縮包", + "i18n_512e1a7722":"請選擇操作者", + "i18n_51341b5024":"服務端分發的腳本", + "i18n_514b320d25":"如何選擇構建方式", + "i18n_5169b9af9d":"信息丟失", + "i18n_5177c276a0":"集羣不能手動創建,創建需要多個服務端使用通一個數據庫,並且配置不同的集羣 id 來自動創建集羣信息", + "i18n_518df98392":"從尾搜索", + "i18n_5195c0d198":"可以管理{count}個工作空間", + "i18n_51c92e6956":"同步系統配置", + "i18n_51d47ddc69":"回調 url", + "i18n_51d6b830d4":"在線構建目錄", + "i18n_52409da520":"聯繫人", + "i18n_527466ff94":"請求參數", + "i18n_527f7e18f1":"上傳前請閲讀更新日誌裏面的説明和注意事項並且更新前", + "i18n_52a8df6678":"】文件夾麼?", + "i18n_52b526ab9e":"清空瀏覽器緩存配置將恢復默認", + "i18n_52b6b488e2":"腳本模版是存儲在節點(插件端),執行也都將在節點裏面執行,服務端會定時去拉取執行日誌,拉取頻率為 100 條/分鐘", + "i18n_52c6af8174":"請輸入客户端密鑰 [clientSecret]", + "i18n_52d24791ab":"真的要刪除這些文件麼?", + "i18n_52eedb4a12":"報警方式", + "i18n_52ef46c618":"不發佈:只執行構建流程並且保存構建歷史", + "i18n_532495b65b":"副本數", + "i18n_53365c29c8":"下載狀態:", + "i18n_534115e981":"信息不完整不能編輯", + "i18n_5349f417e9":"搜關鍵詞", + "i18n_536206b587":"當前機器還未監控到任何數據", + "i18n_537b39a8b5":"必填", + "i18n_53bdd93fd6":"查看腳本庫", + "i18n_541e8ce00c":"關於開源軟件", + "i18n_542a0e7db4":"同步授權", + "i18n_543296e005":"請輸入授權 url [authorizationUri]", + "i18n_543a5aebc8":"真的刪除當前變量嗎?", + "i18n_543de6ff04":"分發狀態消息", + "i18n_54506fe138":"重置選擇", + "i18n_5457c2e99f":"# 使用 copy 文件的方式緩存,反之使用軟鏈的形式。copy 文件方式緩存 node_modules 可以避免 npm WARN reify Removing non-directory", + "i18n_547ee197e5":"新建目錄", + "i18n_5488c40573":"節點項目", + "i18n_54f271cd41":"腳本模板", + "i18n_5516b3130c":"飛書賬號登錄", + "i18n_551e46c0ea":" 名稱: ", + "i18n_55405ea6ff":"導出", + "i18n_556499017a":"項目文件會存放到", + "i18n_5569a840c8":"請輸入IP禁止,多個使用換行,支持配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_55721d321c":"參數描述", + "i18n_55939c108f":"輸入文件或者文件夾名", + "i18n_55abea2d61":"服務端", + "i18n_55b2d0904f":"在執行多節點分發時候使用 順序重啟、完整順序重啟 時候需要保證項目能正常重啟", + "i18n_55cf956586":"加入集羣", + "i18n_55d4a79358":"配置需要聲明使用具體的 docker 來執行構建相關操作(建議使用服務端所在服務器中的 docker)", + "i18n_55da97b631":" ,範圍0~59,但是第一位不做匹配當為7位時,最後一位表示", + "i18n_55e690333a":"當前工作空間還沒有 Docker 集羣", + "i18n_55e99f5106":"釘釘通知地址", + "i18n_55f01e138a":"微信讚賞", + "i18n_56071a4fa6":"超時時間", + "i18n_56230405ae":"解綁不會真實請求節點刪除腳本信息", + "i18n_562d7476ab":"週日", + "i18n_56469e09f7":"請到【系統管理】-> 【資產管理】-> 【Docker管理】新增Docker,或者將已新增的Docker授權關聯、分配到此工作空間", + "i18n_56525d62ac":"掃描", + "i18n_566c67e764":"已經分配到工作空間的機器無非直接刪除,需要到分配到的各個工作空間逐一刪除後才能刪除資產機器", + "i18n_5684fd7d3d":"賬號新密碼為:", + "i18n_56bb769354":"下載前請閲讀更新日誌裏面的説明和注意事項並且更新前", + "i18n_56d9d84bff":"工作空間中邏輯節點中的項目數量:", + "i18n_570eb1c04f":"硬盤佔用率:", + "i18n_5734b2db4e":"讀取行數", + "i18n_576669e450":"請選中要啟動的項目", + "i18n_5785f004ea":"請勿手動刪除數據目錄下面文件,如果需要刪除需要提前備份或者已經確定對應文件棄用後才能刪除", + "i18n_578adf7a12":"請仔細確認後配置,ip配置後立即生效。配置時需要保證當前ip能訪問!127.0.0.1 該IP不受訪問限制.支持配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_578ca5bcfd":"163郵箱", + "i18n_57978c11d1":"日誌彈窗會非全屏打開", + "i18n_579a6d0d92":"命令值", + "i18n_57b7990b45":"當目標工作空間已經存在 SSH 時候將自動同步 SSH 賬號、密碼、私鑰等信息", + "i18n_57c0a41ec6":"當前數據為默認狀態", + "i18n_57cadc4cf3":"會使用 PING 檢查", + "i18n_5805998e42":"重啟策略", + "i18n_5854370b86":"跟蹤文件", + "i18n_585ae8592f":"重建容器", + "i18n_5866b4bced":"集羣數:", + "i18n_587a63264b":"覆蓋還原", + "i18n_588e33b660":"賬號如果開啟 MFA(兩步驗證),使用 Oauth2 登錄不會驗證 MFA(兩步驗證)", + "i18n_589060f38e":"升級中,請稍候...", + "i18n_5893fa2280":"郵箱賬號", + "i18n_58cbd04f02":"SSH 是指,通過 SSH 命令的方式對產物進行發佈或者執行多條命令來實現發佈(需要到 SSH 中提前去新增)", + "i18n_58e998a751":"刪除會檢查數據關聯性,並且節點不存在項目或者腳本", + "i18n_58f9666705":"大小", + "i18n_590b9ce766":"目前支持都插件有(更多插件盡情期待):", + "i18n_590dbb68cf":"結束時間:", + "i18n_590e5b46a0":"自動備份", + "i18n_592c595891":"開始時間", + "i18n_5936ed11ab":"腳本庫用於存儲管理通用的腳本,腳本庫中的腳本不能直接執行。", + "i18n_593e04dfad":"菜單主題", + "i18n_597b1a5130":"更新狀態", + "i18n_59a15a0848":"同步機制採用 IP+PORT+連接方式 確定是同一個服務器", + "i18n_59c316e560":"分發文件", + "i18n_59c75681b4":"通知對象", + "i18n_59d20801e9":"在文件第 17 - 20 行中搜索", + "i18n_5a0346c4b1":"編輯用户", + "i18n_5a1367058c":"返回首頁", + "i18n_5a1419b7a2":"數據名稱", + "i18n_5a42ea648d":"自建 gitlab 訪問地址", + "i18n_5a5368cf9b":"密碼錯誤", + "i18n_5a63277941":"的值有:stop、beforeStop、start、beforeRestart、fileChange", + "i18n_5a7ea53d18":"docker信息", + "i18n_5a8727305e":"請不要優先退出管理節點", + "i18n_5a879a657b":"交換內存", + "i18n_5aabec5c62":"父級ID", + "i18n_5ab90c17a3":"任務結束", + "i18n_5ad7f5a8b2":"結果", + "i18n_5afe5e7ed4":"編輯關聯項目", + "i18n_5b1f0fd370":"用於創建節點分發項目、文件中心發佈文件", + "i18n_5b3ffc2910":"分發中", + "i18n_5b47861521":"名稱:", + "i18n_5baaef6996":"點擊重新同步當前工作空間邏輯節點腳本模版信息", + "i18n_5badae1d90":"沒有任何腳本", + "i18n_5bb162ecbb":"JVM剩餘內存", + "i18n_5bb5b33ae4":"所以這裏 我們", + "i18n_5bca8cf7ee":"自定義host, xxx:192.168.0.x", + "i18n_5bcda1b4d7":"會話已經關閉[system-log]", + "i18n_5bd1d267a9":"在 preferences-->Access Tokens 中獲取", + "i18n_5c3b53e66c":"修改文件", + "i18n_5c4d3c836f":"需要驗證 MFA", + "i18n_5c502af799":"容器名稱必填", + "i18n_5c56a88945":"停用", + "i18n_5c89a5353d":"分配節點", + "i18n_5c93055d9c":"一般用於服務器無法連接且已經確定不再使用", + "i18n_5ca6c1b6c7":"請填寫集羣名稱", + "i18n_5cb39287a8":"監控功能", + "i18n_5cc7e8e30a":"修改文件權限", + "i18n_5d07edd921":"請填寫集羣IP", + "i18n_5d14e91b01":"主要ID", + "i18n_5d368ab0a5":"執行命令將自動替換為 sh 命令文件、並自動加載環境變量:/etc/profile、/etc/bashrc、~/.bashrc、~/.bash_profile", + "i18n_5d414afd86":"從尾搜索、文件前2行、文件後3行", + "i18n_5d459d550a":"處理中", + "i18n_5d488af335":"遠程下載文件", + "i18n_5d5fd4170f":"的值有:1", + "i18n_5d6f47d670":"項目為靜態文件夾", + "i18n_5d803afb8d":"不能和節點正常通訊", + "i18n_5d817c403e":"沒有選擇任何數據", + "i18n_5d83794cfa":"節點名稱:", + "i18n_5d9c139f38":"內容主題", + "i18n_5dc09dd5bd":"重連 ", + "i18n_5dc1f36a27":"證書描述", + "i18n_5dc78cb700":"構建產物保留個數,小於等於 0 為跟隨全局保留配置(如果數值大於 0 將和全局配置對比最小值來參考)。注意自動清理僅會清理記錄狀態為:(構建結束、發佈中、發佈失敗、發佈失敗)的數據避免一些異常構建影響保留個數。 將在創建新的構建記錄時候檢查保留個數", + "i18n_5dc7b04caa":"查看的進程數量", + "i18n_5dff0d31d0":"如果需要定時自動執行則填寫,cron 表達式.默認未開啟秒級別,需要去修改配置文件中:[system.timerMatchSecond])", + "i18n_5e32f72bbf":"刷新文件表格", + "i18n_5e46f842d8":"監控用户", + "i18n_5e9f2dedca":"是否成功", + "i18n_5ecc709db7":"執行時候默認不加載全部環境變量、需要腳本里面自行加載", + "i18n_5ed197a129":"重置初始化在啟動時候傳入參數", + "i18n_5ef040a79d":"丟棄包", + "i18n_5ef72bdfce":"命令內容支持工作空間環境變量", + "i18n_5effe31353":"剔除文件夾", + "i18n_5f4c724e61":"請輸入任務名", + "i18n_5f5cd1bb1e":"新增關聯項目是指,將已經在節點中創建好的項目關聯為節點分發項目來實現統一管理", + "i18n_5fafcadc2d":"會話已經關閉[node-script-consloe]", + "i18n_5fbde027e3":"可以引用工作空間的環境變量 變量佔位符 {'${xxxx}'} xxxx 為變量名稱", + "i18n_5fc6c33832":" 跳至行", + "i18n_5fea80e369":"沒有資產DOCKER", + "i18n_5fffcb255d":"插件運行", + "i18n_601426f8f2":"推送到倉庫", + "i18n_603dc06c4b":"您訪問的頁面不存在", + "i18n_60585cf697":" 歡迎", + "i18n_607558dbd4":"項目數", + "i18n_607e7a4f37":"查看", + "i18n_609b5f0a08":"時", + "i18n_60b4c08f5c":"您確定要停止當前容器嗎?", + "i18n_6106de3d87":"JDK版本", + "i18n_61341628ab":" :表示列表", + "i18n_6143a714d0":"編碼格式", + "i18n_616879745d":"凌晨0點和中午12點", + "i18n_61955b0e4b":"沒有項目狀態以及控制等功能", + "i18n_61a3ec6656":"介紹", + "i18n_61bfa4e925":"需要在倉庫裏面 dockerfile,如果多文件夾查看可以指定二級目錄如果 springboot-test-jar:springboot-test-jar/Dockerfile", + "i18n_61c0f5345d":"SMTP 地址:【smtp.163.com, smtp.126.com...】,密碼是郵箱授權碼,端口默認 25,SSL 端口 465", + "i18n_61e7fa1227":"編輯節點", + "i18n_61e84eb5bb":"開始時間:", + "i18n_620489518c":"參數{index}值", + "i18n_620efec150":"更多開源説明", + "i18n_62170d5b0a":"搜索參考", + "i18n_6228294517":"菜單配置只對非超級管理員生效", + "i18n_622d00a119":"執行腳本的路徑", + "i18n_624f639f16":"通用郵箱", + "i18n_625aa478e2":"從尾搜索、文件前0行、文件後3行", + "i18n_625fb26b4b":"取消", + "i18n_627c952b5e":"總空間", + "i18n_6292498392":" 查找下一個", + "i18n_629a6ad325":"安全管理", + "i18n_629f3211ca":"修剪類型", + "i18n_631d5b88ab":"請輸入項目存放路徑授權,回車支持輸入多個路徑,系統會自動過濾 ../ 路徑、不允許輸入根路徑", + "i18n_632a907224":"重置為重新生成觸發地址,重置成功後之前的觸發器地址將失效,觸發器綁定到生成觸發器到操作人上,如果將對應的賬號刪除觸發器將失效", + "i18n_6334eec584":"5秒一次", + "i18n_635391aa5d":"下載產物", + "i18n_637c9a8819":"至少選擇1個節點項目", + "i18n_638cddf480":"創建人,全匹配", + "i18n_639fd37242":"目前使用的 docker swarm 集羣,需要先創建 swarm 集羣才能選擇", + "i18n_63b6b36c71":"選擇證書", + "i18n_63c9d63eeb":"可以同時展開多個菜單", + "i18n_63dd96a28a":"密碼支持引用工作空間變量:", + "i18n_63e975aa63":"安裝ID:", + "i18n_640374b7ae":"掛載卷", + "i18n_641796b655":"構建完成", + "i18n_6428be07e9":"配置系統公吿", + "i18n_643f39d45f":"非懸空", + "i18n_6446b6c707":"暱稱長度為2-10", + "i18n_646a518953":"請輸入項目ID", + "i18n_6470685fcd":":表示匹配這個位置任意的時間(與_##_*\"作用一致)", + "i18n_649231bdee":"文件後綴", + "i18n_64933b1012":"存儲選項", + "i18n_6496a5a043":"命令名稱", + "i18n_649d7fcb73":"新集羣需要手動配置集羣管理資產分組、集羣訪問地址", + "i18n_649d90ab3c":"關閉右側", + "i18n_649f8046f3":"請選擇SSH節點", + "i18n_64c083c0a9":"結果描述", + "i18n_64eee9aafa":"開機時間", + "i18n_652273694e":"主機", + "i18n_65571516e2":"構建備註:", + "i18n_657969aa0f":"編輯 Docker", + "i18n_657f3883e3":"不執行發佈流程", + "i18n_65894da683":"發佈方式:", + "i18n_65cf4248a8":"不能初始化", + "i18n_65f66dfe97":"清空當前緩衝區內容", + "i18n_66238e0917":"已經存在的賬號與外部系統賬號不一致時不支持綁定外部系統賬號", + "i18n_663393986e":"解綁", + "i18n_6636793319":"真的要刪除節點麼?刪除會檢查數據關聯性,並且節點不存在項目或者腳本", + "i18n_664c205cc3":"真的要清除倉庫隱藏字段信息麼?(密碼,私鑰)", + "i18n_667fa07b52":"個節點升級到", + "i18n_66aafbdb72":"最新構建ID", + "i18n_66ab5e9f24":"新增", + "i18n_66b71b06c6":"上傳壓縮文件(自動解壓)", + "i18n_66c15f2815":"匹配包含數字的行", + "i18n_66e9ea5488":"日誌名稱", + "i18n_6707667676":"主機名", + "i18n_6709f4548f":"隨機生成", + "i18n_67141abed6":"項目授權路徑+項目文件夾", + "i18n_67425c29a5":"超時時間(s)", + "i18n_674a284936":"當isMatchSecond為 true 時才會匹配秒部分默認都是關閉的", + "i18n_674e7808b5":"mfa 驗證碼", + "i18n_679de60f71":"請填寫日誌項目名稱", + "i18n_67aa2d01b9":"工作空間的菜單、環境變量、節點分發授權需要逐一配置", + "i18n_67b667bf98":"部分備份", + "i18n_67e3d3e09c":"批量構建", + "i18n_67e7f9e541":"監控週期", + "i18n_6816da19f3":"關閉其他", + "i18n_6835ed12b9":"環境變量的key", + "i18n_685e5de706":"容器構建", + "i18n_6863e2a7b5":"腳本執行歷史", + "i18n_686a19db6a":"自動刪除", + "i18n_68a1faf6e2":"批量構建傳入其他參數將同步執行修改", + "i18n_68af00bedb":"表格視圖才能使用工作空間同步功能", + "i18n_68c55772ca":"請輸入授權方的網頁應用ID", + "i18n_69056f4792":"部分操作狀態碼可能為 0", + "i18n_690a3d1a69":"執行容器", + "i18n_691b11e443":"當前工作空間", + "i18n_6928f50eb3":"支持配置系統參數:", + "i18n_69384c9d71":"點擊查看歷史趨勢", + "i18n_693a06987c":"請填寫用户賬號", + "i18n_6948363f65":"取消定時,不再定時執行(支持 ! 前綴禁用定時執行,如:!0 0/1 * * * ?)", + "i18n_694fc5efa9":"刷新", + "i18n_695344279b":"文件上傳id生成失敗:", + "i18n_6953a488e3":"選擇邏輯節點", + "i18n_697d60299e":"檢測到當前已經登錄賬號", + "i18n_69c3b873c1":"本地構建", + "i18n_69c743de70":"節點的IP", + "i18n_69de8d7f40":"還原", + "i18n_6a359e2ab3":"scriptId也可以引入腳本庫中的腳本,需要提前同步至機器節點中", + "i18n_6a49f994b1":"構建過程執行對應的腳本,開始構建,構建完成,開始發佈,發佈完成,構建異常,發佈異常", + "i18n_6a4a0f2b3b":"同步機制採用節點地址確定是同一個服務器(節點)", + "i18n_6a588459d0":"工作空間名稱", + "i18n_6a620e3c07":"同步", + "i18n_6a658517f3":"任務日誌", + "i18n_6a66d4cdf3":"延遲,容器回滾間隔時間", + "i18n_6a6c857285":"分發節點", + "i18n_6a8402afcb":"解析文件,準備上傳中 ", + "i18n_6a8c30bd06":"加載編輯器中", + "i18n_6a922e0fb6":"插件端端口", + "i18n_6a9231c3ba":"函數 args 參數,非必填", + "i18n_6aa7403b18":"如果使用 SSH 方式但是 SSH 無法選擇,是表示系統沒有監測到 docker 服務", + "i18n_6aab88d6a3":"保存並重啟", + "i18n_6ab78fa2c4":"郵箱地址", + "i18n_6ac61b0e74":"建議還原和當前版本一致的文件或者臨近版本的文件", + "i18n_6ad02e7a1b":"頁面資源加載中....", + "i18n_6adcbc6663":"配置方式:SSH列表->操作欄中->關聯按鈕->對應工作空間->操作欄中->配置按鈕", + "i18n_6af7686e31":"分鐘刷新一次", + "i18n_6b0bc6432d":"操作者", + "i18n_6b189bf02d":"容器數:", + "i18n_6b29a6e523":"啟動項目", + "i18n_6b2e348a2b":"定時執行", + "i18n_6b46e2bfae":"真的當前工作空間麼", + "i18n_6b4fd0ca47":"支持配置發送方:遵循RFC-822標準 發件人可以是以下形式:", + "i18n_6b6d6937d7":"163郵箱 SSL", + "i18n_6bb5ba7438":"限制禁止在在線終端執行的命令", + "i18n_6be30eaad7":"請輸入超時時間", + "i18n_6bf1f392c0":"當前狀態", + "i18n_6c08692a3a":"密碼若沒修改可以不用填寫", + "i18n_6c14188ba0":"不能下載目錄", + "i18n_6c24533675":"請選擇一位報警聯繫人或者填寫webhook", + "i18n_6c72e9d9de":"編輯分發項目", + "i18n_6c776e9d91":"項目啟動,停止,重啟,文件變動都將請求對應的地址,非必填,GET請求", + "i18n_6d5f0fb74b":"鏡像構建成功後是否需要推送到遠程倉庫", + "i18n_6d68bd5458":"全量備份", + "i18n_6d7f0f06be":"請選擇發佈操作", + "i18n_6d802636ab":"隱私", + "i18n_6da242ea50":"任務Id", + "i18n_6dcf6175d8":"現在就去", + "i18n_6de1ecc549":"查看服務端腳本", + "i18n_6e02ee7aad":"週期的長度,以微秒為單位。", + "i18n_6e2d78a20e":"從尾搜索、文件前20行、文件後3行", + "i18n_6e60d2fc75":"頁面啟用緊湊模式", + "i18n_6e69656ffb":"文件不能為空", + "i18n_6e70d2fb91":"構建參數,如:key1=value1&key2=value2", + "i18n_6ea1fe6baa":"基礎信息", + "i18n_6eb39e706c":"編輯機器", + "i18n_6ef90ec712":"請填寫要拉取的鏡像名稱", + "i18n_6f15f0beea":"兩次密碼不一致...", + "i18n_6f32b1077d":"請輸入工作空間備註,留空使用默認的名稱", + "i18n_6f5b238dd2":" SSH、本地命令發佈都執行變量替換,系統預留變量有:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_6f6ee88ec4":"支持開源", + "i18n_6f73c7cf47":"保留天數:", + "i18n_6f7ee71e77":"靜態目錄", + "i18n_6f854129e9":"分組/標籤", + "i18n_6f8907351b":"同步節點配置", + "i18n_6f8da7dcca":"節點地址格式為:IP:PORT (示例:192.168.1.100:2123)", + "i18n_6f9193ac80":"啟用兩步驗證", + "i18n_6fa1229ea9":"一鍵分發同步多個節點的授權配置", + "i18n_6ffa21d235":"分發節點是指將變量同步到對應節點,在節點腳本中也可以使用當前變量", + "i18n_7006410585":"無論返回什麼退出代碼,始終重新啟動容器。", + "i18n_7010264d22":"沒有開啟任何認證", + "i18n_702430b89d":"頁面啟用寬鬆模式", + "i18n_702afc34a0":"差異發佈:", + "i18n_7030ff6470":"錯誤", + "i18n_7035c62fb0":"賬號", + "i18n_704f33fc74":"從頭搜索、文件前0行、文件後3行", + "i18n_706333387b":"此功能不能保證新增的容器和之前容器參數完全一致請慎重使用。", + "i18n_7088e18ac9":"卷", + "i18n_708c9d6d2a":"請選擇", + "i18n_70a6bc1e94":"當前系統已經初始化過啦,不能重複初始化", + "i18n_70b3635aa3":"執行時間", + "i18n_70b5b45591":"快速安裝", + "i18n_70b9a2c450":"真的要退出系統麼?", + "i18n_710ad08b11":"禁用", + "i18n_7114d41b1d":"超級管理員沒有任何限制", + "i18n_712cdd7984":"合作諮詢", + "i18n_713c986135":"容器構建會在 docker 中生成相關掛載目錄,一般情況不需要人為操作", + "i18n_7156088c6e":"編碼方式", + "i18n_71584de972":"非服務器開機自啟,如需開機自啟建議配置", + "i18n_715ec3b393":"用於快捷同步其他機器節點的配置", + "i18n_7173f80900":"拒絕", + "i18n_71a2c432b0":"編輯變量", + "i18n_71bbc726ac":"跟隨系統", + "i18n_71c6871780":"定時任務表達式", + "i18n_71dc8feb59":"未配置", + "i18n_71ee088528":"橋接模式:", + "i18n_7220e4d5f9":"方式", + "i18n_7229ecc631":"次", + "i18n_7293bbb0ff":"總 inode 數", + "i18n_729eebb5ff":"沒有對應的SSH", + "i18n_72d14a3890":"請選擇用户的權限組", + "i18n_72d46ec2cf":"登錄信息過期", + "i18n_72d4ade571":",僅是用於提示參數的含義", + "i18n_72e7a5d105":"鏡像id", + "i18n_72eae3107e":"灰綠 abbott", + "i18n_72ebfe28b0":"定時", + "i18n_7307ca1021":"自動啟動", + "i18n_7327966572":"徹底刪除", + "i18n_7329a2637c":"集羣ID", + "i18n_73485331c2":"文件信息", + "i18n_73578c680e":"數據目錄是指程序在運行過程中產生的文件以及數據存儲目錄", + "i18n_73651ba2db":"批量重啟", + "i18n_7370bdf0d2":"腳本日誌", + "i18n_738a41f965":"項目名稱", + "i18n_73a87230e0":"文件系統類型", + "i18n_73b7b05e6e":" 將腳本分發到對應的機器節點中,對應的機器節點可以引用對應的腳本 ", + "i18n_73b7e8e09e":"在使用 beta 版過程中遇到問題可以隨時反饋給我們,我們會盡快為您解答。", + "i18n_73c980987a":"加載容器可用標籤中....", + "i18n_73d8160821":"以 yaml/yml 格式配置,scriptId 為項目路徑下的腳本文件的相對路徑或者腳本模版ID,可以到腳本模版編輯彈窗中查看 scriptId", + "i18n_73ed447971":"強烈建議您使用 TLS 證書", + "i18n_73f798a129":"免費社羣", + "i18n_7457228a61":"遠程下載地址", + "i18n_74bdccbb5d":"我的工作空間", + "i18n_74c5c188ae":"操作成功接口 HTTP 狀態碼為 200", + "i18n_74d5f61b9f":"構建觸發", + "i18n_74d980d4f4":"在單頁列表裏面 file 類型項目將自動排序到最後", + "i18n_74dc77d4f7":"容器id", + "i18n_74dd7594fc":"發生報警時候請求", + "i18n_74ea72bbd6":"集羣管理", + "i18n_751a79afde":"30分鐘", + "i18n_7527da8954":"普通用户", + "i18n_7548ea6316":"點擊可以摺疊左側菜單欄", + "i18n_75528c19c7":"自動重啟", + "i18n_7561bc005e":"構建過程請求,非必填,GET請求", + "i18n_75769d1ac8":"讀", + "i18n_757a730c9e":"無法連接", + "i18n_758edf4666":"從頭搜索、文件前2行、文件後3行", + "i18n_75c63f427a":"此選項為一個實驗屬性實際效果基本無差異", + "i18n_75fc7de737":"路由", + "i18n_7617455241":"文件中如果存在:MemAvailable、MemTotal 這兩個字段,那麼 oshi 直接使用,所以本系統 中內存佔用計算方式:內存佔用=(total-available)/total", + "i18n_762e05a901":"差異發佈是指對應構建產物和項目文件夾裏面的文件是否存在差異,如果存在增量差異那麼上傳或者覆蓋文件。", + "i18n_7650487a87":"地址", + "i18n_76530bff27":"請輸入私人令牌", + "i18n_7653297de3":"跳轉", + "i18n_765592aa05":"如果未掛載容器數據目錄請提前備份數據後再使用此功能。", + "i18n_765d09eea5":"當前文件不可讀,需要配置可讀文件授權", + "i18n_767fa455bb":"目錄", + "i18n_768e843a3e":"類 192", + "i18n_769d88e425":"完成", + "i18n_76aebf3cc6":"日誌大小", + "i18n_76ebb2be96":"1分鐘", + "i18n_77017a3140":"關聯容器標籤", + "i18n_770a07d78f":"當目標工作空間不存在對應的 腳本 時候將自動創建一個新的 腳本", + "i18n_771d897d9a":"狀態碼", + "i18n_77373db7d8":"接收報警消息,非必填,GET請求", + "i18n_7737f088de":"批量重新啟動", + "i18n_773b1a5ef6":"請選擇語言模式", + "i18n_775fde44cf":"進程端口緩存:", + "i18n_7760785daf":"自由腳本", + "i18n_7764df7ccc":"開啟差異發佈但不開啟清空發佈時相當於只做增量和變動更新", + "i18n_77688e95af":"容器重建是指使用已經創建的容器參數重新創建一個相同的容器。", + "i18n_7777a83497":"請輸入構建備註,長度小於 240", + "i18n_77834eb6f5":"您使用本系統", + "i18n_7785d9e038":"低版本項目數據未存儲節點ID,對應項目數據也將出來在孤獨數據中(此類數據不影響使用)", + "i18n_77b9ecc8b1":"備份名稱", + "i18n_77c1e73c08":"腳本存放路徑:{'${user.home}'}/.jpom/xxxx.sh,執行腳本路徑:{'${user.home}'},執行腳本方式:bash {'${user.home}'}/.jpom/xxxx.sh par1 par2", + "i18n_77c262950c":"使用 Access Token 一次導入多個項目", + "i18n_77e100e462":"還沒有狀態消息", + "i18n_780afeac65":"是否開啟", + "i18n_780fb9f3d0":"更新時間:", + "i18n_7824ed010c":"真的取消當前發佈任務嗎?", + "i18n_7854b52a88":"啟用", + "i18n_787fdcca55":"系統配置", + "i18n_788a3afc90":"已失聯", + "i18n_78a4b837e3":"能通訊的IP", + "i18n_78b2da536d":"構建過程請求對應的地址,開始構建,構建完成,開始發佈,發佈完成,構建異常,發佈異常", + "i18n_78ba02f56b":"真的要徹底刪除分發信息麼?刪除後節點下面的項目也都將徹底刪除,徹底項目會自動刪除項目相關文件奧(包含項目日誌,日誌備份,項目文件)", + "i18n_78caf7115c":"任務名稱", + "i18n_78dccb6e97":"所有節點(插件端)", + "i18n_79076b6882":"真的要批量刪除這些構建信息麼?刪除也將同步刪除所有的構建歷史記錄信息,如果中途刪除失敗將終止刪除操作", + "i18n_7912615699":"連接狀態", + "i18n_791870de48":"倉庫密碼", + "i18n_791b6d0e62":"排名按照字母 a-z 排序", + "i18n_79698c57a2":"當前工作空間還沒有節點", + "i18n_798f660048":"模版節點", + "i18n_799ac8bf40":"支持變量引用:{'${TASK_ID}'}、{'${FILE_ID}'}、{'${FILE_NAME}'}、{'${FILE_EXT_NAME}'}", + "i18n_79a7072ee1":"令牌 url", + "i18n_79c6b6cff7":"關聯分組", + "i18n_79d3abe929":"複製", + "i18n_7a30792e2a":"編輯 SSH", + "i18n_7a3c815b1e":"文件目錄", + "i18n_7a4ecc606c":"鏡像標籤,如:key1=values1&keyvalue2 使用 URL 編碼", + "i18n_7a5dd04619":"注意執行相關命令需要所在服務器中存在對應的環境", + "i18n_7a7e25e9eb":"確定要將此數據下移嗎?下移操作可能因為列表後續數據沒有排序值操作無效!", + "i18n_7a811cc1e5":"複製 ", + "i18n_7a93e0a6ae":"選擇企業版本或者購買授權:", + "i18n_7aa81d1573":"請輸入文件名稱", + "i18n_7aaee3201a":"如果需要刪除需要提前備份或者已經確定對應文件棄用後才能刪除 !!!!", + "i18n_7afb02ed93":"當前沒有可以引用的環境變量", + "i18n_7b2cbfada9":"發佈前停止:", + "i18n_7b36b18865":"分區ID", + "i18n_7b61408779":"# 項目文件備份路徑", + "i18n_7b8e7d4abc":"真的要刪除執行記錄麼?", + "i18n_7b961e05d0":"表示月的最後一天", + "i18n_7bcbf81120":"接收包", + "i18n_7bcc3f169c":"# 內置變量 ${JPOM_WORKING_DIR} ${JPOM_BUILD_ID}", + "i18n_7bf62f7284":"手動取消分發", + "i18n_7c0ee78130":"構建日誌", + "i18n_7c223eb6e9":"發佈系統公吿", + "i18n_7c9bb61536":"日誌項目名稱", + "i18n_7cb8d163bb":"變量名稱", + "i18n_7cc3bb7068":"不會真實請求節點刪除項目信息", + "i18n_7ce511154f":"創建之後不能修改", + "i18n_7d23ca925c":"服務端時間", + "i18n_7d3f2fd640":"在文件第 3 - 2147483647 行中搜索", + "i18n_7ddbe15c84":"網絡", + "i18n_7dde69267a":"未綁定集羣的分組:", + "i18n_7de5541032":"如果 ssh 沒有配置授權目錄是不能選擇的喲", + "i18n_7dfc7448ec":"真的要刪除倉庫信息麼?", + "i18n_7dfcab648d":"產物", + "i18n_7e000409bb":"容易出現挖礦情況", + "i18n_7e1b283c57":" 添加", + "i18n_7e2b40fc86":"選擇節點", + "i18n_7e300e89b1":"分發成功", + "i18n_7e33f94952":",如果想要切換路徑後執行命令則需要", + "i18n_7e359f4b71":"硬盤總量:", + "i18n_7e58312632":"編輯日誌搜索", + "i18n_7e866fece6":"請輸入兩步驗證碼", + "i18n_7e930b95ef":"發佈文件", + "i18n_7e951d56d9":"操作時間", + "i18n_7e9f0d2606":"項目是指,節點中的某一個項目,需要提前在節點中創建項目", + "i18n_7ef30cfd31":"附加環境變量是指讀取倉庫指定環境變量文件來新增到執行構建運行時", + "i18n_7f0abcf48d":"需要到編輯中去為一個節點綁定一個 ssh信息才能啟用該功能", + "i18n_7f3809d36b":"構建結束", + "i18n_7f5bcd975b":"cpu佔用", + "i18n_7f7c624a84":"批量操作", + "i18n_7f7ee903da":"發佈隱藏文件", + "i18n_7fb5bdb690":"軟件致謝", + "i18n_7fb62b3011":"批量刪除", + "i18n_7fbc0f9aae":"執行時間開始", + "i18n_7fc88aeeda":"修改密碼", + "i18n_800dfdd902":"今天", + "i18n_80198317ff":"並向您的朋友推薦或分享:", + "i18n_8023baf064":"通知狀態", + "i18n_80669da961":"CPU佔用", + "i18n_807ed6f5a6":"暫無任何數據", + "i18n_8086beecb3":"標籤名稱:", + "i18n_808c18d2bb":"值為 true 表示項目當前為運行中", + "i18n_809b12d6a0":"請耐心等待暫時不用刷新頁面", + "i18n_80cfc33cbe":"確認重置", + "i18n_81301b6813":"打開終端", + "i18n_81485b76d8":"請輸入主機地址", + "i18n_814dd5fb7d":"真的要刪除備份信息麼?", + "i18n_815492fd8d":"舊版程序包占有空間", + "i18n_8160b4be4e":"異常關閉", + "i18n_819767ada1":"用户名", + "i18n_8198e4461a":"項目:", + "i18n_81afd9e713":"隊列等待", + "i18n_81c1dff69c":"解決辦法", + "i18n_81d7d5cd8a":"命令詳細描述", + "i18n_81e4018e9d":"懸空類型", + "i18n_82416714a8":"需要測試的端口", + "i18n_824607be6b":"保留天數", + "i18n_824914133f":"沒有任何腳本庫", + "i18n_8283f063d7":"項目完整目錄", + "i18n_828efdf4e5":"開啟MFA數", + "i18n_82915930eb":"併發執行", + "i18n_829706defc":"在線構建(構建關聯倉庫、構建歷史)", + "i18n_829abe5a8d":"分組", + "i18n_82b89bd049":"日誌彈窗會全屏打開", + "i18n_82d2c66f47":"批量分配", + "i18n_8306971039":"所屬用户", + "i18n_8309cec640":"請選擇節點項目,可能是節點中不存在任何項目,需要去節點中創建項目", + "i18n_833249fb92":"當前文件用時", + "i18n_8347a927c0":"修改", + "i18n_835050418f":"確認要上傳最新的插件包嗎?", + "i18n_83611abd5f":"發佈", + "i18n_8363193305":"請輸入回調重定向 url [redirectUri]", + "i18n_8388c637f6":"自啟動", + "i18n_83aa7f3123":"分發id", + "i18n_83c61f7f9e":"請選擇監控用户", + "i18n_83ccef50cd":"當目標工作空間已經存在 腳本 時候將自動同步 腳本內容、默認參數、定時執行、描述", + "i18n_83f25dbaa0":"綁定節點", + "i18n_8400529cfb":"重置自定義的進程名信息", + "i18n_8432a98819":"操作功能", + "i18n_843f05194a":"顯示所有", + "i18n_84415a6bb1":"重置下載 token 信息,重置後之前的下載 token 將失效", + "i18n_844296754e":"虛擬內存", + "i18n_84592cd99c":"可以理解為項目打包的目錄。 如 Jpom 項目執行(構建命令)", + "i18n_84597bf5bc":"阿里雲企業郵箱配置", + "i18n_84632d372f":"點擊查看詳情", + "i18n_84777ebf8b":"安全提醒", + "i18n_847afa1ff2":"請輸入IP授權,多個使用換行,0.0.0.0 是開放所有IP,支持配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_848c07af9b":"管理面板", + "i18n_848e4e21da":"如:--server", + "i18n_8493205602":"開", + "i18n_84aa0038cf":"系統日誌", + "i18n_84b28944b7":"超時時間(S)", + "i18n_84d331a137":"秒 (值太小可能會取不到節點狀態)", + "i18n_84e12f7434":"會話已經關閉[ssh-terminal]", + "i18n_853d8ab485":"正在構建", + "i18n_85451d2eb5":"請輸入變量值", + "i18n_8580ad66b0":"真的要徹底刪除項目麼?徹底項目會自動刪除項目相關文件奧(包含項目日誌,日誌備份,項目文件)", + "i18n_85be08c99a":"未查詢到任何數據", + "i18n_85cfcdd88b":"本地構建是指直接在服務端中的服務器執行構建命令", + "i18n_85da2e5bb1":"重啟中,請稍候...", + "i18n_85ec12ccd3":"延遲,容器升級間隔時間", + "i18n_85f347f9d0":"用户限制用户只能對應的工作空間裏面操作對應的功能", + "i18n_85fe5099f6":"集羣", + "i18n_86048b4fea":"移除", + "i18n_860c00f4f7":"每小時", + "i18n_863a95c914":"真的要保存當前配置嗎?如果配置有誤,可能無法啟動服務需要手動還原奧!!!", + "i18n_867cc1aac4":" :範圍:0~23", + "i18n_869b506d66":"獨立的項目分發請到分發管理中去修改", + "i18n_869ec83e33":"未使用", + "i18n_86b7eb5e83":"刪除前需要將關聯數據都刪除後才能刪除當前工作空間?", + "i18n_86c1eb397d":"切換賬號", + "i18n_86cd8dcead":"啟動時間", + "i18n_86e9e4dd58":"倉庫lastcommit", + "i18n_86f3ec932c":"讀取大小", + "i18n_86fb7b5421":"節點賬號", + "i18n_8704e7bdb7":"請輸入令牌 url [accessTokenUri]", + "i18n_871cc8602a":"二級目錄", + "i18n_8724641ba8":"間隔(/) > 區間(-) > 列表(,)", + "i18n_8756efb8f4":"真的要刪除當前文件夾麼?", + "i18n_87659a4953":"確認要關閉 beta 計劃嗎?", + "i18n_8780e6b3d1":"文件管理", + "i18n_878aebf9b2":"登錄名稱", + "i18n_87d50f8e03":"容器ID", + "i18n_87db69bd44":"限制資源", + "i18n_87dec8f11e":"錯誤的工作空間數據", + "i18n_87e2f5bf75":"追加腳本模板", + "i18n_87eb55155a":"行數:", + "i18n_8813ff5cf8":"如果是在啟動服務端後安裝並配置的環境變量需要通過終端命令來重啟服務端才能生效", + "i18n_883848dd37":"實際內存佔用", + "i18n_8844085e15":"凌晨0點", + "i18n_884ea031d3":"請輸入變量描述", + "i18n_8887e94cb7":"順序執行(有執行失敗將繼續)", + "i18n_888df7a89e":"不推薦", + "i18n_88ab27cfd0":"分組/標籤:", + "i18n_88b4b85562":"打包預發佈環境 npm i && npm run build:stage", + "i18n_88b79928e7":"證書丟失", + "i18n_88c5680d0d":"管理狀態:", + "i18n_88c85a2506":"新增的節點(插件端)將自動", + "i18n_88e6615734":"解綁會檢查數據關聯性,同時將自動刪除節點項目和腳本緩存信息,一般用於服務器無法連接且已經確定不再使用", + "i18n_88f5c7ac4a":"請選擇排序字段", + "i18n_8900539e06":"寫入大小", + "i18n_89050136f8":"發佈後操作", + "i18n_891db2373b":"自動刷新", + "i18n_897d865225":"鏡像數:", + "i18n_89944d6ccb":"標籤限制為字母數字且長度 1-10", + "i18n_899dbd7b9a":"CPU型號", + "i18n_899fe0c5dd":"節點地址為插件端的 IP:PORT 插件端端口默認為:212", + "i18n_89a40a1a8b":"分發過程請求,非必填,GET請求", + "i18n_89cfb655e0":"容器名標籤", + "i18n_89d18c88a3":"請輸入文件任務名", + "i18n_89f5ca6928":"支持通配符", + "i18n_8a1767a0d2":"開啟此選項後可以正常發佈隱藏文件", + "i18n_8a3e316cd7":"不編碼", + "i18n_8a414f832f":"集羣地址", + "i18n_8a49e2de39":"QQ 郵箱配置", + "i18n_8a4dbe88b8":"點擊進入節點管理", + "i18n_8a745296f4":"開機時間:", + "i18n_8aa25f5fbe":"發佈類型", + "i18n_8ae2b9915c":"請填寫第", + "i18n_8aebf966b2":"集羣訪問地址", + "i18n_8b1512bf3a":"如果端口", + "i18n_8b2e274414":"上次重載結果", + "i18n_8b3db55fa4":"集羣ID:", + "i18n_8b63640eee":"賬號被禁用", + "i18n_8b6e758e4c":"懸停到儀表盤上顯示具體含義", + "i18n_8b73b025c0":"簡單易用,但不支持密鑰導出備份", + "i18n_8b83cd1f29":"要拉取的鏡像名稱", + "i18n_8ba971a184":"私人令牌", + "i18n_8ba977b4b7":"# 限制備份指定文件後綴(支持正則)", + "i18n_8bd3f73502":"節點密碼", + "i18n_8be76af198":"163 郵箱配置", + "i18n_8be868ba1b":"類 10", + "i18n_8c0283435b":":表示連續區間,例如在分上,表示2,3,4,5,6,7,8分", + "i18n_8c24b5e19c":"請使用應用掃碼綁定令牌,然後輸入驗證碼確認綁定才生效", + "i18n_8c2da7cce9":"沒有任何證書", + "i18n_8c4db236e1":"請輸入腳本標記,標記只能是字母或者數字長度需要小於 20 並且全局唯一", + "i18n_8c61c92b4b":"備份類型", + "i18n_8c66392870":"需要使用 ssh-keygen -m PEM -t rsa -b 4096 -C", + "i18n_8c67370ee5":"如果產物同步到文件中心,當前值會共享", + "i18n_8c7c7f3cfa":"服務端腳本", + "i18n_8c7ce1da57":"開啟 dockerTag 版本遞增後將在每次構建時自動將版本號最後一位數字同步為構建序號ID, 如:當前構建為第 100 次構建 testtag:1.0 -> testtag:1.100,testtag:1.0.release -> testtag:1.100.release。如果沒有匹配到數字將忽略遞增操作", + "i18n_8c7d19b32a":"允許執行的內存節點 (MEM) (0-3, 0,1)。 僅在 NUMA 系統上有效。", + "i18n_8cae9cb626":"淺色 idea", + "i18n_8ccbbb95a4":"請填寫遠程URL", + "i18n_8cd628f495":"所有的IP:", + "i18n_8d0fa2ee2d":"請輸入端口號", + "i18n_8d1286cd2e":"沒有任何分發日誌", + "i18n_8d13037eb7":"狀態消息:", + "i18n_8d202b890c":"批量觸發器地址", + "i18n_8d3d771ab6":"編輯集羣", + "i18n_8d5956ca2a":"以 yaml/yml 格式配置,scriptId 為項目路徑下的腳本文件的相對路徑或者服務端腳本模版ID,可以到服務端腳本模版編輯彈窗中查看 scriptId", + "i18n_8d5c1335b6":"容器名稱數字字母,且長度大於1", + "i18n_8d62b202d9":"請選擇要使用的文件", + "i18n_8d63ef388e":"暫停", + "i18n_8d6d47fbed":"# 在指定目錄執行: ./ 項目目錄 /root/ 特定目錄 默認在 {'${jpom_agent_data_path}'}/script_run_cache ", + "i18n_8d6f38b4b1":"文件描述", + "i18n_8d90b15eaf":"# 宿主機文件上傳到容器 /host:/container:true", + "i18n_8d92fb62a7":"請選擇模板節點", + "i18n_8d9a071ee2":"導入", + "i18n_8da42dd738":"秒級別(默認未開啟秒級別,需要去修改配置文件中:[system.timerMatchSecond])", + "i18n_8dbe0c2ffa":"佔用空間:", + "i18n_8dc09ebe97":"獲取", + "i18n_8dc8bbbc20":"文件系統", + "i18n_8de2137776":"集羣任務", + "i18n_8e2ed8ae0d":"【獨立分發】", + "i18n_8e331a52de":"只允許訪問的 IP 地址", + "i18n_8e34aa1a59":"以此機器節點配置為模板", + "i18n_8e389298e4":"導出鏡像", + "i18n_8e38d55231":"真的要徹底退出系統麼?徹底退出將退出登錄和清空瀏覽器緩存", + "i18n_8e54ddfe24":"啟動", + "i18n_8e6184c0d3":"項目可能支持關聯如下數據:", + "i18n_8e6a77838a":"請選擇要分發到的機器節點", + "i18n_8e872df7da":"注意是整行不能包含空格", + "i18n_8e89763d95":"宿主機ip", + "i18n_8e8bcfbb4f":"確定要修剪對應的信息嗎?修剪會自動清理對應的數據", + "i18n_8e9bd127fb":"需要提前為工作空間配置授權目錄", + "i18n_8ea4c3f537":"從尾搜索、文件前100行、文件後100行", + "i18n_8ea93ff060":"節點腳本模版是存儲在節點中的命令腳本用於在線管理一些腳本命令,如初始化軟件環境、管理應用程序等", + "i18n_8ef0f6c275":"關閉 beta 計劃", + "i18n_8f0bab9a5a":"在讀取的日誌文件數", + "i18n_8f0c429b46":"遷移操作不具有事務性質,如果流程被中斷或者限制條件不滿足可能產生宂餘數據!!!!", + "i18n_8f36f2ede7":"工作空間名稱:", + "i18n_8f3747c057":"服務名稱", + "i18n_8f40b41e89":"過期時間:", + "i18n_8f7a163ee9":"快速安裝插件端", + "i18n_8f8f88654f":"暫無節點信息", + "i18n_8fb7785809":"如果在生成私鑰的過程中有加密,那麼需要把加密密碼填充到上面的密碼框中", + "i18n_8fbcdbc785":"請輸入別名碼", + "i18n_8fd9daf8e9":"保證在內網中使用可以忽略 TLS 證書", + "i18n_8fda053c83":"寫入次數", + "i18n_8ffded102f":"如果需要定時自動構建則填寫,cron 表達式.默認未開啟秒級別,需要去修改配置文件中:[system.timerMatchSecond])", + "i18n_900c70fa5f":"警吿", + "i18n_9014d6d289":"備份列表", + "i18n_90154854b6":"請輸入host", + "i18n_901de97cdb":"方式請求接口參數傳入到請求體 ContentType 請使用:text/plain", + "i18n_903b25f64e":"未知狀態", + "i18n_904615588b":"文件類型沒有控制枱功能", + "i18n_9057ac9664":"請選擇觸發類型", + "i18n_9065a208e8":"通過 URL 下載遠程文件到項目文件夾,需要到對應的工作空間下授權目錄配置中配置允許的 HOST 授權", + "i18n_906f6102a7":"重啟成功", + "i18n_9086111cff":"關聯工作空間 docker", + "i18n_90b5a467c1":"刷新目錄", + "i18n_90c0458a4c":"導入備份", + "i18n_90eac06e61":"宿主機目錄", + "i18n_912302cb02":"瀏覽器", + "i18n_9136e1859a":"Docker鏡像", + "i18n_913ef5d129":"執行重啟", + "i18n_916cde39c4":"所有參數將拼接成字符串以空格分隔形式執行腳本,需要注意參數順序和未填寫值的參數將自動忽略", + "i18n_916ff9eddd":"請輸入暱稱", + "i18n_91985e3574":"自動探測", + "i18n_91a10b8776":" 腳本庫 ", + "i18n_920f05031b":"狀態描述", + "i18n_922b76febd":"運行模式必填", + "i18n_923f8d2688":"發佈後命令", + "i18n_9255f9c68f":"會話已經關閉[tail-file]", + "i18n_92636e8c8f":"跳過", + "i18n_9282b1e5da":"企業微信掃碼", + "i18n_929e857766":"證書類型", + "i18n_92c6aa6db9":"如果您 SSH 機器中存在 docker 但是系統沒有監測到,您需要到【配置管理】-", + "i18n_92dde4c02b":"廣吿投放", + "i18n_92e3a830ae":"幫助", + "i18n_92f0744426":"容器構建是指使用 docker 容器執行構建,這樣可以達到和宿主機環境隔離不用安裝依賴環境", + "i18n_92f3fdb65f":"倉庫:", + "i18n_92f9a3c474":"切換語言後頁面將自動刷新", + "i18n_9300692fac":"標記引用", + "i18n_9302bc7838":"請輸入要檢查的端口", + "i18n_930882bb0a":"個", + "i18n_9308f22bf6":"單個觸發器地址中:第一個隨機字符串為腳本ID,第二個隨機字符串為 token", + "i18n_930fdcdf90":"配置名 (如:size)", + "i18n_9324290bfe":"如:key1", + "i18n_932b4b7f79":"注意:在每一個子表達式中優先級:", + "i18n_934156d92c":"創建分發項目", + "i18n_9341881037":"確定要取批量構建嗎?注意:同時運行多個構建將佔用較大的資源,請慎重使用批量構建,如果批量構建的數量超多構建任務隊列等待數,構建任務將自動取消", + "i18n_935b06789f":"您還未執行操作", + "i18n_9362e6ddf8":"危險操作!!!", + "i18n_938dd62952":"執行路徑", + "i18n_939d5345ad":"提交", + "i18n_93e1df604a":"機器分組", + "i18n_93e894325d":"批量啟動", + "i18n_9402665a2c":" 持續搜索(對話框不會自動關閉,按 Enter 查找下一個,按 Shift-Enter 查找上一個)", + "i18n_9412eb8f99":"請填寫平台地址", + "i18n_9443399e7d":" ,範圍1970~2099,但是第7位不做解析,也不做匹配", + "i18n_94763baf5f":"可以到節點管理中的【插件端配置】=>【授權配置】修改", + "i18n_947d983961":"温馨提示", + "i18n_948171025e":"會話已經關閉[docker-log]", + "i18n_949934d97c":"超大", + "i18n_949a8b7bd2":"列設置", + "i18n_94aa195397":"證書文件", + "i18n_94ca71ae7b":"請選擇要使用的證書", + "i18n_94d4fcca1b":"創建賬號", + "i18n_952232ca52":"構建歷史可能佔有較多硬盤空間,建議根據實際情況配置保留個數", + "i18n_953357d914":"忽略校驗 state", + "i18n_953ec2172b":"未重啟成功:", + "i18n_954fb7fa21":"真的要刪除項目麼?刪除項目不會刪除項目相關文件奧,建議先清理項目相關文件再刪除項目", + "i18n_956ab8a9f7":"配置文件嗎?配置文件一旦創建不能通過管理頁面刪除的奧?", + "i18n_957c1b1c50":"集羣節點", + "i18n_95a43eaa59":"創建人", + "i18n_95b351c862":"編輯", + "i18n_95c5c939e4":"可選擇的列表和項目授權目錄是一致的,即相同配置", + "i18n_95dbee0207":"遠程下載安全HOST", + "i18n_96283fc523":"備份文件不存在", + "i18n_964d939a96":" 長名稱:", + "i18n_969098605e":"環境變量是指配置在系統中的一些固定參數值,用於腳本執行時候快速引用。", + "i18n_96b78bfb6a":"請勿手動刪除數據目錄下面文件 !!!!", + "i18n_96c1c8f4ee":"灰綠 abcdef", + "i18n_96c28c4f17":"加入到哪個集羣", + "i18n_96d46bd22e":"手動刷新統計", + "i18n_96e6f43118":"容器 runtime", + "i18n_974be6600d":"密碼必須包含數字,字母,字符,且大於6位", + "i18n_977bfe8508":"標籤(TAG)", + "i18n_979b7d10b0":"構建中斷", + "i18n_97a19328a8":"立即開啟", + "i18n_97cb3c4b2e":"工作空間環境變量用於構建命令相關", + "i18n_97d08b02e7":"網絡端口測試", + "i18n_97ecc1bbe9":"輸出流量", + "i18n_981cbe312b":"至", + "i18n_9829e60a29":"實時版本號", + "i18n_98357846a2":"表格視圖才能使用批量操作功能", + "i18n_983f59c9d4":"驗證碼", + "i18n_9878af9db5":"請到【系統管理】-> 【資產管理】-> 【Docker管理】新增Docker並創建集羣,或者將已存在的的 Docker 集羣授權關聯、分配到此工作空間", + "i18n_9880bd3ba1":"此工具用於檢查 cron 表達式是否正確,以及計劃運行時間", + "i18n_989f1f2b61":"真的要重啟項目麼?", + "i18n_98a315c0fc":"授權", + "i18n_98cd2bdc03":"表格視圖才能使用同步配置功能", + "i18n_98d69f8b62":"工作空間", + "i18n_98e115d868":"運行中的定時任務", + "i18n_9914219dd1":"從頭搜索", + "i18n_9932551cd5":"內存", + "i18n_993a5c7eee":"#配置説明:https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerCreate", + "i18n_99593f7623":"客户端ID", + "i18n_9964d6ed3f":"掛載", + "i18n_996dc32a98":"系統類型", + "i18n_9970ad0746":"主題", + "i18n_9971192b6a":"。如:http", + "i18n_9973159a4d":"匹配一個字符", + "i18n_998b7c48a8":"查看容器", + "i18n_99b3c97515":"小時級別", + "i18n_99cba05f94":"真的要刪除 SSH 麼?當前 ssh 關聯的腳本在刪除後均將失效", + "i18n_99d3e5c718":" 開始搜索", + "i18n_99f0996c0a":"請選擇日誌目錄", + "i18n_9a00e13160":"單個觸發器地址中:第一個隨機字符串為項目ID(服務端),第二個隨機字符串為 token", + "i18n_9a0c5b150c":"編輯 命令", + "i18n_9a2ee7044f":"變量值", + "i18n_9a436e2a53":"修剪具有指定標籤的對象,多個使用逗號分隔", + "i18n_9a4b872895":"集羣操作", + "i18n_9a56bb830e":"用户暱稱", + "i18n_9a77f3523e":"鏡像 tag", + "i18n_9a7b52fc86":"所有", + "i18n_9a8eb63daf":"配置工作空間權限", + "i18n_9ab433e930":"其他配置", + "i18n_9ac4765895":"一個整數值,表示此容器相對於其他容器的相對 CPU 權重。", + "i18n_9adf43e181":"正在構建數", + "i18n_9ae40638d2":"部署證書", + "i18n_9af372557e":"服務端端口", + "i18n_9aff624153":"監控", + "i18n_9b0bc05511":"在文件第 1 - 100 行中搜索", + "i18n_9b1c5264a0":"上傳後", + "i18n_9b280a6d2d":"節點:", + "i18n_9b3e947cc9":"節點狀態:", + "i18n_9b5f172ebe":"綁定集羣", + "i18n_9b7419bc10":"QQ郵箱", + "i18n_9b74c734e5":"節點賬號密碼為插件端的賬號密碼,並非用户賬號(管理員)密碼", + "i18n_9b78491b25":"請輸入授權路徑,回車支持輸入多個路徑,系統會自動過濾 ../ 路徑、不允許輸入根路徑", + "i18n_9b7ada2613":" :範圍:1~31,", + "i18n_9b9e426d16":"在讀取的日誌文件數:", + "i18n_9ba71275d3":",請耐心等待暫時不用刷新頁面,升級成功後會自動刷新", + "i18n_9baca0054e":"修改人", + "i18n_9bbb6b5b75":"真的要刪除日誌搜索麼?", + "i18n_9bd451c4e9":"節點已經存在", + "i18n_9be8ff8367":"支持變量替換:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_9bf4e3c9de":"創建分發項目是指,全新創建一個屬於節點分發到項目,創建成功後項目信息將自動同步到對應的節點中,修改節點分發信息也自動同步到對應的節點中", + "i18n_9bf5aa6672":"web socket 錯誤,請檢查是否開啟 ws 代理", + "i18n_9c19a424dc":"請輸入原密碼", + "i18n_9c2a917905":"搜索命令", + "i18n_9c2f1d3f39":"內的root擁有真正的root權限。", + "i18n_9c3a3e1b03":"父級不存在自動刪除", + "i18n_9c3a5e1dad":"請到【系統管理】-> 【資產管理】-> 【機器管理】新增節點,或者將已新增的機器授權關聯、分配到此工作空間", + "i18n_9c3c05d91b":"節點地址建議使用內網地址", + "i18n_9c55e8e0f3":"允許執行的 CPU(例如,0-3、0,1)。", + "i18n_9c66f7b345":"真的要刪除機器麼?刪除會檢查數據關聯性", + "i18n_9c84cd926b":"新機器還需要綁定工作空間,因為我們建議將不同集羣資源分配到不同的工作空間來管理", + "i18n_9c942ea972":"證書生成方式可以參考文檔)來連接 docker 提升安全性", + "i18n_9c99e8bec9":"不填寫則發佈至項目的根目錄", + "i18n_9cac799f2f":"選擇分組名", + "i18n_9caecd931b":"字段", + "i18n_9cd0554305":"如果不需要保留較多構建歷史信息可以到服務端修改構建相關配置參數", + "i18n_9ce5d5202a":"運行的Jar包:", + "i18n_9d577fe51b":"文件來源", + "i18n_9d5b1303e0":"創建集羣會將嘗試獲取 docker 中集羣信息,如果存在集羣信息將自動同步集羣信息到系統,反之不存在集羣信息將自動創建 swarm 集羣", + "i18n_9d7d471b77":"請選擇節點角色", + "i18n_9d89cbf245":"分發名稱", + "i18n_9dd62c9fa8":"終端命令無限制", + "i18n_9ddaa182bd":"插件端時間:", + "i18n_9de72a79fe":"查看文件", + "i18n_9e09315960":"重建", + "i18n_9e2e02ef08":"分發類型", + "i18n_9e4ae8a24f":"釘釘掃碼", + "i18n_9e560a4162":"方式生成的 SSH key", + "i18n_9e5ffa068e":"基本信息", + "i18n_9e6b699597":"nanoCPUs 最小 1000000", + "i18n_9e78f02aad":"參數描述,參數描述沒有實際作用,僅是用於提示參數的含義", + "i18n_9e96d9c8d3":"系統負載:", + "i18n_9e98fa5c0d":"文件名欄支持右鍵菜單", + "i18n_9ec961d8cb":"選擇構建產物", + "i18n_9ee0deb3c8":"網絡開了小差!請重試...:", + "i18n_9ee9d48699":"創建後不支持修改", + "i18n_9f01272a10":" 法律風險", + "i18n_9f0de3800b":"請填寫倉庫名稱", + "i18n_9f52492fbc":"配置詳情請參考配置示例", + "i18n_9f6090c819":"傳入參數有:buildId、buildName、type、statusMsg、triggerTime", + "i18n_9f6fa346d8":"請輸入 SSH 名稱", + "i18n_9f70e40e04":"運行時間", + "i18n_9fb12a2d14":"發佈後執行的命令(非阻塞命令),一般是啟動項目命令 如:ps -aux | grep java, 支持變量替換:{'${ BUILD_ID }'}、{'${ BUILD_NAME }'}、{'${ BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID }'} ", + "i18n_9fb61a9936":"版本遞增", + "i18n_9fc2e26bfa":"請選擇項目", + "i18n_9fca7c455f":"登錄時間", + "i18n_9febf31146":"請選擇文件", + "i18n_9ff5504901":"傳入環境變量有:buildId、buildName、type、statusMsg、triggerTime、buildNumberId、buildSourceFile", + "i18n_a001a226fd":"更新時間", + "i18n_a03c00714f":"批量關閉", + "i18n_a03ea1e864":"請選擇分發到的節點", + "i18n_a04b7a8f5d":"單個觸發器請求支持將參數解析為環境變量傳入腳本執行,比如傳入參數名為 abc=efg 在腳本中引入則為:{'${trigger_abc}'}", + "i18n_a050cbc36d":"您的瀏覽器版本太低,不支持該功能", + "i18n_a056d9c4b3":"選擇腳本", + "i18n_a05c1667ca":"構建歷史", + "i18n_a08cbeb238":"並且配置正確的環境變量", + "i18n_a09375d96c":"懸空", + "i18n_a093ae6a6e":"自動續期", + "i18n_a0a111cbbd":"分發項目-", + "i18n_a0a3d583b9":"總內存:", + "i18n_a0b9b4e048":"請輸入客户端ID [clientId]", + "i18n_a0d0ebc519":"全局代理", + "i18n_a0e31d89ff":"一般建議 10 秒以上", + "i18n_a0f1bfad78":"數據目錄大小包含:臨時文件、在線構建文件、數據庫文件等", + "i18n_a11cc7a65b":"請輸入內容", + "i18n_a13d8ade6a":"關聯節點數據是異步獲取有一定時間延遲", + "i18n_a14da34559":"資源監控異常", + "i18n_a156349591":" 查看", + "i18n_a1638e78e8":"匹配包含 a 或者 b 的行", + "i18n_a17450a5ff":"選擇壓縮文件", + "i18n_a17b5ab021":"當前文件已經存在啦", + "i18n_a17bc8d947":"控制枱日誌只是啟動項目輸出的日誌信息,並非項目日誌。可以關閉控制枱日誌備份功能:", + "i18n_a189314b9e":"不發佈", + "i18n_a1a3a7d853":"現成生成", + "i18n_a1b745fba0":"請輸入備份名稱", + "i18n_a1bd9760fc":"定時任務", + "i18n_a1c4a75c2d":"是一款開源軟件您使用這個項目並感覺良好,或是想支持我們繼續開發,您可以通過如下方式支持我們:", + "i18n_a1da57ab69":"支付寶轉賬", + "i18n_a1e24fe1f6":"用户時間", + "i18n_a1f58b7189":"參數{count}值", + "i18n_a1fb7f1606":"腳本管理", + "i18n_a20341341b":"顯示前N行", + "i18n_a24d80c8fa":"項目啟動,停止,重啟,文件變動都將請求對應的地址", + "i18n_a25657422b":"變量名", + "i18n_a2a0f52afe":"不填將使用默認的 $HOME/.ssh 目錄中的配置,使用優先級是:id_dsa>id_rsa>identity", + "i18n_a2ae15f8a7":"構建流程", + "i18n_a2e62165dc":"真的要保存當前配置嗎?IP 授權請慎重配置奧( 授權是指只允許訪問的 IP ),配置後立馬生效 如果配置錯誤將出現無法訪問的情況,需要手動恢復奧!!!", + "i18n_a2ebd000e4":"不做任何操作", + "i18n_a3296ef4f6":"全屏終端", + "i18n_a33a2a4a90":"服務端同步的腳本不能在此修改", + "i18n_a34545bd16":"構建參數,如:key1=values1&keyvalue2 使用 URL 編碼", + "i18n_a34b91cdd7":"環境變量還可以用於倉庫賬號密碼、ssh密碼引用", + "i18n_a34c24719b":"開始執行任務", + "i18n_a35740ae41":"操作提示", + "i18n_a3751dc408":" :12點的每分鐘執行", + "i18n_a37c573d7b":"可以是 Unix 時間戳、日期格式的時間戳或 Go 持續時間字符串(例如 10m、1h30m),相對於守護進程機器的時間計算。", + "i18n_a38ed189a2":"上傳更新前請閲讀更新日誌裏面的説明和注意事項並且", + "i18n_a39340ec59":"禁止命令", + "i18n_a396da3e22":"當前工作空間還沒有項目並且也沒有任何節點", + "i18n_a3d0154996":"文件狀態", + "i18n_a3f1390bf1":"修改後如果有原始關聯數據將失效,需要重新配置關聯", + "i18n_a4006e5c1e":"創建備份", + "i18n_a421ec6187":"編輯環境變量", + "i18n_a4266aea79":"真的要刪除此服務麼?", + "i18n_a436c94494":"飛書掃碼", + "i18n_a472019766":"節點Id", + "i18n_a497562c8e":"執行人", + "i18n_a4f5cae8d2":"開啟狀態", + "i18n_a4f629041c":"路徑需要配置絕對路徑", + "i18n_a50fbc5a52":"支持指定網卡名稱來綁定:", + "i18n_a51cd0898f":"容器名稱", + "i18n_a51d8375b7":"選擇靜態文件", + "i18n_a52a10123f":"如果升級失敗需要手動恢復奧", + "i18n_a52aa984cd":"真的要刪除權限組麼?", + "i18n_a53d137403":"會話已經關閉[free-script]", + "i18n_a5617f0369":"SSH連接信息", + "i18n_a577822cdd":"保存並構建", + "i18n_a59d075d85":"自定義克隆深度,避免大倉庫全部克隆", + "i18n_a5d550f258":"間隔時間", + "i18n_a5daa9be44":"上傳前請檢查包是否完整,否則可能出現更新後無法正常啟動的情況!!", + "i18n_a5e9874a96":"請選擇發佈到哪個 docker 集羣", + "i18n_a5f84fd99c":"非隱私", + "i18n_a6269ede6c":"管理節點", + "i18n_a62fa322b4":"證書將打包成 zip 文件上傳到對應的文件夾", + "i18n_a637a42173":"選擇的構建歷史產物已經不存在啦", + "i18n_a63fe7b615":"分配到工作空間後還需要到關聯中進行配置對應工作空間才能完美使用奧", + "i18n_a657f46f5b":"周", + "i18n_a66644ff47":"類 172", + "i18n_a66fff7541":"遠程下載URL", + "i18n_a6bf763ede":"機器節點", + "i18n_a6fc9e3ae6":"上傳文件", + "i18n_a74b62f4bb":"硬盤信息", + "i18n_a75a5a9525":"發佈目錄,構建產物上傳到對應目錄", + "i18n_a75b96584d":"服務Id", + "i18n_a75f781415":"服務端菜單", + "i18n_a7699ba731":"上傳成功", + "i18n_a76b4f5000":"管理員數", + "i18n_a77cc03013":"如果引用腳本庫需要提前將對應腳本分發到機器節點中才能正常使用", + "i18n_a795fa52cd":"徹底退出", + "i18n_a7a9a2156a":"請輸入確認密碼", + "i18n_a7c8eea801":"嘗試自動續簽成功", + "i18n_a7ddb00197":"阿里雲企業郵箱 SSL", + "i18n_a805615d15":"type 的值有:startReady、pull、executeCommand、release、done、stop、success、error", + "i18n_a810520460":"密碼", + "i18n_a823cfa70c":"容器標籤", + "i18n_a84a45b352":"升級策略", + "i18n_a8754e3e90":"填寫正確的IP地址", + "i18n_a87818b04f":"等待開始", + "i18n_a8920fbfad":"命令路徑請修改為您的服務器中的實際路徑", + "i18n_a89646d060":"創建工作空間後還需要在對應工作空間中分別管理對應數據", + "i18n_a8f44c3188":"賬號是系統特定演示使用的賬號", + "i18n_a90cf0796b":"信息:", + "i18n_a912a83e6f":"插件版本", + "i18n_a918bde61d":"您還未構建", + "i18n_a91ce167c1":"文件id", + "i18n_a9463d0f1a":"搜索模式,默認查看文件最後多少行,從頭搜索指從指定行往下搜索,從尾搜索指從文件尾往上搜索多少行", + "i18n_a94feac256":"載速度根據網速來確定,如果網絡不佳下載會較慢", + "i18n_a952ba273f":"聯繫時請備註來意", + "i18n_a9795c06c8":"沒有SSH", + "i18n_a98233b321":"使用微軟全家桶的推薦", + "i18n_a9886f95b6":"確定要刪除此腳本庫嗎?", + "i18n_a9add9b059":"數據存儲目錄:", + "i18n_a9b50d245b":"不綁定", + "i18n_a9c52ffd40":"嚴格執行:", + "i18n_a9c999e0bd":"建議在上傳後的腳本中對文件進行自定義更名,SSH 上傳默認為:{'${FILE_ID}'}.{'${FILE_EXT_NAME}'}", + "i18n_a9de52acb0":"操作方法", + "i18n_a9eed33cfb":"如果版本相差大需要重新初始化數據來保證和當前程序裏面字段一致", + "i18n_a9f94dcd57":"部署", + "i18n_aa53a4b93a":"沒有任何網絡接口信息", + "i18n_aa9236568f":"統計趨勢", + "i18n_aabdc3b7c0":"項目路徑", + "i18n_aac62bc255":"點擊查看日誌", + "i18n_aad7450231":"請輸入選擇綁定的集羣", + "i18n_aadf9d7028":"用於下載遠程文件來進行節點分發和文件上傳", + "i18n_aaeb54633e":"重載", + "i18n_ab006f89e7":"手動刪除", + "i18n_ab13dd3381":"自建 Gitlab 賬號登錄", + "i18n_ab3615a5ad":"下載安裝包", + "i18n_ab3725d06b":"會話已經關閉", + "i18n_ab7f78ba4c":"空間ID(全匹配)", + "i18n_ab968d842f":"構建鏡像嘗試去更新基礎鏡像的新版本", + "i18n_ab9a0ee5bd":"文件夾路徑 需要在倉庫裏面 dockerfile", + "i18n_ab9c827798":"沒有docker集羣", + "i18n_abb6b7260b":"如果多選 ssh 下面目錄只顯示選項中的第一項,但是授權目錄需要保證每項都配置對應目錄", + "i18n_abba4043d8":"從頭搜索、文件前20行、文件後3行", + "i18n_abba4775e1":"命令參數", + "i18n_abd9ee868a":"網絡模式:bridge、container:、host、container、none", + "i18n_abdd7ea830":"請輸入新密碼", + "i18n_abee751418":"容器Id: ", + "i18n_ac00774608":"第", + "i18n_ac0158db83":"任務id", + "i18n_ac2f4259f1":"新版本:", + "i18n_ac408e4b03":"請選擇證書類型", + "i18n_ac5f3bfa5b":"選擇要監控的項目,file 類型項目不可以監控", + "i18n_ac762710a5":"支持自定義排序字段:sort", + "i18n_ac783bca36":"真的要退出並切換賬號登錄麼?", + "i18n_acb4ce3592":"請選擇靜態文件中的文件", + "i18n_acd5cb847a":"失敗", + "i18n_ace71047a0":"請到【系統管理】-> 【資產管理】-> 【SSH管理】新增SSH,或者將已新增的SSH授權關聯、分配到此工作空間", + "i18n_acf14aad3c":"不用挨個配置。配置後會覆蓋之前的配置", + "i18n_ad209825b5":"請選擇修剪類型", + "i18n_ad311f3211":"請選擇倉庫", + "i18n_ad35f58fb3":"佔用空間", + "i18n_ad4b4a5b3b":"宿主", + "i18n_ad780debbc":"回滾策略", + "i18n_ad8b626496":"真的要刪除構建歷史記錄麼?", + "i18n_ad9788b17d":"異常恢復", + "i18n_ad9a677940":"指定 settings 文件打包 mvn -s xxx/settings.xml clean package", + "i18n_adaf94c06b":"執行結果", + "i18n_adbec9b14d":"創建備份信息", + "i18n_adcd1dd701":"返回列表", + "i18n_add91bb395":"邏輯節點", + "i18n_ae0d608495":"是否使用MFA", + "i18n_ae0fd9b9d2":"備份時間", + "i18n_ae12edc5bf":"點擊複製文件路徑", + "i18n_ae17005c0c":"未加入", + "i18n_ae35be7986":"token,全匹配", + "i18n_ae653ec180":"詳細描述", + "i18n_ae6838c0e6":"節點分發", + "i18n_ae809e0295":"後綴,精準搜索", + "i18n_aeade8e979":"未初始化", + "i18n_aeb44d34e6":"一次性捐款贊助", + "i18n_aec7b550e2":"刪除工作空間確認", + "i18n_aed1dfbc31":"中", + "i18n_aefd8f9f27":"請選擇還原方式", + "i18n_af013dd9dc":"重啟成功後會自動刷新", + "i18n_af0df2e295":"需要到 ssh 信息中配置允許編輯的文件後綴", + "i18n_af14cd6893":"請填寫構建 DSL 配置內容,可以點擊上方切換 tab 查看配置示例", + "i18n_af3a9b6303":"企業微信掃碼賬號登錄", + "i18n_af427d2541":"數據更新時間", + "i18n_af4d18402a":"已經斷開連接啦", + "i18n_af51211a73":"頁面內容會出現滾動條", + "i18n_af708b659f":"內存:", + "i18n_af7c96d2b9":"同步機制採用容器 host 確定是同一個服務器(docker)", + "i18n_af924a1a14":"下載異常", + "i18n_af98c31607":"物理節點項目數量:", + "i18n_afa8980495":"請輸入允許編輯文件的後綴及文件編碼,不設置編碼則默認取系統編碼,示例:設置編碼:txt{'@'}utf-8, 不設置編碼:txt", + "i18n_afb9fe400b":"使用率:", + "i18n_b04070fe42":"選擇代理類型", + "i18n_b04209e785":"關聯數據:", + "i18n_b05345caad":"所有者", + "i18n_b07a33c3a8":"請選擇分發節點", + "i18n_b0b9df58fd":"SSH節點", + "i18n_b0fa44acbb":"佔用率:", + "i18n_b10b082c25":"的值有:stop、beforeStop、start、beforeRestart", + "i18n_b1192f8f8e":"真的取消當前分發嗎?", + "i18n_b11b0c93fa":"如果在 Linux 中實際運行內存可能和您直接使用 free -h 命令查詢到 free 和 total 字段計算出數值相差過大那麼此時就是您當前服務器中的交換內存引起的", + "i18n_b12d003367":"隱私字段", + "i18n_b153126fc2":"請輸入工作空間名稱", + "i18n_b15689296a":"風險提醒", + "i18n_b15d91274e":"關閉", + "i18n_b166a66d67":"確定要將此數上移嗎?", + "i18n_b17299f3fb":"插件端進程ID:", + "i18n_b1785ef01e":"節點名稱", + "i18n_b186c667dc":"分發過程請求對應的地址,開始分發,分發完成,分發失敗,取消分發", + "i18n_b188393ea7":"發佈的SSH", + "i18n_b1a09cee8e":"清空還原", + "i18n_b1dae9bc5c":"管理員", + "i18n_b28836fe97":"分發 ID 等同於項目 ID", + "i18n_b28c17d2a6":" (MEM) (0-3, 0,1)。 僅在 NUMA 系統上有效。", + "i18n_b29fd18c93":"請選擇指定發佈的項目", + "i18n_b2f296d76a":"5分鐘", + "i18n_b30d07c036":"批量關閉啟動", + "i18n_b328609814":"管理員擁有:管理服務端的部分權限", + "i18n_b339aa8710":"表格", + "i18n_b33c7279b3":"認證方式", + "i18n_b3401c3657":"容器目錄", + "i18n_b341f9a861":"任務時間", + "i18n_b343663a14":"清空發佈是指在上傳新文件前,會將項目文件夾目錄裏面的所有文件先刪除後再保存新文件", + "i18n_b36e87fe5b":"不執行,但是編譯測試用例 mvn clean package -DskipTests", + "i18n_b37b786351":"分組名", + "i18n_b384470769":"同步緩存", + "i18n_b38d6077d6":"登錄IP", + "i18n_b38d7db9b0":"下載構建日誌,如果按鈕不可用表示日誌文件不存在,一般是構建歷史相關文件被刪除", + "i18n_b3913b9bb7":"請輸入構建環境變量:xx=abc 多個變量回車換行即可", + "i18n_b399058f25":"強大安全的密碼管理付費應用", + "i18n_b39909964f":"請輸入郵箱賬號", + "i18n_b3b1f709d4":"剔除", + "i18n_b3bda9bf9e":"請選擇工作空間", + "i18n_b3ef35a359":"源倉庫", + "i18n_b3f9beb536":":3~18分,每5分鐘執行一次,即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……", + "i18n_b3fe677b5f":"失敗率", + "i18n_b408105d69":"密碼字段和密鑰字段在編輯的時候不會返回,如果需要重置或者清空就請點我", + "i18n_b437a4d41d":"也支持 URL 參數格式:test_par=123abc&test_par2=abc21", + "i18n_b44479d4b8":"可用標籤", + "i18n_b4750210ef":"集羣修改時間:", + "i18n_b499798ec5":"禁用分發節點", + "i18n_b4a8c78284":"選擇工作空間", + "i18n_b4c83b0b56":"倉庫賬號", + "i18n_b4dd6aefde":"會話已經關閉[script-console]", + "i18n_b4e2b132cf":"插件端運行端口默認使用:", + "i18n_b4fc1ac02c":"取消構建", + "i18n_b4fd7afd31":"個性配置", + "i18n_b513f53eb4":"超時時間 單位秒", + "i18n_b515d55aab":"可以通過證書管理中提前上傳或者點擊後面選擇證書去選擇/導入證書", + "i18n_b53dedd3e0":"發佈前執行的命令(非阻塞命令),一般是關閉項目命令", + "i18n_b55f286cba":"載前請閲讀更新日誌裏面的説明和注意事項並且", + "i18n_b56585aa18":"配置後可以控制想要在某個時間段禁止用户操作某些功能,優先判斷禁用時段", + "i18n_b57647c5aa":"真的要解綁腳本關聯的節點麼?", + "i18n_b57ecea951":"已經運行時間:", + "i18n_b5a1e1f2d1":"觸發類型:", + "i18n_b5a6a07e48":"週二", + "i18n_b5b51ff786":"上傳 SQL 文件", + "i18n_b5c291805e":"初始化系統", + "i18n_b5c3770699":"控制枱", + "i18n_b5c5078a5d":"所有的IPV4列表", + "i18n_b5ce5efa6e":"集羣服務", + "i18n_b5d0091ae3":"構建ID", + "i18n_b5d2cf4a76":"當目標工作空間已經存在 腳本 時候將自動同步 腳本內容、默認參數、自動執行、描述", + "i18n_b5fdd886b6":"全屏查看日誌", + "i18n_b60352bc4f":"虛擬", + "i18n_b6076a055f":"登錄失敗", + "i18n_b61a7e3ace":"腳本名稱:", + "i18n_b63c057330":"真的要刪除操作監控麼?", + "i18n_b650acd50b":"恢復默認名稱", + "i18n_b6728e74a4":"運行目錄:", + "i18n_b6a828205d":"緩存構建", + "i18n_b6afcf9851":"禁止命令是不允許在終端執行的命令,多個逗號隔開。(超級管理員沒有任何限制)", + "i18n_b6c9619081":"端口:", + "i18n_b6e8fb4106":"平台登錄", + "i18n_b6ee682dac":"插件數:", + "i18n_b714160f52":"分發項目 ID", + "i18n_b71a7e6aab":"本地命令", + "i18n_b7579706a3":"校驗", + "i18n_b7c139ed75":"如果項目目錄較大或者涉及到深目錄,建議關閉掃描避免獲取項目目錄掃描過長影響性能", + "i18n_b7cfa07d78":"確認綁定", + "i18n_b7df1586a9":"當目標工作空間已經存在節點時候將自動同步 docker 倉庫配置信息", + "i18n_b7ea5e506c":"系統信息", + "i18n_b7ec1d09c4":"服務ID", + "i18n_b7f770d80b":"需要先安裝依賴 npm i && npm run build", + "i18n_b8545de30e":"請至少選擇 1 個節點", + "i18n_b85b213579":"發件人名稱", + "i18n_b86224e030":"節點狀態", + "i18n_b87c9acca3":"真的要強制退出集羣嗎?", + "i18n_b8915a4933":"真的關閉當前用户的兩步驗證麼?", + "i18n_b8ac664d98":"勾選數據表", + "i18n_b90a30dd20":"此處不填不會修改密碼", + "i18n_b91961bf0b":"傳入參數有:projectId、projectName、type、result", + "i18n_b922323119":"鏡像標籤,如:key1=value1&key2=value2", + "i18n_b939d47e23":"公鑰", + "i18n_b953d1a8f1":"不能關閉了", + "i18n_b96b07e2bb":"僅修剪未使用和未標記的鏡像", + "i18n_b9a4098131":"觸發器地址", + "i18n_b9af769752":"鏡像名稱必填", + "i18n_b9b176e37a":"請選擇腳本", + "i18n_b9bcb4d623":"插件:", + "i18n_b9c1616fd5":"SSH方式連接的 docker 不建議用於容器構建(SSH 方式用於構建非常不穩定)", + "i18n_b9c4cf7483":" 全部替換", + "i18n_b9c52d9a85":"文件名:", + "i18n_ba17b17ba2":"沒有任何SSH腳本命令", + "i18n_ba1f68b5dd":"這樣使得", + "i18n_ba20f0444c":"強制刪除", + "i18n_ba311d8a6a":"腳本", + "i18n_ba3a679655":"以 yaml/yml 格式配置", + "i18n_ba452d57f2":"使用率最大的分區:", + "i18n_ba52103711":"剩餘 inode 數", + "i18n_ba619a0942":"默認構建錯誤將自動忽略隱藏文件", + "i18n_ba6e91fa9e":"權限", + "i18n_ba6ea3d480":"頁面全屏,高度 100%。局部區域可以滾動", + "i18n_ba8d1dca4a":"注意:", + "i18n_baafe06808":"安全組規則", + "i18n_bab17dc6b1":"選擇進程名", + "i18n_baef58c283":"請輸入標籤名 字母數字 長度 1-10", + "i18n_baefd3db91":"授權可以直接訪問的目錄,多個回車換行即可", + "i18n_bb316d9acd":"下載速度根據網速來確定,如果網絡不佳下載會較慢", + "i18n_bb4409015b":"機器 ssh 名", + "i18n_bb4740c7a7":"執行 命令", + "i18n_bb5aac6004":"構建產物同步到文件中心保留天數", + "i18n_bb667fdb2a":"未報警", + "i18n_bb7eeae618":"僅統計:", + "i18n_bb8d265c7e":"版本需要大於 18", + "i18n_bb9a581f48":"登錄成功,需要驗證 MFA", + "i18n_bb9ef827bf":"禁止訪問", + "i18n_bba360b084":"真的要刪除對應的觸發器嗎?", + "i18n_bbbaeb32fc":"機器延遲", + "i18n_bbcaac136c":"表中的錯誤數據嗎?", + "i18n_bbd63a893c":"自動檢測服務端所在服務器中是否存在 docker,如果存在將自動新增到列表中", + "i18n_bbf2775521":"鏡像名稱", + "i18n_bc2c23b5d2":"修剪操作會刪除相關數據,請謹慎操作。請您再確認本操作後果後再使用", + "i18n_bc2f1beb44":"真的要解鎖用户麼?", + "i18n_bc4b0fd88a":"網絡 Reachable 測試", + "i18n_bc8752e529":"分發項目", + "i18n_bcaf69a038":"請選擇一個節點", + "i18n_bcc4f9e5ca":"如", + "i18n_bcf48bf7a8":"授權 url", + "i18n_bcf83722c4":"變量描述", + "i18n_bd0362bed3":"新增分組", + "i18n_bd49bc196c":"編輯項目", + "i18n_bd4e9d0ee2":"原始名:", + "i18n_bd5d9b3e93":"使用哪個 docker 構建,填寫 docker 標籤( 標籤在 docker 編輯頁面配置) 默認查詢可用的第一個,如果tag 查詢出多個將依次構建", + "i18n_bd6c436195":"請輸入腳本描述", + "i18n_bd7043cae3":"遠程下載", + "i18n_bd7c8c96bc":"手動上傳", + "i18n_bda44edeb5":"不能操作", + "i18n_bdc1fdde6c":"beta計劃:", + "i18n_bdd4cddd22":"將還原【", + "i18n_bdd87b63a6":"微信二維碼", + "i18n_bdd9d38d7e":"列寬", + "i18n_be166de983":"軟鏈的項目", + "i18n_be1956b246":"深色2 blackboard", + "i18n_be2109e5b1":"確定要重置用户密碼嗎?", + "i18n_be24e5ffbe":"Java 項目(java -jar xxx)", + "i18n_be28f10eb6":"請選擇發佈的一級目錄和填寫二級目錄", + "i18n_be381ac957":"請選擇要使用的倉庫", + "i18n_be3a4d368e":"分發中、2:分發結束、3:已取消、4:分發失敗", + "i18n_be4b9241ec":"默認狀態碼為 200 表示執行成功", + "i18n_be5b6463cf":"語法參考", + "i18n_be5fbbe34c":"保存", + "i18n_beafc90157":"分發到機器節點中的腳本庫在節點腳本支持使用 G{'@'}(\"xxxx\")格式來引用,當存在引用時系統會自動替換引用腳本庫中的腳本內容", + "i18n_bebcd7388f":"加載構建數據中", + "i18n_bec98b4d6a":"狀態:", + "i18n_becc848a54":"私鑰文件絕對路徑(絕對路徑前面新增 file", + "i18n_bef1065085":"Chrome 擴展", + "i18n_bf0e1e0c16":"輸入倉庫名稱或者倉庫路徑進行{slot1}", + "i18n_bf77165638":"您確定要重啟當前容器嗎?", + "i18n_bf7da0bf02":"新密碼", + "i18n_bf91239ad7":"命令描述", + "i18n_bf93517805":"下列配置信息僅在當前瀏覽器生效", + "i18n_bf94b97d1a":"修改時間:", + "i18n_bfacfcd978":"將取消自動加載環境變量", + "i18n_bfc04cfda7":"分支", + "i18n_bfda12336c":"搜索查看", + "i18n_bfe68d5844":"鏈接", + "i18n_bfe8fab5cd":"需要配置授權目錄(授權才能正常使用發佈),授權目錄主要是用於確定可以發佈到哪些目錄中", + "i18n_bfed4943c5":"參數值", + "i18n_c00fb0217d":"請填寫用户暱稱", + "i18n_c03465ca03":"禁用數量", + "i18n_c0996d0a94":" :每週一和週二的11:59執行", + "i18n_c0a9e33e29":"請選擇構建對應的分支,必選", + "i18n_c0d19bbfb3":"請輸入 key 的值", + "i18n_c0d38f475f":"軟內存", + "i18n_c0d5d68f5f":"忽略", + "i18n_c0e498a259":"點擊圖標查看關聯的所有任務", + "i18n_c0f4a31865":"邏輯刪除", + "i18n_c11eb9deff":"文件MD5", + "i18n_c12ba6ff43":"我們有權利追訴破壞開源並因此獲利的團隊個人的全部違法所得,也歡迎給我們提供侵權線索。", + "i18n_c163613a0d":"如果當前集羣還存在可能出現數據不一致問題奧", + "i18n_c1690fcca5":"導入證書", + "i18n_c16ab7c424":"】 嗎?注意:取消/停止構建不一定能正常關閉所有關聯進程", + "i18n_c1786d9e11":"節點地址", + "i18n_c17aefeebf":"系統名:", + "i18n_c18455fbe3":"授權信息錯誤", + "i18n_c195df6308":"異常", + "i18n_c1af35d001":"構建產物", + "i18n_c1b72e7ded":"為變量名稱", + "i18n_c23fbf156b":"未選擇ssh", + "i18n_c26e6aaabb":"擅自修改或者刪除版權信息有法律風險", + "i18n_c2add44a1d":"一些例子:", + "i18n_c2b2f87aca":"腳本孤獨數據", + "i18n_c2ee58c247":"構建命令", + "i18n_c2f11fde3a":"初始化系統賬户", + "i18n_c31ea1e3c4":"沒有任何操作日誌", + "i18n_c325ddecb1":"CPU 週期的長度,以微秒為單位。", + "i18n_c32e7adb20":"請輸入遠程下載安全HOST,回車支持輸入多個路徑,示例 https://www.test.com 等", + "i18n_c34175dbef":"控制枱日誌備份路徑: ", + "i18n_c3490e81bf":"重啟創建之前會自動將之前的容器刪除掉", + "i18n_c34f1dc2b9":"參數中的 id 、token 和觸發構建一致", + "i18n_c3512a3d09":"請選選擇類型", + "i18n_c35c1a1330":"排序值", + "i18n_c360e994db":"排序", + "i18n_c36ab9a223":"為 docker bridge 上的容器創建一個新的網絡堆棧", + "i18n_c37ac7f024":"清除代碼", + "i18n_c3aeddb10d":"當前工作空間還沒有邏輯節點不能創建節點分發奧", + "i18n_c3f28b34bb":"集羣名稱", + "i18n_c446efd80d":"編輯 Script", + "i18n_c4535759ee":"系統提示", + "i18n_c46938460b":"系統使用 docker http 接口實現和 docker 通訊和管理,但是默認", + "i18n_c469afafe0":"您確定要刪除當前容器嗎?", + "i18n_c494fbec77":"手動新增", + "i18n_c4a61acace":"命令?", + "i18n_c4b5d36ff0":"節點狀態會自動識別服務器中是否存在 java 環境,如果沒有 Java 環境不能快速安裝節點", + "i18n_c4cfe11e54":"如果上傳的壓縮文件是否自動解壓 支持的壓縮包類型有 tar", + "i18n_c4e0c6b6fe":"篩選項目", + "i18n_c4e2cd2266":"還需要對相關數據都操作後才能達到預期排序", + "i18n_c5099cadcd":"插件數", + "i18n_c53021f06d":"填寫【xxx", + "i18n_c530a094f9":"構建方式:", + "i18n_c538b1db4a":"軟鏈項目(類似於項目副本使用相關路徑的文件)", + "i18n_c583b707ba":"個參數的描述", + "i18n_c5a2c23d89":"非全屏", + "i18n_c5aae76124":"綁定 SSH", + "i18n_c5bbaed670":"狀態碼錯誤", + "i18n_c5c3583bfc":"發送驗證碼", + "i18n_c5c69827c5":"等網絡端口限制", + "i18n_c5de93f9c7":"需要手動確認", + "i18n_c5e7257212":"當前節點ID:", + "i18n_c5f9a96133":"系統默認將對 demo 賬號限制很多權限。非演示場景不建議使用 demo 賬號", + "i18n_c600eda869":"命令文件將在 {'${數據目錄}'}/script/xxxx.sh、bat 執行", + "i18n_c618659cea":"自定義分支通配表達式", + "i18n_c6209653e4":"SMTP 服務器域名", + "i18n_c68dc88c51":"請輸入監控名稱", + "i18n_c6a3ebf3c4":"接收大小", + "i18n_c6e4cddba0":"請選擇監控的功能", + "i18n_c6f1c6e062":"目錄不能編輯", + "i18n_c6f6a9b234":"遷移到其他工作空間", + "i18n_c704d971d6":"請填寫構建和產物", + "i18n_c7099dabf6":"正在上傳文件", + "i18n_c71a67ab03":"分發後", + "i18n_c75b14a04e":"監控頻率可以到服務端配置文件中修改", + "i18n_c75d0beca8":"開啟頁面操作引導、導航", + "i18n_c7689f4c9a":"這裏構建命令最終會在服務器上執行。如果有多行命令那麼將", + "i18n_c76cfefe72":"端口", + "i18n_c7c4e4632f":",更新期間允許的失敗率", + "i18n_c7e0803a17":"密碼只會出現一次,關閉窗口後無法再次查看密碼", + "i18n_c806d0fa38":"壓縮包", + "i18n_c83752739f":"支持的字段可以通過接口返回的查看", + "i18n_c840c88b7c":"真的要清空 【", + "i18n_c84ddfe8a6":"執行日誌", + "i18n_c8633b4b77":"通過私人令牌導入倉庫", + "i18n_c87bd94cd7":"鏡像Id: ", + "i18n_c889b9f67d":"新增關聯項目", + "i18n_c89e9681c7":"臨時文件佔用空間", + "i18n_c8a2447aa9":"加入", + "i18n_c8b2aabc07":"文件中如果不存在:MemAvailable,那麼 MemAvailable = MemFree+Active(file)+Inactive(file)+SReclaimable,所以本系統 中內存佔用計算方式:內存佔用=(total-(MemFree+Active(file)+Inactive(file)+SReclaimable))/total", + "i18n_c8c452749e":"選擇 SQL 文件", + "i18n_c8c45e8467":"請根據自身項目啟動時間來配置", + "i18n_c8c6e37071":"温馨提醒", + "i18n_c8ce4b36cb":"重命名", + "i18n_c90a1f37ce":"節點id", + "i18n_c96b442dfb":"通用郵箱 SSL", + "i18n_c96f47ec1b":"異步請求不能保證有序性", + "i18n_c972010694":"產物目錄", + "i18n_c9744f45e7":"否", + "i18n_c97e6e823a":"重新啟動容器,除非它已被停止", + "i18n_c983743f56":"總內存", + "i18n_c996a472f7":"每天0/12點刷新一次", + "i18n_c99a2f7ed8":"啟動命令", + "i18n_c9b0f8e8c8":"真的要刪除", + "i18n_c9b79a2b4f":"全局代理配置後將對服務端的網絡生效,代理實現方式:ProxySelector", + "i18n_c9daf4ad6b":"多線程", + "i18n_ca32cdfd59":"內存使用率:", + "i18n_ca69dad8fc":"流程執行完腳本後,輸出的內容最後一行必須為:running", + "i18n_ca774ec5b4":"上次構建基於 commitId:", + "i18n_caa9b5cd94":"需要將標籤值配置到構建 DSL 中的", + "i18n_cab7517cb4":"節點地址:", + "i18n_cabdf0cd45":"選擇產物", + "i18n_cac26240b5":"容器日誌", + "i18n_cac6ff1d82":"批量獲取構建狀態地址", + "i18n_cad01fe13c":"黑白 ambiance-mobile", + "i18n_caf335a345":"java信息", + "i18n_cb09b98416":"個性配置區", + "i18n_cb156269db":"單位秒,最小值 1 秒", + "i18n_cb25f04b46":"在文件最後 3 行中搜索", + "i18n_cb28aee4f0":"節點分發【暫不支持遷移】", + "i18n_cb46672712":"日誌閲讀 【暫不支持遷移】", + "i18n_cb93a1f4a5":"、root、manager", + "i18n_cb951984f2":"已存在", + "i18n_cb9b3ec760":"批量觸發參數 BODY json: [ { \"id\":\"1\", \"token\":\"a\",\"action\":\"status\" } ]", + "i18n_cbc44b5663":"執行時間結束", + "i18n_cbcc87b3d4":"、名稱、版權等", + "i18n_cbce8e96cf":"證書信息", + "i18n_cbdc4f58f6":"請輸入機器的名稱", + "i18n_cbdcabad50":"釋放", + "i18n_cbee7333e4":"本地命令是指,在服務端本地執行多條命令來實現發佈", + "i18n_cc3a8457ea":"節點狀態是異步獲取有一定時間延遲", + "i18n_cc42dd3170":"開啟", + "i18n_cc51f34aa4":"編輯服務", + "i18n_cc5dccd757":"工作空間中邏輯節點中腳本模版數量:", + "i18n_cc617428f7":"隱私變量是指一些密碼字段或者關鍵密鑰等重要信息,隱私字段只能修改不能查看(編輯彈窗中無法看到對應值)。 隱私字段一旦創建後將不能切換為非隱私字段", + "i18n_cc637e17a0":"產物:", + "i18n_cc92cf1e25":"請填寫產物目錄", + "i18n_cc9a708364":"狀態碼錯誤 ", + "i18n_cca4454cf8":"請輸入公吿內容", + "i18n_ccb2fdd838":"切換工作空間", + "i18n_ccb91317c5":"命令內容", + "i18n_ccea973fc7":"當前節點地址:", + "i18n_cd1aedc667":"在 設置 --> 應用 --> 生成令牌", + "i18n_cd649f76d4":"時間範圍", + "i18n_cd998f12fa":"當目標工作空間已經存在節點時候將自動同步節點授權信息、代理配置信息", + "i18n_cda84be2f6":"操作日誌", + "i18n_cdc478d90c":"系統名", + "i18n_ce043fac7d":"當前工作空間還沒有SSH", + "i18n_ce07501354":"點擊數字查看運行中的任務", + "i18n_ce1ecd8a5b":"還有更多相關依賴開源組件", + "i18n_ce23a42b47":"任務名", + "i18n_ce40cd6390":"關聯腳本", + "i18n_ce559ba296":"運行命令", + "i18n_ce7e6e0ea9":"當前配置僅對選擇的工作空間生效,其他工作空間需要另行配置", + "i18n_ce84c416f9":"請輸入用户信息 url [userInfoUri]", + "i18n_ced3d28cd1":"不掃描", + "i18n_ceee1db95a":"容器端口", + "i18n_ceffe5d643":"兩步驗證應用", + "i18n_cf38e8f9fd":"當前為節點分發的授權路徑配置", + "i18n_cfa72dd73a":"請輸入要檢查的 cron 表達式", + "i18n_cfb00269fd":"執行腳本", + "i18n_cfbb3341d5":"當前登錄的賬號是:", + "i18n_cfd482e5ef":"暫無數據,請先新增節點項目數據", + "i18n_cfeea27648":"創建文件 /xxx/xxx/xxx", + "i18n_cfeff30d2c":"目錄:", + "i18n_d00b485b26":"回滾", + "i18n_d0132b0170":"還原過程中不能操作哦...", + "i18n_d02a9a85df":"請選擇報警聯繫人", + "i18n_d047d84986":"個 / 共", + "i18n_d0874922f0":"綁定指定目錄可以在線管理,同時構建 ssh 發佈目錄也需要在此配置", + "i18n_d0a864909b":"方式生成公私鑰", + "i18n_d0b2958432":"版本號", + "i18n_d0b7462bdc":"此編輯僅能編輯當前 SSH 在此工作空間的名稱信息", + "i18n_d0be2fcd05":":表示間隔時間,例如在分上,表示每兩分鐘,同樣*可以使用數字列表代替,逗號分隔", + "i18n_d0c06a0df1":"請輸入驗證碼", + "i18n_d0c879f900":"上傳前", + "i18n_d0eddb45e2":"私鑰", + "i18n_d0f53484dc":"腳本ID:", + "i18n_d1498d9dbf":"構建備註", + "i18n_d159466d0a":"關聯數據名", + "i18n_d175a854a6":"構建目錄", + "i18n_d17eac5b5e":"我要加入", + "i18n_d18d658415":"腳本ID", + "i18n_d19bae9fe0":"插件端安裝並啟動成功後將主動上報節點信息,如果上報的 IP+PROP 能正常通訊將新增節點信息", + "i18n_d1aa9c2da9":"子網掩碼:", + "i18n_d1b8eaaa9e":"需要輸入驗證碼,確認綁定後才生效奧", + "i18n_d1f0fac71d":"沒有選擇節點不能保存腳本", + "i18n_d1f56b0a7e":"傳入參數有:monitorId、monitorName、nodeId、nodeName、projectId、projectName、title、content、runStatus", + "i18n_d263a9207f":"支持 html 格式", + "i18n_d27cf91998":"參考地址:", + "i18n_d2cac1245d":"是否將【", + "i18n_d2e2560089":"文件名稱", + "i18n_d2f484ff7e":"。如果輸出最後一行不是預期格式項目狀態將是未運行", + "i18n_d2f4a1550a":"沒有任何節點腳本", + "i18n_d301fdfc20":"開啟自動創建用户後第一次登錄僅自動創建賬號,還需要管理員手動分配權限組", + "i18n_d30b8b0e43":"未構建", + "i18n_d31d625029":"加入 beta 計劃可以及時獲取到最新的功能、一些優化功能、最快修復 bug 的版本,但是 beta 版也可能在部分新功能上存在不穩定的情況。", + "i18n_d324f8b5c9":" 替換", + "i18n_d35a9990f4":"已無更多節點項目,請先創建項目", + "i18n_d373338541":"支持IP 地址、域名、主機名", + "i18n_d3ded43cee":"查看構建日誌", + "i18n_d3e480c8c0":"失敗次數", + "i18n_d3fb6a7c83":",2 秒後將自動跳轉到登錄頁面", + "i18n_d40b511510":"證書", + "i18n_d438e83c16":"項目分組", + "i18n_d4744ce461":"還沒有配置權限組,不能創建用户", + "i18n_d47ea92b3a":"編輯證書", + "i18n_d4aea8d7e6":"執行次數", + "i18n_d4e03f60a9":"插件端啟動的時候檢查項目狀態,如果項目狀態是未運行則嘗試執行啟動項目", + "i18n_d5269713c7":"表示構建產物為文件夾時將打包為", + "i18n_d57796d6ac":" :範圍:0~59", + "i18n_d584e1493b":"搜索ssh名稱", + "i18n_d58a55bcee":"關", + "i18n_d5a73b0c7f":"上傳", + "i18n_d5c2351c0e":"請選擇可以執行的星期", + "i18n_d5c68a926e":"執行順序", + "i18n_d5d46dd79b":"分鐘級別", + "i18n_d615ea8e30":"選擇升級文件", + "i18n_d61af4e686":"斷點/分片別名下載", + "i18n_d61b8fde35":"切換集羣", + "i18n_d64cf79bd4":"確認要加入 beta 計劃嗎?", + "i18n_d65d977f1d":"填寫運行參數", + "i18n_d679aea3aa":"運行中", + "i18n_d6937acda5":"項目存儲的文件夾,jar 包存放的文件夾", + "i18n_d6a5b67779":"系統進程", + "i18n_d6cdafe552":"會話已經關閉[project-console]", + "i18n_d6eab4107a":"Java 項目(java -jar Springboot war)【不推薦】", + "i18n_d72471c540":"瀏覽器標識", + "i18n_d731dc9325":"時間戳:", + "i18n_d7471c0261":"請選擇執行節點", + "i18n_d75c02d050":"停止項目", + "i18n_d7ac764d3a":"分發間隔時間 (順序重啟、完整順序重啟)方式才生效", + "i18n_d7ba18c360":"分發節點是指在編輯完腳本後自動將腳本內容同步節點的腳本中", + "i18n_d7bebd0e5e":"狀態操作請到控制枱中控制", + "i18n_d7c077c6f6":"命令日誌", + "i18n_d7cc44bc02":"用户資料", + "i18n_d7d11654a7":"不存在", + "i18n_d7ec2d3fea":"名稱", + "i18n_d7ee59f327":"私鑰,不填將使用默認的 $HOME/.ssh 目錄中的配置。支持配置文件目錄:file:", + "i18n_d7ef19d05b":"還沒有配置模板節點", + "i18n_d81bb206a8":"無", + "i18n_d82ab35b27":"文件前N行", + "i18n_d82b19148f":"請選擇要同步授權的機器節點", + "i18n_d83aae15b5":"在線構建文件主要保存,倉庫文件,構建歷史產物等。不支持主動清除,如果文件佔用過大可以配置保留規則和對單個構建配置是否保存倉庫、產物文件等", + "i18n_d84323ba8d":"倉庫自動遷移後可能會重複存在請手動解決", + "i18n_d87940854f":"計劃次數", + "i18n_d87f215d9a":"卡片", + "i18n_d88651584f":"剩餘空間", + "i18n_d8a36a8a25":"編輯 Docker 集羣", + "i18n_d8bf90b42b":"其他用户可以配置權限解除限制", + "i18n_d8c7e04c8e":"信息", + "i18n_d8db440b83":"您需要根據您業務情況來評估是否可以加入 beta。", + "i18n_d911cffcd5":"下載地址", + "i18n_d921c4a0b6":"真的要刪除“", + "i18n_d926e2f58e":"取消任務", + "i18n_d937a135b9":"淺色 eclipse", + "i18n_d94167ab19":"網絡端口", + "i18n_d9435aa802":"解析模式", + "i18n_d9531a5ac3":"還沒有配置權限組不能創建用户奧", + "i18n_d9569a5d3b":"多IP可以使用", + "i18n_d9657e2b5f":"請輸入項目文件夾", + "i18n_d9ac9228e8":"創建", + "i18n_d9c28e376c":"功能管理", + "i18n_da1abf0865":"驗證碼 6 為純數字", + "i18n_da1cb76e87":"請輸入腳本內容", + "i18n_da317c3682":"【推薦】使用快速安裝方式導入機器並自動新增邏輯節點", + "i18n_da4495b1b4":"郵箱:", + "i18n_da509a213f":"工作空間用於隔離數據,工作空間下面可以有不同數據,不同權限,不同菜單等來實現權限控制", + "i18n_da671a4d16":"微信:", + "i18n_da79c2ec32":"配置示例", + "i18n_da89135649":"企業服務", + "i18n_da8cb77838":"在線升級", + "i18n_da99dbfe1f":"分發狀態", + "i18n_dab864ab72":"快速綁定", + "i18n_dabdc368f5":"請選擇分配類型", + "i18n_dacc2e0e62":"硬件硬盤", + "i18n_dadd4907c2":"】目錄,", + "i18n_daf783c8cd":"分", + "i18n_db06c78d1e":"測試", + "i18n_db094db837":"修改變量值地址", + "i18n_db2d99ed33":"還未配置集羣地址,不能切換集羣", + "i18n_db4470d98d":"報警狀態", + "i18n_db4b998fbd":"Oauth2 使用提示", + "i18n_db5cafdc67":"真的要解綁節點麼?", + "i18n_db686f0328":"公鑰,不填將使用默認的 $HOME/.ssh 目錄中的配置。支持配置文件目錄:file:", + "i18n_db709d591b":"不同步", + "i18n_db732ecb48":"延遲", + "i18n_db81a464ba":"執行構建時會生成一個容器來執行,構建結束後會自動刪除對應的容器", + "i18n_db9296212a":"定時構建", + "i18n_dba16b1b92":"構建事件", + "i18n_dbad1e89f7":"兩步驗證", + "i18n_dbb166cf29":"服務id", + "i18n_dbb2df00cf":"發佈目錄", + "i18n_dbba7e107a":"發佈項目", + "i18n_dbc0b66ca4":"個結果", + "i18n_dc0d06f9c7":"請填寫發佈的二級目錄", + "i18n_dc2961a26f":"節點名:", + "i18n_dc2c61a605":"自建 Gitlab", + "i18n_dc32f465da":"容器地址 tcp", + "i18n_dc3356300f":"如果要配置 SSH 請到【系統管理】-> 【資產管理】-> 【SSH 管理】中去配置。", + "i18n_dc39b183ea":"確定要忽略綁定兩步驗證嗎?強烈建議超級管理員開啟兩步驗證來保證賬號安全性", + "i18n_dcc846e420":"批量構建全部參數舉例", + "i18n_dcd72e6014":"獲取單個構建狀態地址", + "i18n_dce5379cb9":"隱藏", + "i18n_dcf14deb0e":"代理地址 (127.0.0.1:8888)", + "i18n_dd1d14efd6":"查看腳本", + "i18n_dd23fdf796":"編輯過程中可以切換節點但是要注意數據是否匹配", + "i18n_dd4e55c39c":"未開始", + "i18n_dd95bf2d45":"正常登錄", + "i18n_ddc7d28b7b":"變量", + "i18n_dddf944f5f":"重置頁面操作引導、導航成功", + "i18n_ddf0c97bce":"請注意備份數據防止數據丟失!!", + "i18n_ddf7d2a5ce":"命令", + "i18n_de17fc0b78":"硬盤最大的使用率:", + "i18n_de3394b14e":"沒有資產機器", + "i18n_de4cf8bdfa":"付費加入我們的技術交流羣優先解答您所有疑問", + "i18n_de5dadc480":"pull日誌", + "i18n_de6bc95d3b":"清空當前目錄文件", + "i18n_de78b73dab":"單個觸發器地址", + "i18n_debdfce084":"請輸入集羣名稱", + "i18n_decef97c7c":"服務端IP授權配置", + "i18n_deea5221aa":"標記", + "i18n_df011658c3":"範圍", + "i18n_df1da2dc59":"容器在一個 CPU 週期內可以獲得的 CPU 時間的微秒。", + "i18n_df3833270b":"地址:", + "i18n_df39e42127":"自動執行", + "i18n_df59a2804d":"禁止掃描", + "i18n_df5f80946d":"# node 鏡像源 https://registry.npmmirror.com/-/binary/node/", + "i18n_df9497ea98":"存在", + "i18n_df9d1fedc5":"節點分發是指,一個項目部署在多個節點中使用節點分發一步完成多個節點中的項目發佈操作", + "i18n_dfb8d511c7":"用户名稱", + "i18n_dfcc9e3c45":"分發後操作", + "i18n_e039ffccc8":"】此文件還原到項目目錄?", + "i18n_e049546ff3":"【系統配置目錄】中修改", + "i18n_e06497b0fb":"查看當前可用容器", + "i18n_e06caa0060":"文件修改時間", + "i18n_e074f6b6af":"SMTP 服務器端口", + "i18n_e07cbb381c":"沒有任何倉庫", + "i18n_e09d0d8c41":"不建議使用常用名稱如", + "i18n_e0a0e26031":"使用當前鏡像創建一個容器", + "i18n_e0ae638e73":"保留產物是指對在構建完成後是否保留構建產物相關文件,用於回滾", + "i18n_e0ba3b9145":"節點腳本", + "i18n_e0ce74fcac":"自動滾動", + "i18n_e0d6976b48":"請選擇集羣關聯分組", + "i18n_e0ea800e34":"打包正式環境 npm i && npm run build:prod", + "i18n_e0ec07be7d":"客户端密鑰", + "i18n_e0f937d57f":"臨時token", + "i18n_e0fcbca309":"工作空間ID:", + "i18n_e15f22df2d":"真的要清除構建信息麼?", + "i18n_e166aa424d":"關於系統", + "i18n_e17a6882b6":"腳本標記", + "i18n_e19cc5ed70":"同步節點授權", + "i18n_e1c965efff":"請選擇狀態", + "i18n_e1fefde80f":"節點賬號密碼默認由系統生成:可以通過插件端數據目錄下 agent_authorize.json 文件查看(如果自定義配置了賬號密碼將沒有此文件)", + "i18n_e222f4b9ad":"執行前需要檢查命令中的地址在對應的服務器中是否可以訪問,如果無法訪問將不能自動綁定節點,", + "i18n_e257dd2607":"請選擇SSH連接信息", + "i18n_e26dcacfb1":" 檢查 ", + "i18n_e2adcc679a":"單位 ns 秒", + "i18n_e2b0f27424":"項目ID僅會在機器節點中限制唯一,不同工作空間(相同的工作空間)下是允許相同的項目ID", + "i18n_e2be9bab6b":"複製下面任意一條命令到還未安裝插件端的服務器中去執行,執行前需要放行", + "i18n_e2f942759e":"會話異常", + "i18n_e30a93415b":"使用私人令牌,可以在你不輸入賬號密碼的情況下對你賬號內的倉庫進行管理,你可以在創建令牌時指定令牌所擁有的權限。", + "i18n_e319a2a526":"重置為重新生成觸發地址,重置成功後之前的觸發器地址將失效,構建觸發器綁定到生成觸發器到操作人上,如果將對應的賬號刪除觸發器將失效", + "i18n_e31ca72849":"上傳壓縮文件", + "i18n_e354969500":"令牌導入", + "i18n_e362bc0e8a":"路徑:{source}(宿主機) => {destination}(容器)", + "i18n_e39de3376e":"分配", + "i18n_e39f4a69f4":"腳本數", + "i18n_e39ffe99e9":"請輸入密碼", + "i18n_e3cf0abd35":"郵箱驗證碼", + "i18n_e3e85de50c":"請選擇構建方式", + "i18n_e3ead2bd0d":"常見構建命令示例", + "i18n_e3ee3ca673":"不追加腳本模板", + "i18n_e4013f8b81":"機器名稱", + "i18n_e414392917":"集羣信息:", + "i18n_e42b99d599":"月", + "i18n_e43359ca06":"請選擇 SSH節點", + "i18n_e44f59f2d9":"發佈前命令", + "i18n_e475e0c655":"證書共享", + "i18n_e48a715738":"新建文件", + "i18n_e4b51d5cd0":"運行狀態", + "i18n_e4bea943de":"倉庫地址", + "i18n_e4bf491a0d":"正在下載,請稍等...", + "i18n_e4d0ebcd58":"選擇集羣", + "i18n_e5098786d3":"args 參數", + "i18n_e54029e15b":"退出集羣", + "i18n_e54c5ecb54":"編輯構建", + "i18n_e5915f5dbb":"(存在兼容問題,實際使用中需要提前測試) python3 sdk 鏡像使用:https://repo.huaweicloud.com/python/{'${PYTHON3_VERSION}'}/Python-{'${PYTHON3_VERSION}'}.tar.xz", + "i18n_e5a63852fd":"節點密碼,請查看節點啟動輸出的信息", + "i18n_e5ae5b36db":"關鍵詞高亮,支持正則(正則可能影響性能請酌情使用)", + "i18n_e5f71fc31e":"搜索", + "i18n_e60389f6d6":"當前前端打包時間:", + "i18n_e60725e762":"週三", + "i18n_e63fb95deb":"隊列數", + "i18n_e64d788d11":"升級成功", + "i18n_e6551a2295":"引用工作空間環境變量可以方便後面多處使用相同的密碼統一修改", + "i18n_e6bf31e8e6":"長期token", + "i18n_e6cde5a4bc":"沒有檢查到最新版", + "i18n_e6e453d730":"請輸入變量名稱", + "i18n_e6e5f26c69":"QQ郵箱 SSL", + "i18n_e703c7367c":"當前狀態:", + "i18n_e710da3487":"用時", + "i18n_e72a0ba45a":"用户組", + "i18n_e72f2b8806":"輸入倉庫名稱或者倉庫路徑進行搜索", + "i18n_e747635151":"Script 名稱", + "i18n_e76e6a13dd":"不引用環境變量", + "i18n_e78e4b2dc4":"級別", + "i18n_e7d83a24ba":"成功次數", + "i18n_e7e8d4c1fb":"斷點/分片下載", + "i18n_e7ffc33d05":"上傳後執行", + "i18n_e8073b3843":"請選擇用户權限組", + "i18n_e825ec7800":"協議類型", + "i18n_e8321f5a61":"發佈方式:", + "i18n_e83a256e4f":"確認", + "i18n_e84b981eb4":"配置值 (如:5g)", + "i18n_e8505e27f4":"升級前請閲讀更新日誌裏面的説明和注意事項並且", + "i18n_e8e3bfbbfe":"確認關閉", + "i18n_e8f07c2186":"如果未填寫將解析壓縮包裏面的 txt", + "i18n_e9290eaaae":"關閉左側", + "i18n_e930e7890f":"表達式類似於Linux的crontab表達式,表達式使用空格分成5個部分,按順序依次為:", + "i18n_e95f9f6b6e":"SSL 連接", + "i18n_e96705ead1":"如果按鈕不可用則表示當前節點已經關閉啦,需要去編輯中啟用", + "i18n_e976b537f1":"緩存監控", + "i18n_e97a16a6d7":" :每兩分鐘執行", + "i18n_e9bd4484a7":"發送方郵箱賬號", + "i18n_e9c2cb1326":"次要ID", + "i18n_e9e9373c6f":"執行任務中", + "i18n_e9ea1e7c02":"文件保存天數,默認 3650 天", + "i18n_e9ec2b0bee":"並等待上一個項目啟動完成才能關閉下一個項目", + "i18n_e9f2c62e54":",新增默認參數後在手動執行腳本時需要填寫參數值", + "i18n_ea15ae2b7f":"選項", + "i18n_ea3c5c0d25":"臨時文件佔用空間:", + "i18n_ea58a20cda":"機器DOCKER", + "i18n_ea7fbabfa1":"請輸入賬户名", + "i18n_ea89a319ec":"# 宿主機目錄和容器目錄掛載 /host:/container:ro", + "i18n_ea8a79546f":"請輸入發佈的文件id", + "i18n_ea9f824647":"拉取倉庫超時時間,單位秒", + "i18n_eaa5d7cb9b":"過期天數", + "i18n_eadd05ba6a":"中等", + "i18n_eaf987eea0":"權重(相對權重)。", + "i18n_eb164b696d":"排除發佈", + "i18n_eb5bab1c31":"非必填", + "i18n_eb79cea638":"週五", + "i18n_eb7f9ceb71":"腳本庫:", + "i18n_eb969648aa":"請提前備份數據再操作奧", + "i18n_ebc2a1956b":"編輯監控", + "i18n_ebc96f0a5d":"總內存(內存 + 交換)。 設置為 -1 以禁用交換。", + "i18n_ec1f13ff6d":"總數:", + "i18n_ec219f99ee":"執行結束", + "i18n_ec22193ed1":"請選擇分組", + "i18n_ec537c957a":"{slot1}機目錄", + "i18n_ec6e39a177":"確認要下載更新最新版本嗎?", + "i18n_ec7ef29bdf":"請輸入靜態,回車支持輸入多個路徑,系統會自動過濾 ../ 路徑、不允許輸入根路徑", + "i18n_ec989813ed":"狀態信息:", + "i18n_eca37cb072":"創建時間", + "i18n_ecdf9093d0":"同時展開多個", + "i18n_ecff77a8d4":"使用", + "i18n_ed145eba38":"硬盤佔用", + "i18n_ed19a6eb6f":"在線構建文件佔用空間", + "i18n_ed367abd1a":"修改用户資料", + "i18n_ed39deafd8":"編輯倉庫", + "i18n_ed40308fe9":"# maven 鏡像源 https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_ed6a8ee039":" :表示多個定時表達式", + "i18n_ed8ea20fe6":"安裝ID", + "i18n_edb4275dcd":"gmail郵箱", + "i18n_edb881412a":"注意:為了避免不必要的事件執行腳本,選擇的腳本的備註中包含需要實現的事件參數關鍵詞,如果需要執行 success 事件,那麼選擇的腳本的備註中需要包含 success 關鍵詞", + "i18n_edc1185b8e":"嘗試自動續簽失敗", + "i18n_edd716f524":"請選擇發佈的一級目錄", + "i18n_ede2c450d1":"沒有任何登錄日誌", + "i18n_ee19907fad":"# 基礎鏡像 目前僅支持 ubuntu-latest", + "i18n_ee4fac2f3c":"為了避免部分節點不能及時響應造成監控阻塞,節點統計超時時間不受節點超時配置影響將採用默認超時時間(10秒)", + "i18n_ee6ce96abb":"s 秒", + "i18n_ee8ecb9ee0":"優先級", + "i18n_ee9a51488f":"請輸入發佈目錄", + "i18n_eeb6908870":"上一步", + "i18n_eec342f34e":"默認賬號為:jpomAgent", + "i18n_eee6510292":"配置授權目錄", + "i18n_eee83a9211":"資源", + "i18n_eeef8ced69":"解綁會檢查數據關聯性,同時將自動刪除節點項目和腳本緩存信息", + "i18n_eef3653e9a":"jvm{slot1},{slot2}.如:-Xms512m -Xmx512m", + "i18n_eef4dfe786":"Java 項目(java -Djava.ext.dirs=lib -cp conf:run.jar $MAIN_CLASS)", + "i18n_ef016ab402":"確認創建該", + "i18n_ef28d3bff2":"頁面內容自動撐開出現屏幕滾動條", + "i18n_ef651d15b0":"創建之後不能修改,分發 ID 等同於項目 ID", + "i18n_ef734bf850":"更多説明", + "i18n_ef7e3377a0":"允許時段", + "i18n_ef800ed466":"程序運行的 main 類(jar 模式運行可以不填)", + "i18n_ef8525efce":"分配到其他工作空間", + "i18n_ef9c90d393":"沒有任何的腳本庫", + "i18n_efae7764ac":"賬號登錄", + "i18n_efafd0cbd4":"密碼(6-18位數字、字母、符號組合)", + "i18n_efb88b3927":"系統運行時間", + "i18n_efd32e870d":"插件構建時間", + "i18n_efe71e9bec":"真的要解綁當前節點分發麼?", + "i18n_efe9d26148":"真的要刪除該證書麼,刪除會將證書文件一併刪除奧?", + "i18n_f038f48ce5":"編輯腳本", + "i18n_f04a289502":"svn ssh 必填登錄用户", + "i18n_f05e3ec44d":"禁止訪問,當前IP限制訪問", + "i18n_f06f95f8e6":"孤獨數據", + "i18n_f087eb347c":"構建命令示例", + "i18n_f08afd1f82":"已選擇", + "i18n_f0a1428f65":"賬號支持引用工作空間變量:", + "i18n_f0aba63ae7":"頒發者", + "i18n_f0db5d58cb":"開啟兩步驗證使賬號更安全", + "i18n_f0eb685a84":"文件來兼容您的機器", + "i18n_f105c1d31d":"最後執行人", + "i18n_f113c10ade":"排空", + "i18n_f139c5cf32":"輸入新名稱", + "i18n_f175274df0":"登錄名稱,賬號,創建之後不能修改", + "i18n_f1a2a46f52":"# 使用哪個 docker 構建,填寫 docker 標籤 默認查詢可用的第一個,如果 tag 查詢出多個也選擇第一個結果", + "i18n_f1b2828c75":"安裝插件端", + "i18n_f1d8533c7f":"請輸入證書信息或者選擇證書信息,證書信息填寫規則:序列號:證書類型", + "i18n_f1fdaffdf0":"後台構建", + "i18n_f240f9d69c":"分支名稱:", + "i18n_f26225bde6":"詳情", + "i18n_f26ef91424":"下載", + "i18n_f282058f75":"靜態文件項目(前端、日誌等)", + "i18n_f2d05944ad":"創建 Docker 集羣", + "i18n_f30f1859ba":"如果您基於 Jpom 二次開發修改了", + "i18n_f332f2c8df":"網關", + "i18n_f3365fbf4d":"未獲取到 Docker 或者禁用監控", + "i18n_f33db5e0b2":"點擊刷新構建信息", + "i18n_f37f8407ec":"文件ID:", + "i18n_f3947e6581":"開源不等同於免費", + "i18n_f3e93355ee":"重啟項目", + "i18n_f425f59044":"系統版本:", + "i18n_f49dfdace4":"權限組", + "i18n_f4b7c18635":"密碼長度為6-20", + "i18n_f4baf7c6c0":"未啟動", + "i18n_f4bbbaf882":"分支/標籤", + "i18n_f4dd45fca9":"請輸入遠程地址", + "i18n_f4edba3c9d":"未知的表格類型", + "i18n_f4fb0cbecf":"還沒有任何結果", + "i18n_f5399c620e":"真的要在該集羣剔除此節點麼?", + "i18n_f562f75c64":"服務地址", + "i18n_f56c1d014e":"執行成功", + "i18n_f5c3795be5":"官方", + "i18n_f5d0b69533":"完整的私鑰內容 如", + "i18n_f5d14ee3f8":"磁盤佔用", + "i18n_f5f65044ea":"容器安裝的服務端不能使用本地構建(因為本地構建依賴啟動服務端本地的環境,容器方式安裝不便於管理本地依賴插件)", + "i18n_f63345630c":"# 將容器中的 node_modules 文件緩存到 docker 卷中", + "i18n_f63870fdb0":"請填寫容器名稱", + "i18n_f652d8cca7":"嘗試自動續簽...", + "i18n_f66335b5bf":"錯誤信息:", + "i18n_f66847edb4":"網頁應用ID", + "i18n_f668c8c881":"集羣名稱:", + "i18n_f685377a22":"腳本庫 ", + "i18n_f68f9b1d1b":"最後心跳時間", + "i18n_f6d6ab219d":"更新完成後確實成功的時間", + "i18n_f6d96c1c8c":"為了兼容Quartz表達式,同時支持6位和7位表達式,其中:", + "i18n_f6dee0f3ff":"分發 ID", + "i18n_f712d3d040":"備註示例:", + "i18n_f71316d0dd":"替換引用", + "i18n_f71a30c1b9":"數據目錄佔用空間", + "i18n_f7596f3159":"如果需要在其他工作空間需要提前切換生成命令", + "i18n_f76540a92e":"準備中", + "i18n_f782779e8b":"結束時間", + "i18n_f7b9764f0f":"項目啟動,停止,重啟都將請求對應的地址", + "i18n_f7e8d887d6":"工作空間環境變量", + "i18n_f7f340d946":"真的要清除 SSH 隱藏字段信息麼?(密碼,私鑰)", + "i18n_f8460626f0":"節點賬號,請查看節點啟動輸出的信息", + "i18n_f86324a429":"使用 ANT 表達式來實現在過濾指定目錄來實現發佈排除指定目錄", + "i18n_f89cc4807e":"授權路徑是指項目文件存放到服務中的文件夾", + "i18n_f89fa9b6c6":"選擇倉庫", + "i18n_f8a613d247":"請選擇節點", + "i18n_f8b3165e0d":"當前項目被禁用", + "i18n_f8f20c1d1e":"修剪在此時間戳之前創建的對象 例如:24h", + "i18n_f8f456eb9a":"類型項目特有的 type:reload、restart", + "i18n_f932eff53e":"條數據", + "i18n_f9361945f3":"主機名 hostname", + "i18n_f967131d9d":"倉庫名稱", + "i18n_f976e8fcf4":"監控名稱", + "i18n_f97a4d2591":"請選擇要加入到哪個集羣", + "i18n_f9898595a0":"注意:同一個分組不建議被多個集羣綁定", + "i18n_f98994f7ec":"發佈方式", + "i18n_f99ead0a76":"鏡像名稱不正確 不能更新", + "i18n_f9ac4b2aa6":"操作人", + "i18n_f9c9f95929":"Java 項目(java -classpath)", + "i18n_f9cea44f02":"當前工作空間還沒有 Docker", + "i18n_f9f061773e":"不填寫則使用節點分發配置的二級目錄", + "i18n_fa2f7a8927":"失敗策略", + "i18n_fa4aa1b93b":"運行項目", + "i18n_fa57a7afad":"容器標籤,如:xxxx:latest 多個使用逗號隔開, 配置附加環境變量文件支持加載倉庫目錄下 .env 文件環境變量 如: xxxx:{'${VERSION}'}", + "i18n_fa624c8420":"禁用後該用户不能登錄平台", + "i18n_fa7f6fccfd":"項目名稱:", + "i18n_fa7ffa2d21":"解鎖", + "i18n_fa8e673c50":"編輯工作空間", + "i18n_faa1ad5e5c":"協議", + "i18n_faaa995a8b":"可以關閉", + "i18n_faaadc447b":"序號", + "i18n_fabc07a4f1":"請選擇監控操作", + "i18n_fad1b9fb87":"新增腳本模版需要到節點管理中去新增", + "i18n_fb1f3b5125":"當前工作空間關聯數據統計", + "i18n_fb3a2241bb":"狀態描述:", + "i18n_fb5bc565f3":"解析文件失敗:", + "i18n_fb61d4d708":"真的要回滾該構建歷史記錄麼?", + "i18n_fb7b9876a6":"請輸入腳本名稱", + "i18n_fb852fc6cc":"進行中", + "i18n_fb8fb9cc46":"統計説明", + "i18n_fb91527ce5":"節點可用性:", + "i18n_fb9d826b2f":"發佈後執行的命令(非阻塞命令),一般是啟動項目命令 如:ps -aux | grep java", + "i18n_fba5f4f19a":"DSL環境變量", + "i18n_fbd7ba1d9b":"最後分發時間", + "i18n_fbee13a873":"工作空間總數:", + "i18n_fbfa6c18bf":"已分配", + "i18n_fbfeb76b33":"左邊菜單欄主題切換", + "i18n_fc06c70960":"您確定要刪除當前鏡像嗎?", + "i18n_fc4e2c6151":"登錄用户", + "i18n_fc5fb962da":"郵箱密碼或者授權碼", + "i18n_fc92e93523":"生效時間", + "i18n_fc954d25ec":"代理", + "i18n_fcaef5b17a":"重用另一個容器網絡堆棧", + "i18n_fcb4c2610a":"通知異常", + "i18n_fcb7a47b70":"阿里雲企業郵箱", + "i18n_fcba60e773":"構建", + "i18n_fcbf0d0a55":"需要先安裝依賴 yarn && yarn run build", + "i18n_fcca8452fe":"集羣地址主要用於切換工作空間自動跳轉到對應的集羣", + "i18n_fcef976c7a":"私鑰內容", + "i18n_fd6e80f1e0":"正常", + "i18n_fd7b461411":"不清空", + "i18n_fd7e0c997d":"選擇文件", + "i18n_fd93f7f3d7":"可以將腳本分發到機器節點中在 DSL 項目中引用,達到多個項目共用相同腳本", + "i18n_fda92d22d9":"關聯節點會自動識別服務器中是否存在 java 環境,如果沒有 Java 環境不能快速安裝節點", + "i18n_fdba50ca2d":"如果端口暴露到公網很", + "i18n_fdbac93380":"SMTP 地址:smtp.mxhichina.com,端口使用 465 並且開啟 SSL,用户名需要和郵件發送人一致,密碼為郵箱的登錄密碼", + "i18n_fdbc77bd19":"安全", + "i18n_fdcadf68a5":"SMTP 端口", + "i18n_fde1b6fb37":"需要提前為機器配置授權目錄", + "i18n_fdfd501269":"java sdk 鏡像使用:https://mirrors.tuna.tsinghua.edu.cn/ 支持版本有:8, 9, 10, 11, 12, 13, 14, 15, 16, 17", + "i18n_fe1b192913":"目錄創建成功後需要手動刷新右邊樹才能顯示出來喲", + "i18n_fe231ff92f":"關閉頁面操作引導、導航", + "i18n_fe2df04a16":"版本", + "i18n_fe32def462":"活躍", + "i18n_fe7509e0ed":"值", + "i18n_fe828cefd9":"項目文件夾是項目實際存放的目錄名稱", + "i18n_fe87269484":"集羣修改時間", + "i18n_fea996d31e":"請填寫構建名稱", + "i18n_fec6151b49":"賬户名稱", + "i18n_feda0df7ef":"賬號郵箱", + "i18n_ff17b9f9cd":"企業微信", + "i18n_ff1fda9e47":"禁止", + "i18n_ff39c45fbc":"使用容器內的主機網絡堆棧。 注意:主機模式賦予容器對本地系統服務(如 D-bus)的完全訪問權限,因此被認為是不安全的。", + "i18n_ff3bdecc5e":"文件查看(如果自定義配置了賬號密碼將沒有此文件)", + "i18n_ff80d2671c":"秒後刷新", + "i18n_ff9814bf6b":"觸發類型", + "i18n_ff9dffec4d":"搜索模式", + "i18n_ffa9fd37b5":"工作空間管理", + "i18n_ffaf95f0ef":"啟動的容器 可以看到很多host上的設備 可以執行mount。 可以在docker容器中啟動docker容器。", + "i18n_ffd67549cf":":範圍:1~12,同時支持不區分大小寫的別名:_##_jan\",\"feb\", \"mar\", \"apr\", \"may\",\"jun\", \"jul\", \"aug\",\"sep\",\"oct\", \"nov\", \"dec\"", + "i18n_fffd3ce745":"共享" +} \ No newline at end of file diff --git a/web-vue/src/i18n/locales/zh_tw.json b/web-vue/src/i18n/locales/zh_tw.json new file mode 100644 index 0000000000..32cecd5fda --- /dev/null +++ b/web-vue/src/i18n/locales/zh_tw.json @@ -0,0 +1,2806 @@ +{ + "i18n_0006600738":"加入 Docker 叢集", + "i18n_005de9a4eb":"構建歷史是用於記錄每次構建的資訊,可以保留構建產物資訊,構建日誌。同時還可以快速回滾釋出", + "i18n_0079d91f95":"確定要將此資料置頂嗎?", + "i18n_007f23e18f":"關閉 TLS 認證", + "i18n_00a070c696":"點選可以複製", + "i18n_00b04e1bf0":"傳送包", + "i18n_00d5bdf1c3":"呼叫次數", + "i18n_00de0ae1da":"檔案上傳前需要執行的指令碼(非阻塞命令)", + "i18n_01081f7817":"請輸入允許編輯檔案的字尾及檔案編碼,不設定編碼則預設取系統編碼,多個使用換行。示例:設定編碼:txt{'@'}utf-8, 不設定編碼:txt", + "i18n_010865ca50":"真的要停止專案麼?", + "i18n_0113fc41fc":"全屏日誌", + "i18n_01198a1673":"上傳小檔案", + "i18n_01226f48fc":"對於每一個子表示式,同樣支援以下形式:", + "i18n_0128cdaaa3":"分配型別", + "i18n_01ad26f4a9":"重置觸發器 token 資訊,重置後之前的觸發器 token 將失效", + "i18n_01b4e06f39":"重啟", + "i18n_01e94436d1":"原密碼", + "i18n_020d17aac6":"傳送大小", + "i18n_020f1ecd62":"開始上傳", + "i18n_020f31f535":"路徑需要配置絕對路徑,不支援軟鏈", + "i18n_0215b91d97":"構建序號id需要跟進實際情況替換", + "i18n_0221d43e46":"遠端下載Url不為空", + "i18n_0227161b3e":"執行方式", + "i18n_022b6ea624":"您確定要刪除當前卷嗎?", + "i18n_0253279fb8":"克隆深度", + "i18n_02d46f7e6f":"真的要刪除這些構建歷史記錄麼?", + "i18n_02d9819dda":"提示", + "i18n_02db59c146":"禁止訪問的 IP 地址", + "i18n_02e35447d4":"下載構建產物,如果按鈕不可用表示產物檔案不存在,一般是構建沒有產生對應的檔案或者構建歷史相關檔案被刪除", + "i18n_0306ea1908":"刪除映象", + "i18n_031020489f":"當前工作空間您觸發的構建記錄", + "i18n_03580275cb":"請選中要重啟的專案", + "i18n_0360fffb40":"並開啟此開關", + "i18n_036c0dc2aa":"系統取消分發", + "i18n_0373ba5502":"需要您在需要被管理的伺服器中安裝 agent ,並將 agent 資訊新增到系統中", + "i18n_03816381ec":"切換檢視", + "i18n_0390e2f548":"引數{count}描述", + "i18n_03a74a9a8a":"日誌路徑", + "i18n_03c1f7c142":"請填選擇構建的倉庫", + "i18n_03d9de2834":"專案運維", + "i18n_03dcdf92f5":"隱私變數", + "i18n_03e59bb33c":"緊湊", + "i18n_03f38597a6":"速度", + "i18n_0428b36ab1":"副本", + "i18n_04412d2a22":"操作不能撤回奧", + "i18n_044b38221e":"Java 專案(示例參考,具體還需要根據專案實際情況來決定)", + "i18n_045cd62da3":"型號:", + "i18n_045f89697e":"壓縮包進行釋出", + "i18n_047109def4":"待處理", + "i18n_04a8742dd7":"外掛執行時間", + "i18n_04edc35414":"模板節點", + "i18n_051fa113dd":"方式連線 docker 是通過終端實現,每次操作 docker 相關 api 需要登入一次終端", + "i18n_05510a85b0":"系統中您所有操作日誌", + "i18n_059ac641c0":"特權:", + "i18n_05b52ae2db":"{slot1} 用於容器構建選擇容器功能(fromTag)", + "i18n_05cfc9af9d":"接收錯誤", + "i18n_05e6d88e29":"分發節點是指在編輯完指令碼後自動將指令碼內容同步節點的指令碼,一般使用者節點分發功能中的 DSL 模式", + "i18n_05e78c26b1":"單個觸發器地址中:第一個隨機字串為命令指令碼ID,第二個隨機字串為 token", + "i18n_05f6e923af":"執行錯誤", + "i18n_0647b5fc26":"先停止", + "i18n_066431a665":"請輸入證書描述", + "i18n_066f903d75":"操後上移或者下移可能不會達到預期排序", + "i18n_067638bede":"CPU數", + "i18n_067eb0fa04":"如果這裡的報警聯絡人無法選擇,說明這裡面的管理員沒有設定郵箱,在右上角下拉選單裡面的使用者資料裡可以設定。", + "i18n_0693e17fc1":"有新內容後是否自動滾動到底部", + "i18n_06986031a7":"需要到原始工作空間中去控制節點分發", + "i18n_06e2f88f42":"請輸入名稱", + "i18n_0703877167":"關閉MFA", + "i18n_0719aa2bb0":"重置密碼", + "i18n_0728fee230":"請輸入公告標題", + "i18n_072fa90836":"壓縮 ", + "i18n_0739b9551d":"埠協議", + "i18n_07683555af":"當前版本號:", + "i18n_0793aa7ba3":"maven sdk 映象使用:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_07a03567aa":"虛擬記憶體佔用", + "i18n_07a0e44145":"主機名:", + "i18n_07a828310b":"並行度", + "i18n_07a8af8c03":"為當前專案實際的程序ID", + "i18n_07b6bb5e40":"嚴格執行指令碼(構建命令、事件指令碼、本地釋出指令碼、容器構建命令)執行返回狀態碼必須是 0、否則將構建狀態標記為失敗", + "i18n_07d2261f82":"預設是當前時間到今年結束", + "i18n_080b914139":"上傳包", + "i18n_0836332bf6":"升級協議", + "i18n_083b8a2ec9":"一個物理節點被多個服務端繫結也會產生孤獨資料奧", + "i18n_08902526f1":"面板:", + "i18n_0895c740a6":"交換記憶體佔用", + "i18n_089a88ecee":"系統時間:", + "i18n_08ab230290":"操作說明", + "i18n_08ac1eace7":"檔案上傳成功後需要執行的指令碼(非阻塞命令)", + "i18n_08b1fa1304":"請輸入使用者名稱", + "i18n_08b55fea3c":"管理", + "i18n_0934f7777a":"新標籤終端", + "i18n_095e938e2a":"停止", + "i18n_09723d428d":"報警聯絡人", + "i18n_09d14694e7":"需要 SSH 監控中能獲取到 docker 資訊", + "i18n_09e7d24952":"實際記憶體佔用率:", + "i18n_0a056b0d5a":"動態檔案", + "i18n_0a1d18283e":"構建確認彈窗", + "i18n_0a47f12ef2":"如果孤獨資料被工作空間下的其他功能關聯,修正後關聯的資料將失效對應功能無法查詢到關聯資料", + "i18n_0a54bd6883":"Gmail 郵箱配置", + "i18n_0a60ac8f02":"是", + "i18n_0a63bf5b41":"軟記憶體限制。", + "i18n_0a9634edf2":"地址萬用字元,* 表示所有地址都將使用代理", + "i18n_0aa60d1169":"您還未登入過", + "i18n_0aa639865c":"真的要刪除機器 SSH 麼?", + "i18n_0ac4999a4c":"網絡卡資訊", + "i18n_0ac9e3e675":"繫結成功後將不再顯示,強烈建議儲存此二維碼或者下面的 MFA key", + "i18n_0af04cdc22":"支援兩種方式填充:", + "i18n_0af5d9f8e8":"當前區域為系統管理、資產管理中心", + "i18n_0b23d2f584":"差異構建", + "i18n_0b2fab7493":"當前 SSH 的授權目錄(檔案目錄、檔案字尾、禁止命令)需要請到 【系統管理】-> 【資產管理】-> 【SSH 管理】-> 操作欄中->關聯按鈕->對應工作空間->操作欄中->配置按鈕", + "i18n_0b3edfaf28":"設定記憶體限制。", + "i18n_0b58866c3e":"斷點/分片單檔案下載", + "i18n_0b76afbf5d":"允許執行的 CPU(例如,0-3、0", + "i18n_0b9d5ba772":"請尊重開源協議,不要擅自修改版本資訊,否則可能承擔法律責任。", + "i18n_0baa0e3fc4":"釋出中", + "i18n_0bac3db71c":"重啟服務端後失效", + "i18n_0bbc7458b4":"回到首頁", + "i18n_0bc45241af":"傳入引數有:outGivingId、outGivingName、status、statusMsg、executeTime", + "i18n_0bf9f55e9d":"不能關閉", + "i18n_0bfcab4978":"node sdk 映象使用:https://registry.npmmirror.com/-/binary/node", + "i18n_0c0633c367":"不能刪除預設工作空間", + "i18n_0c1de8295a":"獨立", + "i18n_0c1e9a72b7":"將使用微佇列來排隊構建,避免幾乎同時觸發構建被中斷構建(一般使用者倉庫合併程式碼會觸發多次請求),佇列儲存在記憶體中,重啟將丟失", + "i18n_0c1f1cd79b":"不自動重啟", + "i18n_0c1fec657f":"秒", + "i18n_0c2487d394":"下拉搜尋預設搜尋關鍵詞相關的前 10 個,以及已經選擇的機器節點", + "i18n_0c256f73b8":"容器名稱:", + "i18n_0c4eef1b88":"當為6位時,第一位表示", + "i18n_0c5c8d2d11":"基礎資訊:", + "i18n_0c7369bbee":"開啟SSH訪問", + "i18n_0cbf83cc07":"聯絡我們", + "i18n_0ccaa1c8b2":" :表示匹配這個位置所有的時間", + "i18n_0ce54ecc25":"付費社群", + "i18n_0cf4f0ba82":"真的要儲存當前配置嗎?如果配置有誤,可能無法啟動服務需要手動還原奧!!! 儲存成功後請及時關注重啟狀態!!", + "i18n_0cf81d77bb":"請填寫倉庫地址", + "i18n_0d44f4903a":"真的要釋放(刪除)當前專案麼?", + "i18n_0d467f7889":"# 是否開啟日誌備份功能", + "i18n_0d48f8e881":"請輸入服務地址", + "i18n_0d50838436":"資料目錄", + "i18n_0d98c74797":"其他", + "i18n_0da9b12963":"使用者資料", + "i18n_0de68f5626":"登入JPOM", + "i18n_0e052223a4":"重啟服務端需要重新獲取", + "i18n_0e16902c1e":"檢視狀態", + "i18n_0e1ecdae4a":"完整順序執行(有執行失敗將結束本次)", + "i18n_0e25ab3b51":"證書的允許的 IP 需要和 docker host 一致", + "i18n_0e44ae17ae":"服務端機器網路", + "i18n_0e502fed63":"重啟超時,請去伺服器檢視控制檯日誌排查問題", + "i18n_0e55a594fd":"監控專案", + "i18n_0e5f01b9be":"關聯工作空間ssh", + "i18n_0ea78e4279":"檢視日誌", + "i18n_0ec9eaf9c3":"更多", + "i18n_0eccc9451d":"# 備份檔案保留個數", + "i18n_0ee3ca5e88":"掃碼讚賞支援開源專案長期發展", + "i18n_0ef396cbcc":"分發結果", + "i18n_0f004c4cf7":"第三方登入", + "i18n_0f0a5f6107":"正常連線", + "i18n_0f189dbaa4":"沒有任何使用者", + "i18n_0f4f503547":"請輸入版本", + "i18n_0f539ff117":"真的要批量刪除選擇的映象嗎?已經被容器使用的映象無法刪除!", + "i18n_0f59fe5338":"防火牆埠", + "i18n_0f5fc9f300":"檔案管理中心", + "i18n_0f8403d07e":"重新整理倒計時", + "i18n_0fca8940a8":"沒有節點", + "i18n_0ff425e276":"檔案ID", + "i18n_1012e09849":"處理失敗", + "i18n_10145884ba":"檔案後N行", + "i18n_1014b33d22":"分組名稱", + "i18n_101a86bc84":"請輸入...", + "i18n_1022c545d1":"外掛端啟動時自動檢查專案如未啟動將嘗試啟動", + "i18n_102dbe1e39":"注意:環境變數存在作用域:當前工作空間或者全域性,不能跨工作空間引用", + "i18n_102e8ec6d5":"網路流量資訊", + "i18n_1058a0be42":"開啟 TLS 認證,證書資訊:", + "i18n_1062619d5a":"節點賬號密碼預設由系統生成:可以通過外掛端資料目錄下 agent", + "i18n_108d492247":"正則語法參考", + "i18n_10c385b47e":"一鍵分發同步多個節點的系統配置", + "i18n_10d6dfd112":"顯示後N行", + "i18n_10f6fc171a":"SSH 名稱", + "i18n_111e786daa":"填寫備註僅本次構建生效", + "i18n_1125c4a50b":"真的要刪除分發資訊麼?刪除後節點下面的專案也都將刪除", + "i18n_113576ce91":"產物目錄:", + "i18n_1149274cbd":"使用者總數", + "i18n_115cd58b5d":"】備份資料夾麼?", + "i18n_1160ab56fd":"構建命令:", + "i18n_116d22f2ab":"專案ID:", + "i18n_11724cd00b":"叢集建立時間", + "i18n_117a9cbc8d":"語言:", + "i18n_11957d12e4":"報警中", + "i18n_11e88c95ee":" 查詢上一個", + "i18n_121e76bb63":"請選擇構建對應的分支", + "i18n_1235b052ff":"節點地址 (192.168.1.100:2123)", + "i18n_1278df0cfc":"關聯節點如果伺服器存在 java 環境,但是外掛端未執行則會顯示快速安裝按鈕", + "i18n_127de26370":"SMTP 地址:【smtp.qq.com】,使用者名稱一般是QQ號碼,密碼是郵箱授權碼,埠預設 587/465", + "i18n_12934d1828":"日誌目錄是指控制檯日誌儲存目錄", + "i18n_12afa77947":"開啟快取構建目錄將保留倉庫檔案,二次構建將 pull 程式碼, 不開啟快取目錄每次構建都將重新拉取倉庫程式碼(較大的專案不建議關閉快取)", + "i18n_12d2c0aead":"請將此密碼複製告知該使用者", + "i18n_12dc402a82":"參考資料", + "i18n_130318a2a1":"路由無效,無法跳轉", + "i18n_1303e638b5":"修改時間", + "i18n_13627c5c46":"配置ssh", + "i18n_138776a1dc":"預設是在外掛端資料目錄/{'${projectId}'}/{'${projectId}'}.log", + "i18n_138a676635":"注意", + "i18n_13c76c38b7":"# scriptId 可以引用指令碼庫中的指令碼(G{'@'}xxx)其中 xxx 為指令碼庫中的指令碼標記,前提需要提取將對應指令碼同步至對應機器節點", + "i18n_13d10a9b78":"沒有資產SSH", + "i18n_13d947ea19":"需要您先新增資產機器再分配機器節點(邏輯節點)到當前工作空間", + "i18n_13f7bb78ef":"預設統計機器中除本地介面(環回或無硬體地址)網絡卡流量總和", + "i18n_13f931c5d9":"檢視任務", + "i18n_1432c7fcdb":"系統公告", + "i18n_143bfbc3a1":"點選重新同步當前工作空間邏輯節點專案資訊", + "i18n_143d8d3de5":"否則將刪除滿足條件的所有資料", + "i18n_148484b985":"實現您需要配置 docker 容器到服務端中來管理,並且分配到當前工作空間中", + "i18n_1498557b2d":"同時只能展開一個選單", + "i18n_14a25beebb":"10秒一次", + "i18n_14d342362f":"標籤", + "i18n_14dcfcc4fa":"還未執行reload", + "i18n_14dd5937e4":"附加環境變數 .env 新增多個使用逗號分隔", + "i18n_14e6d83ff5":"時間:", + "i18n_14ee5b5dc5":"命令檔案將在 {'${外掛端資料目錄}'}/script/xxxx.sh 、bat 執行", + "i18n_14feaa5b3a":"重新整理倒計時 ", + "i18n_1535fcfa4c":"傳送", + "i18n_156af3b3d1":"選單配置", + "i18n_1593dc4920":"真的要刪除該記錄麼?刪除後構建關聯的容器標籤將無法使用", + "i18n_159a3a8037":"更新映象", + "i18n_15c0ba2767":"上傳專案檔案", + "i18n_15c46f7681":"修改介面 HTTP 狀態碼為 200 並且響應內容為:success 才能確定操作成功反之均可能失敗", + "i18n_15d5fffa6a":"響應結果", + "i18n_15e9238b79":"接收", + "i18n_15f01c43e8":"日誌備份列表", + "i18n_15fa91e3ab":"天級別", + "i18n_1603b069c2":"週一", + "i18n_1622dc9b6b":"未知", + "i18n_162e219f6d":"丟失", + "i18n_164cf07e1c":"清空覆蓋", + "i18n_16646e46b1":"產物檔案大小:", + "i18n_16b5e7b472":"直接構建", + "i18n_16f7fa08db":"嗎?", + "i18n_17006d4d51":"是否自動跳轉到系統頁面", + "i18n_170fc8e27c":"週四", + "i18n_174062da44":"分發方式", + "i18n_1775ff0f26":"建議新增指定時間範圍", + "i18n_178ad7e9bc":"引數中的 id 、token 和觸發構建一致、buildNumId 構建序號id", + "i18n_17a101c23e":"孤獨資料是指機器節點裡面存在資料,但是無法和當前系統繫結上關係(關係繫結=節點ID+工作空間ID對應才行),一般情況下不會出現這樣的資料", + "i18n_17a74824de":"構建方式", + "i18n_17acd250da":"下移", + "i18n_17b4c9c631":"沒有任何節點", + "i18n_17b5e684e5":"需要到 節點管理中的【外掛端配置】的授權配置中配置允許編輯的檔案字尾", + "i18n_17c06f6a8b":"最後執行時間", + "i18n_17d444b642":"執行方式", + "i18n_1810e84971":"才能使用 SSH 方式連線", + "i18n_1818e9c264":"JVM總記憶體", + "i18n_1819d0cdda":"如果開啟同步到檔案管理中心,在構建釋出流程將自動執行同步到檔案管理中心的操作。", + "i18n_181e1ad17d":"長按可以拖動排序", + "i18n_1857e7024c":"系統版本", + "i18n_185926bf98":"全屏", + "i18n_1862c48f72":"本地狀態:", + "i18n_1880b85dc5":"黑白 ambiance", + "i18n_18b0ab4dd2":"機器SSH名", + "i18n_18b34cf50d":"不滾動", + "i18n_18c63459a2":"預設", + "i18n_18c7e2556e":"如果當前構建資訊已經在其他頁面更新過,需要點選重新整理按鈕來獲取最新的資訊,點選重新整理後未儲存的資料也將丟失", + "i18n_18d49918f5":"賬號被鎖定", + "i18n_18eb76c8a0":"memory 最小 4M", + "i18n_192496786d":"事件指令碼", + "i18n_19675b9d36":"清除程式碼(倉庫目錄)為刪除伺服器中儲存倉庫目錄裡面的所有東西,刪除後下次構建將重新拉起倉庫裡面的檔案,一般用於解決伺服器中檔案和遠端倉庫中檔案有衝突時候使用。執行時間取決於原始碼目錄大小和檔案數量如超時請耐心等待,或稍後重試", + "i18n_1974fe5349":"繫結成功", + "i18n_197be96301":"待完善", + "i18n_19f974ef6a":"開啟差異釋出並且開啟清空釋出時將自動刪除專案目錄下面有的檔案但是構建產物目錄下面沒有的檔案【清空釋出差異上傳前會先執行刪除差異檔案再執行上傳差異檔案】", + "i18n_19fa0be4d2":" 官方文件", + "i18n_19fcb9eb25":"時間", + "i18n_1a44b9e2f7":"同步到其他工作空間", + "i18n_1a55f76ace":"構建命令,構建產物相對路徑為:", + "i18n_1a56bb2237":"至少選擇一個節點和專案", + "i18n_1a6aa24e76":"執行", + "i18n_1a704f73c2":"請選擇一個檔案", + "i18n_1a8f90122f":"提示資訊 ", + "i18n_1abf39bdb6":"# 將此目錄快取到全域性(多個構建可以共享此快取目錄)", + "i18n_1ad696efdc":"構建執行的命令(非阻塞命令),如:mvn clean package、npm run build。支援變數:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_SOURCE_FILE}'}、{'${BUILD_NUMBER_ID}'}、倉庫目錄下 .env、工作空間變數", + "i18n_1ae2955867":"指定 pom 檔案打包 mvn -f xxx/pom.xml clean package", + "i18n_1afdb4a364":"隱藏滾動條。縱向滾動方式提醒:滾輪,橫行滾動方式:Shift+滾輪", + "i18n_1b03b0c1ff":"已經分配到工作空間的 Docker 或者叢集無非直接刪除,需要到分配到的各個工作空間逐一刪除後才能刪除資產 Docker 或者叢集", + "i18n_1b38c0bc86":"備份檔案儲存目錄:", + "i18n_1b5266365f":"原始IP", + "i18n_1b5bcdf115":"會話已經關閉[node-system-log]", + "i18n_1b7cba289a":"資料統計", + "i18n_1b8fff7308":"開啟 MFA", + "i18n_1b963fd303":"【推薦】騰訊身份驗證碼", + "i18n_1b973fc4d1":"分組名稱:", + "i18n_1ba141c9ac":"請選擇軟鏈的專案", + "i18n_1ba584c974":"配置容器", + "i18n_1baae8183c":"是否解壓", + "i18n_1c040e6b87":"一般情況下不建議降級操作", + "i18n_1c10461124":"示例:key,key1 或者 key=value,key1=value1", + "i18n_1c13276448":"當前工作空間關聯構建", + "i18n_1c2e9d0c76":"沒有任何構建", + "i18n_1c3cf7f5f0":"關聯", + "i18n_1c61dfb86f":"掛載點", + "i18n_1c8190b0eb":"請填寫專案 DSL 配置內容,可以點選上方切換 tab 檢視配置示例", + "i18n_1c83d79715":"執行失敗", + "i18n_1c9d3cb687":"使用者名稱ID", + "i18n_1cc82866a4":"分片運算元", + "i18n_1d0269cb77":"已經分配到工作空間的 SSH 無非直接刪除,需要到分配到的各個工作空間逐一刪除後才能刪除資產 SSH", + "i18n_1d263b7efb":"該選項僅本次構建生效", + "i18n_1d38b2b2bc":"請選擇專案授權路徑", + "i18n_1d53247d61":"請選擇邏輯節點", + "i18n_1d650a60a5":"硬碟", + "i18n_1d843d7b45":"此節點暫無專案", + "i18n_1dc518bddb":"專案儲存的資料夾", + "i18n_1dc9514548":"不等同於 PING 測試,此處測試成功表示網路一定通暢,此處測試失敗網路不一定不通暢", + "i18n_1de9b781bd":"使用容器構建,docker 容器所在的宿主機需要有公網,因為需要遠端下載環境依賴的 sdk 和映象", + "i18n_1e07b9f9ce":"請選擇要同步系統配置的機器節點", + "i18n_1e4a59829d":"外掛端開機自啟", + "i18n_1e5533c401":"配置目錄", + "i18n_1e5ca46c26":"排除釋出 ANT 表示式,多個使用逗號分隔", + "i18n_1e88a0cfaf":"不釋出到 docker 叢集", + "i18n_1e93bdad2a":"搜尋專案名", + "i18n_1eb378860a":"真的要 Kill 這個程序麼?", + "i18n_1eba2d93fc":"禁用原因", + "i18n_1ece1616bf":"如果外掛端正常執行但是連線失敗請檢查埠是否開放,防火牆規則,雲伺服器的安全組入站規則", + "i18n_1ed46c4a59":"分發名稱(專案名稱)", + "i18n_1f08329bc4":"搜尋命令名稱", + "i18n_1f0c93d776":" :每分鐘執行", + "i18n_1f0d13a9ad":"服務端分發同步的指令碼不能直接刪除,需要到服務端去操作", + "i18n_1f1030554f":"總計 {total} 條", + "i18n_1f130d11d1":"SMTP 伺服器", + "i18n_1f4c1042ed":"資料夾", + "i18n_1fa23f4daa":"過期時間", + "i18n_1fd02a90c3":"使用者", + "i18n_200707a186":"建立後構建方式不支援修改", + "i18n_2025ad11ee":"真的要解綁節點指令碼麼?", + "i18n_2027743b8d":"系統名稱:", + "i18n_204222d167":"網路延遲", + "i18n_2064fc6808":"不顯示", + "i18n_207243d77a":"如果要將工作空間分配給其他使用者還需要到許可權組管理", + "i18n_207d9580c1":"表示週六", + "i18n_209f2b8e91":"請輸入登入密碼", + "i18n_20a9290498":"您來到系統管理中心", + "i18n_20c8dc0346":"演示賬號", + "i18n_20e0b90021":"真的要刪除監控麼?", + "i18n_20f32e1979":"角色:", + "i18n_211354a780":"內的root只是外部的一個普通使用者許可權。預設false", + "i18n_21157cbff8":"毫秒", + "i18n_211a60b1d6":"編輯容器的一些基礎引數", + "i18n_2141ffaec9":"狀態資料是非同步獲取有一定時間延遲", + "i18n_2168394b82":"檔案id,精準搜尋", + "i18n_2171d1b07d":"預設引數", + "i18n_2191afee6e":"升級超時,請去伺服器檢視控制檯日誌排查問題", + "i18n_21d81c6726":"為當前工作空間中的容器配置標籤", + "i18n_21da885538":"可以使用節點指令碼:", + "i18n_21dd8f23b4":"開源協議", + "i18n_21e4f10399":"優先判斷禁用時段", + "i18n_21efd88b67":"暫無資料", + "i18n_220650a1f5":"配置後將儲存到當前構建", + "i18n_2213206d43":"點選延遲可以檢視對應節點網路延遲歷史資料", + "i18n_222316382d":"關聯節點", + "i18n_2223ff647d":"清空釋出", + "i18n_2245cf01a3":"您沒有許可權訪問", + "i18n_2246d128cb":"企業微信通知地址", + "i18n_22482533ff":"私鑰內容,不填將使用預設的 $HOME/.ssh 目錄中的配置。支援配置檔案目錄:file:/xxxx/xx", + "i18n_224aef211c":"構建資訊", + "i18n_224e2ccda8":"配置", + "i18n_2256690a28":"節點ID:", + "i18n_22670d3682":"請選擇要使用的指令碼", + "i18n_226a6f9cdd":"請檢查是否開啟 ws 代理", + "i18n_226b091218":"型別", + "i18n_22b03c024d":"二維碼", + "i18n_22c799040a":"容器", + "i18n_22cf31df5d":"當前訪問IP:", + "i18n_22e4da4998":"表示專案當前未執行", + "i18n_22e888c2df":"到期時間", + "i18n_2300ad28b8":"讀寫", + "i18n_2314f99795":"檢測到新版本 ", + "i18n_231f655e35":"當前程式打包時間:", + "i18n_23231543a4":"修正", + "i18n_2331a990aa":"掃碼轉賬支援開源專案長期發展", + "i18n_233fb56ab2":"在 設定-->安全設定-->私人令牌 中獲取", + "i18n_234e967afe":"釋出前執行的命令(非阻塞命令),一般是關閉專案命令,支援變數替換:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_2351006eae":"附加環境變數", + "i18n_23559b6453":"# 將容器中的 maven 倉庫檔案快取到 docker 卷中", + "i18n_2356fe4af2":"配合指令碼模版實現自定義專案管理", + "i18n_2358e1ef49":"所屬工作空間: ", + "i18n_235f0b52a1":"傳送錯誤", + "i18n_23b38c8dad":"會話已經關閉[upgrade]", + "i18n_23b444d24c":"快速配置", + "i18n_23eb0e6024":"暱稱", + "i18n_242d641eab":"字尾", + "i18n_2432b57515":"備註", + "i18n_24384ba6c1":"確定要重新同步當前節點專案快取資訊嗎?", + "i18n_24384dab27":"請輸入 value 的值", + "i18n_244d5a0ed8":"構建引數", + "i18n_2456d2c0f8":"如果容器以非零退出程式碼退出,則重新啟動容器。可以指定次數:on-failure:2", + "i18n_2457513054":"週六", + "i18n_2482a598a3":"外掛版本號", + "i18n_248c9aa7aa":"構建狀態", + "i18n_2493ff1a29":"自定義程序型別", + "i18n_2499b03cc5":"保留產物:", + "i18n_249aba7632":"天", + "i18n_24ad6f3354":"如果垮機器(資產機器)遷移之前機器中的專案資料僅是邏輯刪除(專案檔案和日誌均會保留)", + "i18n_24cc0de832":"執行命令", + "i18n_24d695c8e2":"叢集主機名", + "i18n_250688d7c9":"釋出失敗", + "i18n_250a999bb2":"容器標籤,如:xxxx:latest 多個使用逗號隔開", + "i18n_25182fb439":"工作空間選單", + "i18n_251a89efa9":"檢視當前狀態", + "i18n_252706a112":"【推薦】微信小程式搜尋 數盾OTP", + "i18n_2527efedcd":"使用者資訊 url", + "i18n_2560e962cf":"請選擇分發專案", + "i18n_257dc29ef7":"搜尋配置參考", + "i18n_25b6c22d8a":"為避免顯示內容太多而造成瀏覽器卡頓,讀取日誌最後多少行日誌。修改後需要回車才能重新讀取,小於 1 則讀取所有", + "i18n_25be899f66":"篩選之後本次釋出操作只發布篩選項,並且只對本次操作生效", + "i18n_25c6bd712c":"請輸入獲取的計劃執行次數", + "i18n_25f29ebbe6":"指令碼日誌數:", + "i18n_25f6a95de3":"確定要取消構建 【名稱:", + "i18n_2606b9d0d2":"分發機器", + "i18n_260a3234f2":"請選擇SSH", + "i18n_2611dd8703":"當目標工作空間不存在對應的節點時候將自動建立一個新的節點(邏輯節點)", + "i18n_26183c99bf":"檔案中心", + "i18n_2646b813e8":"登入密碼", + "i18n_267bf4bf76":"分發到節點中需要注意跨工作空間重名將被最後一次同步覆蓋", + "i18n_2684c4634d":"版本:", + "i18n_26a3378645":"請選擇執行方式", + "i18n_26b5bd4947":"載入中...", + "i18n_26bb841878":"新建", + "i18n_26bd746dc3":"真的要清空專案目錄和檔案麼?", + "i18n_26c1f8d83e":"最後操作人", + "i18n_26ca20b161":"來源", + "i18n_26eccfaad1":"映象:", + "i18n_26f95520a5":"執行命令包含:", + "i18n_26ffe89a7f":"專案名:", + "i18n_27054fefec":"執行指令碼傳入引數有:startReady、pull、executeCommand、release、done、stop、success", + "i18n_2770db3a99":"載入專案資料中...", + "i18n_2780a6a3cf":"TLS 認證", + "i18n_27b36afd36":"狀態碼為 0 的操作大部分為沒有操作結果或者非同步執行", + "i18n_27ba6eb343":"閘道器:", + "i18n_27ca568be2":"繼續", + "i18n_27d0c8772c":"如果誤操作會產生冗餘資料!!!", + "i18n_27f105b0c3":"請選擇要升級的節點", + "i18n_280379cee4":"儲存並關閉", + "i18n_282c8cda1f":"如果上報的節點資訊包含多個 IP 地址需要使用者確認使用具體的 IP 地址資訊", + "i18n_288f0c404c":"清空", + "i18n_28b69f9233":"構建映象的過程不使用快取", + "i18n_28b988ce6a":"檔案型別", + "i18n_28bf369f34":"釋出後的檔名是:檔案ID.字尾,並非檔案真實名稱 (可以使用上傳後指令碼隨意修改)", + "i18n_28c1c35cd9":"主節點不能直接剔除", + "i18n_28e0fcdf93":"還沒有容器或者未配置標籤不可以使用容器構建奧", + "i18n_28e1c746f7":"ssh 名", + "i18n_28e1eec677":"授權路徑", + "i18n_28f6e7a67b":"靜態檔案", + "i18n_29139c2a1a":"檔名", + "i18n_2926598213":"專案日誌", + "i18n_293cafbbd3":"裁剪", + "i18n_2953a9bb97":"您需要建立一個賬戶用以後續登入管理系統,請牢記超級管理員賬號密碼", + "i18n_295bb704f5":"語言", + "i18n_29b48a76be":"請選擇釋出方式", + "i18n_29efa328e5":"未分發", + "i18n_2a049f4f5b":"分發失敗", + "i18n_2a0bea27c4":"執行域", + "i18n_2a0c4740f1":"檔案", + "i18n_2a1d1da97a":"打包測試環境包 mvn clean package -Dmaven.test.skip=true -Ptest", + "i18n_2a24902516":"叢集ID:", + "i18n_2a38b6c0ae":"未升級成功:", + "i18n_2a3b06a91a":"虛擬MAC", + "i18n_2a3e7f5c38":"手動", + "i18n_2a6a516f9d":"填寫執行命令", + "i18n_2a813bc3eb":"立即下載", + "i18n_2ad3428664":"請選擇釋出到叢集的服務名", + "i18n_2adbfb41e9":"引數如果傳入", + "i18n_2ae22500c7":"禁用時段", + "i18n_2b04210d33":"程序號:", + "i18n_2b0623dab9":"獨立容器", + "i18n_2b0aa77353":"您確定要啟動當前容器嗎?", + "i18n_2b0f199da0":"不執行,也不編譯測試用例 mvn clean package -Dmaven.test.skip=true", + "i18n_2b1015e902":"引數描述沒有實際作用", + "i18n_2b21998b7b":"確定要關閉兩步驗證嗎?關閉後賬號安全性將受到影響,關閉後已經存在的 mfa key 將失效", + "i18n_2b36926bc1":"沒有任何構建歷史", + "i18n_2b4bb321d7":"內容區域主題切換", + "i18n_2b4cf3d74e":"請選擇要使用的構建", + "i18n_2b52fa609c":"發生異常", + "i18n_2b607a562a":"逐行執行", + "i18n_2b6bc0f293":"操作", + "i18n_2b788a077e":"等常用使用者名稱,避免被其他使用者有意或者無意操作造成登入失敗次數過多從而超級管理員賬號被異常鎖定", + "i18n_2b94686a65":"# 給容器新增環境變數", + "i18n_2ba4c81587":"請輸入郵箱地址", + "i18n_2bb1967887":"請找我們授權,否則會有法律風險。", + "i18n_2be2175cd7":"執行容器 標籤", + "i18n_2be75b1044":"全域性", + "i18n_2bef5b58ab":"不填寫則不更新", + "i18n_2c014aeeee":"打包時間", + "i18n_2c5b0e86e6":"使用者密碼重置成功", + "i18n_2c635c80ec":"釋出操作是指,執行完構建命令後將構建產物目錄中的檔案用不同的方式釋出(上傳)到對應的地方", + "i18n_2c74d8485f":"下載完成後需要手動選擇更新到節點才能完成節點更新奧", + "i18n_2c8109fa0b":"當前目錄: ", + "i18n_2c921271d5":"vue 專案(示例參考,具體還需要根據專案實際情況來決定)", + "i18n_2cdbbdabf1":"構建產物目錄,相對倉庫的路徑,如 java 專案的 target/xxx.jar vue 專案的 dist", + "i18n_2cdcfcee15":"功能豐富 專為兩步驗證碼", + "i18n_2ce44aba57":"日誌目錄", + "i18n_2d05c9d012":"關鍵詞,支援正則", + "i18n_2d2238d216":"賬號新增成功", + "i18n_2d3fd578ce":"確定要取批量消選中的構建嗎?注意:取消/停止構建不一定能正常關閉所有關聯程序", + "i18n_2d455ce5cd":"下載中", + "i18n_2d58b0e650":"選擇構建的標籤,不選為最新提交", + "i18n_2d7020be7d":"比如常見的 .env 檔案", + "i18n_2d711b09bd":"內容", + "i18n_2d842318fb":"週期", + "i18n_2d94b9cf0e":"Dockerfile 構建方式不支援在這裡回滾", + "i18n_2d9569bf45":"引數值,新增預設引數後在手動執行指令碼時需要填寫引數值", + "i18n_2d9e932510":"新增目錄", + "i18n_2de0d491d0":"小時", + "i18n_2e0094d663":"真的要刪除該叢集資訊麼?1", + "i18n_2e1f215c5d":"自動建立使用者", + "i18n_2e505d23f7":"下載匯入模板", + "i18n_2e51ca19eb":"如果節點選項是禁用,則表示對應資料有推薦關聯節點(低版本專案資料可能出現此情況)", + "i18n_2e740698cf":"叢集IP", + "i18n_2ea7e70e87":"命令檔案將上傳至 {'${user.home}'}/.jpom/xxxx.sh 執行完成將自動刪除", + "i18n_2ef1c35be8":"執行的 CPU", + "i18n_2f4aaddde3":"刪除", + "i18n_2f5e828ecd":"別名碼", + "i18n_2f5e885bc6":"獲取單個構建日誌地址", + "i18n_2f67a19f9d":"需要選釋出到叢集中的對應的服務名,需要提前去叢集中建立服務", + "i18n_2f6989595f":"管理列表:", + "i18n_2f8d6f1584":"昨天", + "i18n_2f8dc4fb66":"真的要釋放分發資訊麼?釋放之後節點下面的專案資訊還會保留,如需刪除專案還需要到節點管理中操作", + "i18n_2f8fd34058":"指令碼模版是儲存在服務端中的命令指令碼用於線上管理一些指令碼命令,如初始化軟體環境、管理應用程式等", + "i18n_2f97ed65db":"佔用", + "i18n_2fc0d53656":"機器狀態(快取)", + "i18n_2ff65378a4":"真的要刪除對應工作空間的 SSH 麼?", + "i18n_2fff079bc7":"釋出成功", + "i18n_3006a3da65":"系統版本:", + "i18n_300fbf3891":"釋出前停止是指在釋出檔案到專案檔案時先將專案關閉,再進行檔案替換。避免 windows 環境下出現檔案被佔用的情況", + "i18n_302ff00ddb":"超級管理員", + "i18n_3032257aa3":"詳情資訊", + "i18n_30849b2e10":"程序/埠", + "i18n_30aaa13963":"序列號 (SN)", + "i18n_30acd20d6e":"使用者ID", + "i18n_30d9d4f5c9":"新增關聯", + "i18n_30e6f71a18":"自定義標籤通配表示式", + "i18n_30e855a053":"取消分發", + "i18n_30ff009ab3":"# java 映象源 https://mirrors.tuna.tsinghua.edu.cn/Adoptium/", + "i18n_3103effdfd":"請輸入賬號名", + "i18n_31070fd376":"手動回滾", + "i18n_310c809904":"繫結到當前工作空間", + "i18n_312e044529":" :範圍:0(Sunday)~6(Saturday),7也可以表示週日,同時支援不區分大小寫的別名:_##_sun\",\"mon\", \"tue\", \"wed\",\"thu\",\"fri\", \"sat\",", + "i18n_312f45014a":"建立時間:", + "i18n_314f5aca4e":"單個觸發器地址中:第一個隨機字串為構建ID,第二個隨機字串為 token", + "i18n_315eacd193":"上移", + "i18n_31691a647c":"{slot1}埠", + "i18n_3174d1022d":"容器構建注意", + "i18n_3181790b4b":"服務端系統配置", + "i18n_318ce9ea8b":"使用者密碼提示", + "i18n_31aaaaa6ec":"構建ID:", + "i18n_31ac8d3a5d":"執行緒同步器", + "i18n_31bca0fc93":"加入 beta 計劃可以及時獲取到最新的功能、一些優化功能、最快修復 bug 的版本,但是 beta版也可能在部分新功能上存在不穩定的情況。您需要根據您業務情況來評估是否可以加入 beta,在使用 beta版過程中遇到問題可以隨時反饋給我們,我們會盡快為您解答。", + "i18n_31eb055c9c":"並行度,同一時間升級的容器數量", + "i18n_31ecc0e65b":"專案", + "i18n_32112950da":"批量取消", + "i18n_3241c7c05f":"建議使用服務端指令碼分發到指令碼:", + "i18n_32493aeef9":"構建中", + "i18n_329e2e0b2e":"指定目錄打包 yarn && yarn --cwd xxx build", + "i18n_32a19ce88b":"控制檯日誌路徑", + "i18n_32ac152be1":"更新", + "i18n_32c65d8d74":"標題", + "i18n_32cb0ec70e":"請輸入節點名稱", + "i18n_32d0576d85":"的令牌", + "i18n_32dcc6f36e":"重啟策略:no、always、unless-stopped、on-failure", + "i18n_32e05f01f4":"叢集資訊", + "i18n_32f882ae24":"匹配零個或多個字元", + "i18n_330363dfc5":"成功", + "i18n_3306c2a7c7":"讀取預設", + "i18n_33130f5c46":"操作成功", + "i18n_3322338140":"請選擇釋出後操作", + "i18n_332ba869d9":"一般用於節點環境一致的情況", + "i18n_334a1b5206":"安裝節點", + "i18n_335258331a":"已經讀取預設配置檔案到編輯器中", + "i18n_33675a9bb3":"叢集關聯的 docker 資訊丟失,不能繼續使用管理功能", + "i18n_339097ba2e":"準備分發", + "i18n_33c9e2388e":"專案ID", + "i18n_3402926291":"當前日誌檔案大小:", + "i18n_346008472d":"匹配包含 異常 的行", + "i18n_3477228591":"映象", + "i18n_35134b6f94":"檢視節點指令碼", + "i18n_3517aa30c2":"指令碼里面支援的變數有:{'${PROJECT_ID}'}、{'${PROJECT_NAME}'}、{'${PROJECT_PATH}'}", + "i18n_353707f491":"可以到【節點分發】=>【分發授權配置】修改", + "i18n_353c7f29da":"請選擇模版節點", + "i18n_35488f5ba8":"請選擇節點專案", + "i18n_354a3dcdbd":"30秒一次", + "i18n_3574d38d3e":"剩餘記憶體:", + "i18n_35b89dbc59":"確認要下載最新版本嗎?", + "i18n_35cb4b85a9":"【目前只使用匹配到的第一項】", + "i18n_35fbad84cb":"描述根據建立時間升序第一個", + "i18n_3604566503":"請填寫容器地址", + "i18n_364bea440e":"請選擇要引用的指令碼", + "i18n_368ffad051":"{slot1}目錄", + "i18n_36b3f3a2f6":"報警標題", + "i18n_36b5d427e4":"請輸入工作空間描述", + "i18n_36d00eaa3f":"差異構建:", + "i18n_36d4046bd6":"引用指令碼模板", + "i18n_36df970248":"# version 需要在對應映象源中存在", + "i18n_3711cbf638":"預佔資源", + "i18n_37189681ad":"資料Id", + "i18n_373a1efdc0":"請選中要關閉的專案", + "i18n_374cd1f7b7":"建立叢集", + "i18n_375118fad1":"物理節點指令碼模板資料:", + "i18n_375f853ad6":"硬體資訊", + "i18n_3787283bf4":"真的要刪除當前檔案麼?", + "i18n_37b30fc862":"請選擇面板", + "i18n_37c1eb9b23":"配置檔案路徑", + "i18n_37f031338a":"上傳壓縮包並自動解壓", + "i18n_37f1931729":"資料目錄佔用空間:", + "i18n_384f337da1":"同步機制採用", + "i18n_3867e350eb":"環境變數", + "i18n_386edb98a5":"自定義指令碼專案(python、nodejs、go、介面探活、es)【推薦】", + "i18n_38a12e7196":"選擇證書檔案", + "i18n_38aa9dc2a0":"更多配置", + "i18n_38ce27d846":"下一步", + "i18n_38cf16f220":"確定", + "i18n_38da533413":"下面命令將在", + "i18n_3904bfe0db":"設定一個超級管理員賬號", + "i18n_3929e500e0":"通常情況為專案遷移工作空間、遷移物理機器等一些操作可能產生孤獨資料", + "i18n_396b7d3f91":"檔案大小", + "i18n_398ce396cd":"工作空間同步", + "i18n_39b68185f0":"節點地址為外掛端的 IP:PORT 外掛端埠預設為:2123", + "i18n_39c7644388":"埠號", + "i18n_39e4138e30":"叢集建立時間:", + "i18n_39f1374d36":"耗時", + "i18n_3a1052ccfc":"引用環境變數", + "i18n_3a17b7352e":"分鐘", + "i18n_3a3778f20c":"任務ID", + "i18n_3a3c5e739b":"批量構建引數", + "i18n_3a3ff2c936":"卷標籤", + "i18n_3a536dcd7c":"126郵箱", + "i18n_3a57a51660":"指令碼版本:{item}", + "i18n_3a6000f345":"正在執行的執行緒同步器", + "i18n_3a6970ac26":"檔案共享", + "i18n_3a6bc88ce0":"真的要刪除檔案麼?", + "i18n_3a6c2962e1":"金鑰演算法", + "i18n_3a71e860a7":"未開啟當前終端", + "i18n_3a94281b91":"自由指令碼是指直接在機器節點中執行任意指令碼", + "i18n_3aa69a563b":"節點分發是指,一個專案執行需要在多個節點(伺服器)中執行,使用節點分發來統一管理這個專案(可以實現分散式專案管理功能)", + "i18n_3ac34faf6d":"萬用字元", + "i18n_3adb55fbb5":"遷移工作空間", + "i18n_3ae4c953fe":"當定時任務執行到的時間匹配這些表示式後,任務被啟動。", + "i18n_3ae4ddf245":"真的要刪除該 Docker 麼?刪除只會檢查本地系統的資料關聯,不會刪除 docker 容器中資料", + "i18n_3aed2c11e9":"自動", + "i18n_3b14c524f6":"讀取次數", + "i18n_3b19b2a75c":"真的要刪除指令碼麼?", + "i18n_3b885fca15":"快取版本號", + "i18n_3b9418269c":"請填寫關聯容器標籤", + "i18n_3b94c70734":"專案狀態", + "i18n_3ba621d736":"處理成功", + "i18n_3baa9f3d72":"批量構建引數還支援指定引數,delay(延遲執行構建,單位秒)branchName(分支名)、branchTagName(標籤)、script(構建指令碼)、resultDirFile(構建產物)、webhook(通知webhook)", + "i18n_3bc5e602b2":"郵箱", + "i18n_3bcc1c7a20":"最後修改人", + "i18n_3bdab2c607":"10分鐘", + "i18n_3bdd08adab":"描述", + "i18n_3bf3c0a8d6":"節點", + "i18n_3bf9c5b8af":" 分組名:", + "i18n_3c014532b1":"構建耗時:", + "i18n_3c070ea334":"如果關聯的構建關聯的倉庫被多個構建繫結(使用)不能遷移", + "i18n_3c48d9b970":"批量構建引數 BODY json: [ { \"id\":\"1\", \"token\":\"a\" } ]", + "i18n_3c586b2cc0":"自定義 host", + "i18n_3c6248b364":"快取資訊", + "i18n_3c6fa6f667":"cron表示式", + "i18n_3c8eada338":"請選擇編碼方式", + "i18n_3c91490844":"釋出操作", + "i18n_3c99ea4ec2":"例如 2,3,6/3中,由於“/”優先順序高,因此相當於2,3,(6/3),結果與 2,3,6等價", + "i18n_3c9eeee356":"真的要刪除日誌檔案麼?", + "i18n_3cc09369ad":"真的要刪除【", + "i18n_3d06693eb5":"資源:", + "i18n_3d0a2df9ec":"引數", + "i18n_3d3b918f49":"執行構建", + "i18n_3d3d3ed34c":"請輸入選擇關聯分組", + "i18n_3d43ff1199":"置頂", + "i18n_3d48c9da09":"授權配置", + "i18n_3d61e4aaf1":"指定標籤", + "i18n_3d6acaa5ca":"這個容器沒有網路", + "i18n_3d83a07747":"主機 Host", + "i18n_3dc5185d81":"私有", + "i18n_3dd6c10ffd":"上傳升級包", + "i18n_3e445d03aa":"檔案不存在啦", + "i18n_3e51d1bc9c":"請選擇釋出的SSH", + "i18n_3e54c81ca2":"接收流量", + "i18n_3e7ef69c98":"監控操作", + "i18n_3e8c9c54ee":"選擇分組", + "i18n_3ea6c5e8ec":"分發結束", + "i18n_3eab0eb8a9":"本地指令碼", + "i18n_3ed3733078":"終端日誌", + "i18n_3edddd85ac":"日", + "i18n_3ee7756087":"請先選擇節點", + "i18n_3f016aa454":"映象標籤:", + "i18n_3f18d14961":"兩步驗證碼", + "i18n_3f1d478da4":"服務端指令碼、SSH指令碼可以使用 G{'@'}(\"xxxx\") 格式來引用,當存在引用時系統會自動替換引用指令碼庫中的指令碼內容", + "i18n_3f2d5bd6cc":"在檔案第 2 - 2 行中搜尋", + "i18n_3f414ade96":"引數描述,{slot1},僅是用於提示引數的含義", + "i18n_3f553922ae":"】目錄和檔案麼?", + "i18n_3f5af13b4b":"# scriptId 可以是專案路徑下指令碼檔名或者系統中的指令碼模版ID", + "i18n_3f719b3e32":"衝突數", + "i18n_3f78f88499":"打包時間:", + "i18n_3f8b64991f":"解壓時候自動剔除壓縮包裡面多餘的資料夾名", + "i18n_3f8cedd1d7":"用於靜態檔案繫結和讀取(不建議配置大目錄,避免掃描消耗過多資源)", + "i18n_3fb2e5ec7b":"登入日誌", + "i18n_3fb63afb4e":"退出碼", + "i18n_3fbdde139c":"確認密碼", + "i18n_3fca26a684":"批量觸發引數 BODY json: [ { \"id\":\"1\", \"token\":\"a\" } ]", + "i18n_3fea7ca76c":"狀態", + "i18n_402d19e50f":"登入", + "i18n_40349f5514":"數:", + "i18n_4055a1ee9c":"通用的欄位有:createTimeMillis、modifyTimeMillis", + "i18n_406a2b3538":"何為孤獨資料", + "i18n_4089cfb557":"關聯分組主要用於資產監控來實現不同服務端執行不同分組下面的資產監控", + "i18n_40aff14380":"映象ID", + "i18n_40da3fb58b":"新建狀態", + "i18n_40f8c95345":"臨時檔案目錄", + "i18n_411672c954":"請輸入檔案描述", + "i18n_412504968d":"當目標工作空間不存在對應的 SSH 時候將自動建立一個新的 SSH", + "i18n_41298f56a3":"構建失敗", + "i18n_413d8ba722":"舊版程式包占有空間:", + "i18n_413f20d47f":"系統 採用 oshi 庫來監控系統,在 oshi 中使用 /proc/meminfo 來獲取記憶體使用情況。", + "i18n_41638b0a48":"用於區別檔案是否為同一型別,可以針對同型別進行下載管理", + "i18n_417fa2c2be":"引數{index}描述", + "i18n_4188f4101c":"沒有docker", + "i18n_41d0ecbabd":"Block IO 權重", + "i18n_41e8e8b993":"深色", + "i18n_41e9f0c9c6":"工作節點", + "i18n_41fdb0c862":"請先上傳或者下載新版本", + "i18n_4244830033":"請選擇證書檔案", + "i18n_424a2ad8f7":"準備", + "i18n_429b8dfb98":"專案分發", + "i18n_42a93314b4":"基礎映象", + "i18n_42b6bd1b2f":"倉庫路徑", + "i18n_42f766b273":"掛載分割槽", + "i18n_42fd64c157":"先啟動", + "i18n_4310e9ed7d":"請選擇專案執行方式", + "i18n_43250dc692":"觸發器管理", + "i18n_434d888f6f":"請選擇檔案中心的檔案", + "i18n_434d9bd852":"建立使用者後自動關聯上對應的許可權組", + "i18n_4360e5056b":"載入資料中", + "i18n_436367b066":"專案管理", + "i18n_4371e2b426":"請輸入專案名稱", + "i18n_43886d7ac3":"新增執行引數", + "i18n_4393b5e25b":"環回", + "i18n_43c61e76e7":"注意:目前對 SSH key 訪問 git 倉庫地址不支援使用 ssh-keygen -t rsa -C", + "i18n_43d229617a":"待選擇", + "i18n_43e534acf9":"寬鬆", + "i18n_43ebf364ed":"請選擇備份型別", + "i18n_4403fca0c0":"清除", + "i18n_44473c1406":"開啟快取構建目錄將保留倉庫檔案,二次構建將 pull 程式碼, 不開啟快取目錄每次構建都將重新拉取倉庫程式碼(較大的專案不建議關閉快取) 、特別說明如果快取目錄中缺失版本控制相關檔案將自動刪除後重新拉取程式碼", + "i18n_4482773688":"請輸入許可權組名稱", + "i18n_44876fc0e7":"如果不可以選擇則表示對應的使用者沒有配置郵箱", + "i18n_449fa9722b":"為了考慮系統安全我們強烈建議超級管理員開啟兩步驗證來確保賬號的安全性", + "i18n_44a6891817":"新增構建", + "i18n_44c4aaa1d9":"執行模式", + "i18n_44d13f7017":"限定時間", + "i18n_44ed625b19":"網路異常", + "i18n_44ef546ded":"專案監控 【暫不支援遷移】", + "i18n_44efd179aa":"退出登入", + "i18n_45028ad61d":"證書密碼", + "i18n_4524ed750d":"工作空間名", + "i18n_456d29ef8b":"日誌", + "i18n_458331a965":"確認要上傳檔案更新到最新版本嗎?", + "i18n_45a4922d3f":"關聯資料", + "i18n_45b88fc569":"匹配路徑中的零個或多個目錄", + "i18n_45f8d5a21d":"真的要刪除使用者麼?", + "i18n_45fbb7e96a":"專案孤獨資料", + "i18n_46032a715e":"還沒有選擇構建方式", + "i18n_4604d50234":"錯誤資訊", + "i18n_46097a1225":"修正孤獨資料", + "i18n_46158d0d6e":"禁用監控", + "i18n_461e675921":"當前資料為預設狀態,操後上移或者下移可能不會達到預期排序,還需要對相關資料都操作後才能達到預期排序", + "i18n_461ec75a5a":"路徑:", + "i18n_461fdd1576":"打包生產環境包 mvn clean package -Dmaven.test.skip=true -Pprod", + "i18n_4637765b0a":"未啟用", + "i18n_463e2bed82":"批量更新", + "i18n_4642113bba":"點選儀表盤檢視監控歷史資料", + "i18n_4645575b77":"工作空間描述", + "i18n_464f3d4ea3":"角色", + "i18n_465260fe80":"年", + "i18n_4696724ed3":"觸發器", + "i18n_46a04cdc9c":"檔案描述:", + "i18n_46aca09f01":"解綁會檢查資料關聯性,不會真實請求節點刪除專案資訊", + "i18n_46ad87708f":"ssh名稱", + "i18n_46c8ba7b7f":"如果按鈕不可用,請去資產管理 ssh 列表的關聯中新增當前工作空間允許管理的授權資料夾", + "i18n_46e3867956":"執行中", + "i18n_46e4265791":"構建 ID", + "i18n_4705b88497":"作用域", + "i18n_47072e451e":"管理節點:", + "i18n_470e9baf32":"允許執行的記憶體節點", + "i18n_471c6b19cf":"遷移前您檢查遷出機器和遷入機器的連線狀態和網路狀態避免未知錯誤或者中斷造成流程失敗產生冗餘資料!!!!", + "i18n_4722bc0c56":"終端", + "i18n_473badc394":"釋出的節點", + "i18n_4741e596ac":"報警時間", + "i18n_475a349f32":"當前構建還沒有生成觸發器", + "i18n_475cd76aec":"統計的網絡卡:", + "i18n_47768ed092":"極不安全", + "i18n_47bb635a5c":"資料可能出現一定時間延遲", + "i18n_47d68cd0f4":"服務", + "i18n_47dd8dbc7d":"搜尋專案ID", + "i18n_47e4123886":"新增分發", + "i18n_47ff744ef6":"編輯檔案", + "i18n_481ffce5a9":"匹配秒", + "i18n_4826549b41":"命令模版是用於線上管理一些指令碼命令,如初始化軟體環境、管理應用程式等", + "i18n_48281fd3f0":"真的要刪除構建資訊麼?刪除也將同步刪除所有的構建歷史記錄資訊", + "i18n_4838a3bd20":"按住 Ctr 或者 Alt/Option 鍵點選按鈕快速回到第一頁", + "i18n_4871f7722d":"任務更新時間", + "i18n_48735a5187":"剩餘空間(未分配)", + "i18n_48a536d0bb":"修改容器配置,重新執行", + "i18n_48d0a09bdd":"淺色", + "i18n_48e79b3340":"】檔案麼?", + "i18n_48fe457960":"(存在相容問題,實際使用中需要提前測試) go sdk 映象使用:https://studygolang.com/dl/golang/go{'${GO_VERSION}'}.linux-{'${ARCH}'}.tar.gz", + "i18n_4956eb6aaa":"負載", + "i18n_49574eee58":"確定要操作嗎?", + "i18n_49645e398b":"如果配置錯誤需要重啟服務端並新增命令列引數 --rest:ip_config 將恢復預設配置", + "i18n_497bc3532b":"JVM 引數", + "i18n_497ddf508a":"新建空白檔案", + "i18n_498519d1af":"重新整理資料", + "i18n_499f058a0b":"退出登入成功", + "i18n_49a9d6c7e6":"通過以下二維碼進行一次性捐款贊助,請作者喝一杯咖啡☕️", + "i18n_49d569f255":"請輸入要檢查的 host", + "i18n_49e56c7b90":"確認修改", + "i18n_4a00d980d5":"簡單好用", + "i18n_4a0e9142e7":"釘釘", + "i18n_4a346aae15":"外掛版本:", + "i18n_4a4e3b5ae4":"描述:", + "i18n_4a5ab3bc72":"操作:", + "i18n_4a6f3aa451":" :每個點鐘的5分執行,00:05,01:05……", + "i18n_4a98bf0c68":"任務詳情", + "i18n_4aac559105":"權重", + "i18n_4ab578f3df":"環境變數:", + "i18n_4ad6e58ebc":"機器SSH", + "i18n_4af980516d":"為了您的賬號安全系統要求必須開啟兩步驗證來確保賬號的安全性", + "i18n_4b027f3979":"提醒", + "i18n_4b0cb10d18":"請輸入 SMTP host", + "i18n_4b1835640f":"在 Settings-->Developer settings-->Personal access tokens 中獲取", + "i18n_4b386a7209":"獲取變數值地址", + "i18n_4b404646f4":"容器標籤,如:key1=values1&keyvalue2", + "i18n_4b5e6872ea":"駐留集", + "i18n_4b96762a7e":"最後修改時間", + "i18n_4b9c3271dc":"重置", + "i18n_4ba304e77a":"釘釘賬號登入", + "i18n_4bbc09fc55":"在檔案第 3 - 20 行中搜尋", + "i18n_4c096c51a3":"埠號:", + "i18n_4c0eead6ff":"新增引數", + "i18n_4c28044efc":"確認要將選中的 ", + "i18n_4c69102fe1":"再判斷允許時段。配置允許時段後使用者只能在對應的時段執行相應功能的操作", + "i18n_4c7c58b208":"請選擇節點狀態", + "i18n_4c7e4dfd33":"當目標工作空間不存在對應的節點時候將自動建立一個新的docker(邏輯docker)", + "i18n_4c83203419":"跳轉到第三方系統中", + "i18n_4c9bb42608":"字首", + "i18n_4cbc136874":"資料夾:", + "i18n_4cbc5505c7":"差異構建是指構建時候是否判斷倉庫程式碼有變動,如果沒有變動則不執行構建", + "i18n_4ccbdc5301":"選單", + "i18n_4cd49caae4":"分發耗時", + "i18n_4ce606413e":"倉庫型別", + "i18n_4cfca88db8":"選擇分發檔案", + "i18n_4d18dcbd15":"真的要還原備份資訊麼?", + "i18n_4d351f3c91":"禁止 IP", + "i18n_4d49b2a15f":"自動執行:docker", + "i18n_4d775d4cd7":"顯示", + "i18n_4d7dc6c5f8":"寫", + "i18n_4d85ac1250":"系統管理", + "i18n_4d85c37f0d":"工作空間:", + "i18n_4d9c3a0ed0":"Script 內容", + "i18n_4dc781596b":"中使用瞭如下開源軟體,我們衷心感謝有了他們的開源 Jpom 才能更完善", + "i18n_4df483b9c7":"專案檔案 ", + "i18n_4e33dde280":"當前目錄:", + "i18n_4e54369108":"檔案型別沒有觸發器功能", + "i18n_4e7e04b15d":"服務名稱必填", + "i18n_4ed1662cae":"請選擇連線方式", + "i18n_4ee2a8951d":"介面響應 ContentType 均為:text/plain", + "i18n_4ef719810b":"沒有任何執行中的任務", + "i18n_4effdeb1ff":"在檔案第 1 - 2 行中搜尋", + "i18n_4f08d1ad9f":"演算法 OID", + "i18n_4f095befc0":"此配置僅對服務端管理生效, 工作空間的 ssh 配置需要單獨配置", + "i18n_4f35e80da6":"路徑", + "i18n_4f4c28a1fb":"檔案內容格式要求:env_name=xxxxx 不滿足格式的行將自動忽略", + "i18n_4f50cd2a5e":"緊湊模式", + "i18n_4f52df6e44":"關閉中", + "i18n_4f8a2f0b28":"未執行", + "i18n_4f8ca95e7b":"名", + "i18n_4f9e3db4b8":"選擇構建", + "i18n_4fb2400af7":"容器是執行中可以進入終端", + "i18n_4fb95949e5":"開啟中", + "i18n_4fdd2213b5":"專案 ID", + "i18n_500789168c":"清空還原將會先刪除專案目錄中的檔案再將對應備份檔案恢復至當前目錄", + "i18n_5011e53403":"釋出叢集", + "i18n_503660aa89":"排除:", + "i18n_50411665d7":"保留個數", + "i18n_50453eeb9e":"當前工作空間還沒有邏輯節點不能建立節點指令碼奧", + "i18n_504c43b70a":"埠/PID", + "i18n_5068552b18":"歷史監控圖表", + "i18n_50940ed76f":"下載成功", + "i18n_50951f5e74":"請選擇分支", + "i18n_50a299c847":"構建名稱", + "i18n_50c7929dd9":" 歡迎 ", + "i18n_50d2671541":"確定是同一個指令碼", + "i18n_50ed14e70b":"深色 dracula", + "i18n_50f472ee4e":"單位秒,預設 10 秒,最小 3 秒", + "i18n_50f975c08e":"構建產物保留天數,小於等於 0 為跟隨全域性保留配置。注意自動清理僅會清理記錄狀態為:(構建結束、釋出中、釋出失敗、釋出失敗)的資料避免一些異常構建影響保留個數", + "i18n_50fb61ef9d":"指令碼名稱", + "i18n_50fe3400c7":"真的要刪除該執行記錄嗎?", + "i18n_50fefde769":"是否為壓縮包", + "i18n_512e1a7722":"請選擇操作者", + "i18n_51341b5024":"服務端分發的指令碼", + "i18n_514b320d25":"如何選擇構建方式", + "i18n_5169b9af9d":"資訊丟失", + "i18n_5177c276a0":"叢集不能手動建立,建立需要多個服務端使用通一個資料庫,並且配置不同的叢集 id 來自動建立叢集資訊", + "i18n_518df98392":"從尾搜尋", + "i18n_5195c0d198":"可以管理{count}個工作空間", + "i18n_51c92e6956":"同步系統配置", + "i18n_51d47ddc69":"回撥 url", + "i18n_51d6b830d4":"線上構建目錄", + "i18n_52409da520":"聯絡人", + "i18n_527466ff94":"請求引數", + "i18n_527f7e18f1":"上傳前請閱讀更新日誌裡面的說明和注意事項並且更新前", + "i18n_52a8df6678":"】資料夾麼?", + "i18n_52b526ab9e":"清空瀏覽器快取配置將恢復預設", + "i18n_52b6b488e2":"指令碼模版是儲存在節點(外掛端),執行也都將在節點裡面執行,服務端會定時去拉取執行日誌,拉取頻率為 100 條/分鐘", + "i18n_52c6af8174":"請輸入客戶端金鑰 [clientSecret]", + "i18n_52d24791ab":"真的要刪除這些檔案麼?", + "i18n_52eedb4a12":"報警方式", + "i18n_52ef46c618":"不釋出:只執行構建流程並且儲存構建歷史", + "i18n_532495b65b":"副本數", + "i18n_53365c29c8":"下載狀態:", + "i18n_534115e981":"資訊不完整不能編輯", + "i18n_5349f417e9":"搜關鍵詞", + "i18n_536206b587":"當前機器還未監控到任何資料", + "i18n_537b39a8b5":"必填", + "i18n_53bdd93fd6":"檢視指令碼庫", + "i18n_541e8ce00c":"關於開源軟體", + "i18n_542a0e7db4":"同步授權", + "i18n_543296e005":"請輸入授權 url [authorizationUri]", + "i18n_543a5aebc8":"真的刪除當前變數嗎?", + "i18n_543de6ff04":"分發狀態訊息", + "i18n_54506fe138":"重置選擇", + "i18n_5457c2e99f":"# 使用 copy 檔案的方式快取,反之使用軟鏈的形式。copy 檔案方式快取 node_modules 可以避免 npm WARN reify Removing non-directory", + "i18n_547ee197e5":"新建目錄", + "i18n_5488c40573":"節點專案", + "i18n_54f271cd41":"指令碼模板", + "i18n_5516b3130c":"飛書賬號登入", + "i18n_551e46c0ea":" 名稱: ", + "i18n_55405ea6ff":"匯出", + "i18n_556499017a":"專案檔案會存放到", + "i18n_5569a840c8":"請輸入IP禁止,多個使用換行,支援配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_55721d321c":"引數描述", + "i18n_55939c108f":"輸入檔案或者資料夾名", + "i18n_55abea2d61":"服務端", + "i18n_55b2d0904f":"在執行多節點分發時候使用 順序重啟、完整順序重啟 時候需要保證專案能正常重啟", + "i18n_55cf956586":"加入叢集", + "i18n_55d4a79358":"配置需要宣告使用具體的 docker 來執行構建相關操作(建議使用服務端所在伺服器中的 docker)", + "i18n_55da97b631":" ,範圍0~59,但是第一位不做匹配當為7位時,最後一位表示", + "i18n_55e690333a":"當前工作空間還沒有 Docker 叢集", + "i18n_55e99f5106":"釘釘通知地址", + "i18n_55f01e138a":"微信讚賞", + "i18n_56071a4fa6":"超時時間", + "i18n_56230405ae":"解綁不會真實請求節點刪除指令碼資訊", + "i18n_562d7476ab":"週日", + "i18n_56469e09f7":"請到【系統管理】-> 【資產管理】-> 【Docker管理】新增Docker,或者將已新增的Docker授權關聯、分配到此工作空間", + "i18n_56525d62ac":"掃描", + "i18n_566c67e764":"已經分配到工作空間的機器無非直接刪除,需要到分配到的各個工作空間逐一刪除後才能刪除資產機器", + "i18n_5684fd7d3d":"賬號新密碼為:", + "i18n_56bb769354":"下載前請閱讀更新日誌裡面的說明和注意事項並且更新前", + "i18n_56d9d84bff":"工作空間中邏輯節點中的專案數量:", + "i18n_570eb1c04f":"硬碟佔用率:", + "i18n_5734b2db4e":"讀取行數", + "i18n_576669e450":"請選中要啟動的專案", + "i18n_5785f004ea":"請勿手動刪除資料目錄下面檔案,如果需要刪除需要提前備份或者已經確定對應檔案棄用後才能刪除", + "i18n_578adf7a12":"請仔細確認後配置,ip配置後立即生效。配置時需要保證當前ip能訪問!127.0.0.1 該IP不受訪問限制.支援配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_578ca5bcfd":"163郵箱", + "i18n_57978c11d1":"日誌彈窗會非全屏開啟", + "i18n_579a6d0d92":"命令值", + "i18n_57b7990b45":"當目標工作空間已經存在 SSH 時候將自動同步 SSH 賬號、密碼、私鑰等資訊", + "i18n_57c0a41ec6":"當前資料為預設狀態", + "i18n_57cadc4cf3":"會使用 PING 檢查", + "i18n_5805998e42":"重啟策略", + "i18n_5854370b86":"跟蹤檔案", + "i18n_585ae8592f":"重建容器", + "i18n_5866b4bced":"叢集數:", + "i18n_587a63264b":"覆蓋還原", + "i18n_588e33b660":"賬號如果開啟 MFA(兩步驗證),使用 Oauth2 登入不會驗證 MFA(兩步驗證)", + "i18n_589060f38e":"升級中,請稍候...", + "i18n_5893fa2280":"郵箱賬號", + "i18n_58cbd04f02":"SSH 是指,通過 SSH 命令的方式對產物進行釋出或者執行多條命令來實現釋出(需要到 SSH 中提前去新增)", + "i18n_58e998a751":"刪除會檢查資料關聯性,並且節點不存在專案或者指令碼", + "i18n_58f9666705":"大小", + "i18n_590b9ce766":"目前支援都外掛有(更多外掛盡情期待):", + "i18n_590dbb68cf":"結束時間:", + "i18n_590e5b46a0":"自動備份", + "i18n_592c595891":"開始時間", + "i18n_5936ed11ab":"指令碼庫用於儲存管理通用的指令碼,指令碼庫中的指令碼不能直接執行。", + "i18n_593e04dfad":"選單主題", + "i18n_597b1a5130":"更新狀態", + "i18n_59a15a0848":"同步機制採用 IP+PORT+連線方式 確定是同一個伺服器", + "i18n_59c316e560":"分發檔案", + "i18n_59c75681b4":"通知物件", + "i18n_59d20801e9":"在檔案第 17 - 20 行中搜尋", + "i18n_5a0346c4b1":"編輯使用者", + "i18n_5a1367058c":"返回首頁", + "i18n_5a1419b7a2":"資料名稱", + "i18n_5a42ea648d":"自建 gitlab 訪問地址", + "i18n_5a5368cf9b":"密碼錯誤", + "i18n_5a63277941":"的值有:stop、beforeStop、start、beforeRestart、fileChange", + "i18n_5a7ea53d18":"docker資訊", + "i18n_5a8727305e":"請不要優先退出管理節點", + "i18n_5a879a657b":"交換記憶體", + "i18n_5aabec5c62":"父級ID", + "i18n_5ab90c17a3":"任務結束", + "i18n_5ad7f5a8b2":"結果", + "i18n_5afe5e7ed4":"編輯關聯專案", + "i18n_5b1f0fd370":"用於建立節點分發專案、檔案中心釋出檔案", + "i18n_5b3ffc2910":"分發中", + "i18n_5b47861521":"名稱:", + "i18n_5baaef6996":"點選重新同步當前工作空間邏輯節點指令碼模版資訊", + "i18n_5badae1d90":"沒有任何指令碼", + "i18n_5bb162ecbb":"JVM剩餘記憶體", + "i18n_5bb5b33ae4":"所以這裡 我們", + "i18n_5bca8cf7ee":"自定義host, xxx:192.168.0.x", + "i18n_5bcda1b4d7":"會話已經關閉[system-log]", + "i18n_5bd1d267a9":"在 preferences-->Access Tokens 中獲取", + "i18n_5c3b53e66c":"修改檔案", + "i18n_5c4d3c836f":"需要驗證 MFA", + "i18n_5c502af799":"容器名稱必填", + "i18n_5c56a88945":"停用", + "i18n_5c89a5353d":"分配節點", + "i18n_5c93055d9c":"一般用於伺服器無法連線且已經確定不再使用", + "i18n_5ca6c1b6c7":"請填寫叢集名稱", + "i18n_5cb39287a8":"監控功能", + "i18n_5cc7e8e30a":"修改檔案許可權", + "i18n_5d07edd921":"請填寫叢集IP", + "i18n_5d14e91b01":"主要ID", + "i18n_5d368ab0a5":"執行命令將自動替換為 sh 命令檔案、並自動載入環境變數:/etc/profile、/etc/bashrc、~/.bashrc、~/.bash_profile", + "i18n_5d414afd86":"從尾搜尋、檔案前2行、檔案後3行", + "i18n_5d459d550a":"處理中", + "i18n_5d488af335":"遠端下載檔案", + "i18n_5d5fd4170f":"的值有:1", + "i18n_5d6f47d670":"專案為靜態資料夾", + "i18n_5d803afb8d":"不能和節點正常通訊", + "i18n_5d817c403e":"沒有選擇任何資料", + "i18n_5d83794cfa":"節點名稱:", + "i18n_5d9c139f38":"內容主題", + "i18n_5dc09dd5bd":"重連 ", + "i18n_5dc1f36a27":"證書描述", + "i18n_5dc78cb700":"構建產物保留個數,小於等於 0 為跟隨全域性保留配置(如果數值大於 0 將和全域性配置對比最小值來參考)。注意自動清理僅會清理記錄狀態為:(構建結束、釋出中、釋出失敗、釋出失敗)的資料避免一些異常構建影響保留個數。 將在建立新的構建記錄時候檢查保留個數", + "i18n_5dc7b04caa":"檢視的程序數量", + "i18n_5dff0d31d0":"如果需要定時自動執行則填寫,cron 表示式.預設未開啟秒級別,需要去修改配置檔案中:[system.timerMatchSecond])", + "i18n_5e32f72bbf":"重新整理檔案表格", + "i18n_5e46f842d8":"監控使用者", + "i18n_5e9f2dedca":"是否成功", + "i18n_5ecc709db7":"執行時候預設不載入全部環境變數、需要指令碼里面自行載入", + "i18n_5ed197a129":"重置初始化在啟動時候傳入引數", + "i18n_5ef040a79d":"丟棄包", + "i18n_5ef72bdfce":"命令內容支援工作空間環境變數", + "i18n_5effe31353":"剔除資料夾", + "i18n_5f4c724e61":"請輸入任務名", + "i18n_5f5cd1bb1e":"新增關聯專案是指,將已經在節點中建立好的專案關聯為節點分發專案來實現統一管理", + "i18n_5fafcadc2d":"會話已經關閉[node-script-consloe]", + "i18n_5fbde027e3":"可以引用工作空間的環境變數 變數佔位符 {'${xxxx}'} xxxx 為變數名稱", + "i18n_5fc6c33832":" 跳至行", + "i18n_5fea80e369":"沒有資產DOCKER", + "i18n_5fffcb255d":"外掛執行", + "i18n_601426f8f2":"推送到倉庫", + "i18n_603dc06c4b":"您訪問的頁面不存在", + "i18n_60585cf697":" 歡迎", + "i18n_607558dbd4":"專案數", + "i18n_607e7a4f37":"檢視", + "i18n_609b5f0a08":"時", + "i18n_60b4c08f5c":"您確定要停止當前容器嗎?", + "i18n_6106de3d87":"JDK版本", + "i18n_61341628ab":" :表示列表", + "i18n_6143a714d0":"編碼格式", + "i18n_616879745d":"凌晨0點和中午12點", + "i18n_61955b0e4b":"沒有專案狀態以及控制等功能", + "i18n_61a3ec6656":"介紹", + "i18n_61bfa4e925":"需要在倉庫裡面 dockerfile,如果多資料夾檢視可以指定二級目錄如果 springboot-test-jar:springboot-test-jar/Dockerfile", + "i18n_61c0f5345d":"SMTP 地址:【smtp.163.com, smtp.126.com...】,密碼是郵箱授權碼,埠預設 25,SSL 埠 465", + "i18n_61e7fa1227":"編輯節點", + "i18n_61e84eb5bb":"開始時間:", + "i18n_620489518c":"引數{index}值", + "i18n_620efec150":"更多開源說明", + "i18n_62170d5b0a":"搜尋參考", + "i18n_6228294517":"選單配置只對非超級管理員生效", + "i18n_622d00a119":"執行指令碼的路徑", + "i18n_624f639f16":"通用郵箱", + "i18n_625aa478e2":"從尾搜尋、檔案前0行、檔案後3行", + "i18n_625fb26b4b":"取消", + "i18n_627c952b5e":"總空間", + "i18n_6292498392":" 查詢下一個", + "i18n_629a6ad325":"安全管理", + "i18n_629f3211ca":"修剪型別", + "i18n_631d5b88ab":"請輸入專案存放路徑授權,回車支援輸入多個路徑,系統會自動過濾 ../ 路徑、不允許輸入根路徑", + "i18n_632a907224":"重置為重新生成觸發地址,重置成功後之前的觸發器地址將失效,觸發器繫結到生成觸發器到操作人上,如果將對應的賬號刪除觸發器將失效", + "i18n_6334eec584":"5秒一次", + "i18n_635391aa5d":"下載產物", + "i18n_637c9a8819":"至少選擇1個節點專案", + "i18n_638cddf480":"建立人,全匹配", + "i18n_639fd37242":"目前使用的 docker swarm 叢集,需要先建立 swarm 叢集才能選擇", + "i18n_63b6b36c71":"選擇證書", + "i18n_63c9d63eeb":"可以同時展開多個選單", + "i18n_63dd96a28a":"密碼支援引用工作空間變數:", + "i18n_63e975aa63":"安裝ID:", + "i18n_640374b7ae":"掛載卷", + "i18n_641796b655":"構建完成", + "i18n_6428be07e9":"配置系統公告", + "i18n_643f39d45f":"非懸空", + "i18n_6446b6c707":"暱稱長度為2-10", + "i18n_646a518953":"請輸入專案ID", + "i18n_6470685fcd":":表示匹配這個位置任意的時間(與_##_*\"作用一致)", + "i18n_649231bdee":"檔案字尾", + "i18n_64933b1012":"儲存選項", + "i18n_6496a5a043":"命令名稱", + "i18n_649d7fcb73":"新叢集需要手動配置叢集管理資產分組、叢集訪問地址", + "i18n_649d90ab3c":"關閉右側", + "i18n_649f8046f3":"請選擇SSH節點", + "i18n_64c083c0a9":"結果描述", + "i18n_64eee9aafa":"開機時間", + "i18n_652273694e":"主機", + "i18n_65571516e2":"構建備註:", + "i18n_657969aa0f":"編輯 Docker", + "i18n_657f3883e3":"不執行釋出流程", + "i18n_65894da683":"釋出方式:", + "i18n_65cf4248a8":"不能初始化", + "i18n_65f66dfe97":"清空當前緩衝區內容", + "i18n_66238e0917":"已經存在的賬號與外部系統賬號不一致時不支援繫結外部系統賬號", + "i18n_663393986e":"解綁", + "i18n_6636793319":"真的要刪除節點麼?刪除會檢查資料關聯性,並且節點不存在專案或者指令碼", + "i18n_664c205cc3":"真的要清除倉庫隱藏欄位資訊麼?(密碼,私鑰)", + "i18n_667fa07b52":"個節點升級到", + "i18n_66aafbdb72":"最新構建ID", + "i18n_66ab5e9f24":"新增", + "i18n_66b71b06c6":"上傳壓縮檔案(自動解壓)", + "i18n_66c15f2815":"匹配包含數字的行", + "i18n_66e9ea5488":"日誌名稱", + "i18n_6707667676":"主機名", + "i18n_6709f4548f":"隨機生成", + "i18n_67141abed6":"專案授權路徑+專案資料夾", + "i18n_67425c29a5":"超時時間(s)", + "i18n_674a284936":"當isMatchSecond為 true 時才會匹配秒部分預設都是關閉的", + "i18n_674e7808b5":"mfa 驗證碼", + "i18n_679de60f71":"請填寫日誌專案名稱", + "i18n_67aa2d01b9":"工作空間的選單、環境變數、節點分發授權需要逐一配置", + "i18n_67b667bf98":"部分備份", + "i18n_67e3d3e09c":"批量構建", + "i18n_67e7f9e541":"監控週期", + "i18n_6816da19f3":"關閉其他", + "i18n_6835ed12b9":"環境變數的key", + "i18n_685e5de706":"容器構建", + "i18n_6863e2a7b5":"指令碼執行歷史", + "i18n_686a19db6a":"自動刪除", + "i18n_68a1faf6e2":"批量構建傳入其他引數將同步執行修改", + "i18n_68af00bedb":"表格檢視才能使用工作空間同步功能", + "i18n_68c55772ca":"請輸入授權方的網頁應用ID", + "i18n_69056f4792":"部分操作狀態碼可能為 0", + "i18n_690a3d1a69":"執行容器", + "i18n_691b11e443":"當前工作空間", + "i18n_6928f50eb3":"支援配置系統引數:", + "i18n_69384c9d71":"點選檢視歷史趨勢", + "i18n_693a06987c":"請填寫使用者賬號", + "i18n_6948363f65":"取消定時,不再定時執行(支援 ! 字首禁用定時執行,如:!0 0/1 * * * ?)", + "i18n_694fc5efa9":"重新整理", + "i18n_695344279b":"檔案上傳id生成失敗:", + "i18n_6953a488e3":"選擇邏輯節點", + "i18n_697d60299e":"檢測到當前已經登入賬號", + "i18n_69c3b873c1":"本地構建", + "i18n_69c743de70":"節點的IP", + "i18n_69de8d7f40":"還原", + "i18n_6a359e2ab3":"scriptId也可以引入指令碼庫中的指令碼,需要提前同步至機器節點中", + "i18n_6a49f994b1":"構建過程執行對應的指令碼,開始構建,構建完成,開始釋出,釋出完成,構建異常,釋出異常", + "i18n_6a4a0f2b3b":"同步機制採用節點地址確定是同一個伺服器(節點)", + "i18n_6a588459d0":"工作空間名稱", + "i18n_6a620e3c07":"同步", + "i18n_6a658517f3":"任務日誌", + "i18n_6a66d4cdf3":"延遲,容器回滾間隔時間", + "i18n_6a6c857285":"分發節點", + "i18n_6a8402afcb":"解析檔案,準備上傳中 ", + "i18n_6a8c30bd06":"載入編輯器中", + "i18n_6a922e0fb6":"外掛端埠", + "i18n_6a9231c3ba":"函式 args 引數,非必填", + "i18n_6aa7403b18":"如果使用 SSH 方式但是 SSH 無法選擇,是表示系統沒有監測到 docker 服務", + "i18n_6aab88d6a3":"儲存並重啟", + "i18n_6ab78fa2c4":"郵箱地址", + "i18n_6ac61b0e74":"建議還原和當前版本一致的檔案或者臨近版本的檔案", + "i18n_6ad02e7a1b":"頁面資源載入中....", + "i18n_6adcbc6663":"配置方式:SSH列表->操作欄中->關聯按鈕->對應工作空間->操作欄中->配置按鈕", + "i18n_6af7686e31":"分鐘重新整理一次", + "i18n_6b0bc6432d":"操作者", + "i18n_6b189bf02d":"容器數:", + "i18n_6b29a6e523":"啟動專案", + "i18n_6b2e348a2b":"定時執行", + "i18n_6b46e2bfae":"真的當前工作空間麼", + "i18n_6b4fd0ca47":"支援配置傳送方:遵循RFC-822標準 發件人可以是以下形式:", + "i18n_6b6d6937d7":"163郵箱 SSL", + "i18n_6bb5ba7438":"限制禁止在線上終端執行的命令", + "i18n_6be30eaad7":"請輸入超時時間", + "i18n_6bf1f392c0":"當前狀態", + "i18n_6c08692a3a":"密碼若沒修改可以不用填寫", + "i18n_6c14188ba0":"不能下載目錄", + "i18n_6c24533675":"請選擇一位報警聯絡人或者填寫webhook", + "i18n_6c72e9d9de":"編輯分發專案", + "i18n_6c776e9d91":"專案啟動,停止,重啟,檔案變動都將請求對應的地址,非必填,GET請求", + "i18n_6d5f0fb74b":"映象構建成功後是否需要推送到遠端倉庫", + "i18n_6d68bd5458":"全量備份", + "i18n_6d7f0f06be":"請選擇釋出操作", + "i18n_6d802636ab":"隱私", + "i18n_6da242ea50":"任務Id", + "i18n_6dcf6175d8":"現在就去", + "i18n_6de1ecc549":"檢視服務端指令碼", + "i18n_6e02ee7aad":"週期的長度,以微秒為單位。", + "i18n_6e2d78a20e":"從尾搜尋、檔案前20行、檔案後3行", + "i18n_6e60d2fc75":"頁面啟用緊湊模式", + "i18n_6e69656ffb":"檔案不能為空", + "i18n_6e70d2fb91":"構建引數,如:key1=value1&key2=value2", + "i18n_6ea1fe6baa":"基礎資訊", + "i18n_6eb39e706c":"編輯機器", + "i18n_6ef90ec712":"請填寫要拉取的映象名稱", + "i18n_6f15f0beea":"兩次密碼不一致...", + "i18n_6f32b1077d":"請輸入工作空間備註,留空使用預設的名稱", + "i18n_6f5b238dd2":" SSH、本地命令釋出都執行變數替換,系統預留變數有:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_6f6ee88ec4":"支援開源", + "i18n_6f73c7cf47":"保留天數:", + "i18n_6f7ee71e77":"靜態目錄", + "i18n_6f854129e9":"分組/標籤", + "i18n_6f8907351b":"同步節點配置", + "i18n_6f8da7dcca":"節點地址格式為:IP:PORT (示例:192.168.1.100:2123)", + "i18n_6f9193ac80":"啟用兩步驗證", + "i18n_6fa1229ea9":"一鍵分發同步多個節點的授權配置", + "i18n_6ffa21d235":"分發節點是指將變數同步到對應節點,在節點指令碼中也可以使用當前變數", + "i18n_7006410585":"無論返回什麼退出程式碼,始終重新啟動容器。", + "i18n_7010264d22":"沒有開啟任何認證", + "i18n_702430b89d":"頁面啟用寬鬆模式", + "i18n_702afc34a0":"差異釋出:", + "i18n_7030ff6470":"錯誤", + "i18n_7035c62fb0":"賬號", + "i18n_704f33fc74":"從頭搜尋、檔案前0行、檔案後3行", + "i18n_706333387b":"此功能不能保證新增的容器和之前容器引數完全一致請慎重使用。", + "i18n_7088e18ac9":"卷", + "i18n_708c9d6d2a":"請選擇", + "i18n_70a6bc1e94":"當前系統已經初始化過啦,不能重複初始化", + "i18n_70b3635aa3":"執行時間", + "i18n_70b5b45591":"快速安裝", + "i18n_70b9a2c450":"真的要退出系統麼?", + "i18n_710ad08b11":"禁用", + "i18n_7114d41b1d":"超級管理員沒有任何限制", + "i18n_712cdd7984":"合作諮詢", + "i18n_713c986135":"容器構建會在 docker 中生成相關掛載目錄,一般情況不需要人為操作", + "i18n_7156088c6e":"編碼方式", + "i18n_71584de972":"非伺服器開機自啟,如需開機自啟建議配置", + "i18n_715ec3b393":"用於快捷同步其他機器節點的配置", + "i18n_7173f80900":"拒絕", + "i18n_71a2c432b0":"編輯變數", + "i18n_71bbc726ac":"跟隨系統", + "i18n_71c6871780":"定時任務表示式", + "i18n_71dc8feb59":"未配置", + "i18n_71ee088528":"橋接模式:", + "i18n_7220e4d5f9":"方式", + "i18n_7229ecc631":"次", + "i18n_7293bbb0ff":"總 inode 數", + "i18n_729eebb5ff":"沒有對應的SSH", + "i18n_72d14a3890":"請選擇使用者的許可權組", + "i18n_72d46ec2cf":"登入資訊過期", + "i18n_72d4ade571":",僅是用於提示引數的含義", + "i18n_72e7a5d105":"映象id", + "i18n_72eae3107e":"灰綠 abbott", + "i18n_72ebfe28b0":"定時", + "i18n_7307ca1021":"自動啟動", + "i18n_7327966572":"徹底刪除", + "i18n_7329a2637c":"叢集ID", + "i18n_73485331c2":"檔案資訊", + "i18n_73578c680e":"資料目錄是指程式在執行過程中產生的檔案以及資料儲存目錄", + "i18n_73651ba2db":"批量重啟", + "i18n_7370bdf0d2":"指令碼日誌", + "i18n_738a41f965":"專案名稱", + "i18n_73a87230e0":"檔案系統型別", + "i18n_73b7b05e6e":" 將指令碼分發到對應的機器節點中,對應的機器節點可以引用對應的指令碼 ", + "i18n_73b7e8e09e":"在使用 beta 版過程中遇到問題可以隨時反饋給我們,我們會盡快為您解答。", + "i18n_73c980987a":"載入容器可用標籤中....", + "i18n_73d8160821":"以 yaml/yml 格式配置,scriptId 為專案路徑下的指令碼檔案的相對路徑或者指令碼模版ID,可以到指令碼模版編輯彈窗中檢視 scriptId", + "i18n_73ed447971":"強烈建議您使用 TLS 證書", + "i18n_73f798a129":"免費社群", + "i18n_7457228a61":"遠端下載地址", + "i18n_74bdccbb5d":"我的工作空間", + "i18n_74c5c188ae":"操作成功介面 HTTP 狀態碼為 200", + "i18n_74d5f61b9f":"構建觸發", + "i18n_74d980d4f4":"在單頁列表裡面 file 型別專案將自動排序到最後", + "i18n_74dc77d4f7":"容器id", + "i18n_74dd7594fc":"發生報警時候請求", + "i18n_74ea72bbd6":"叢集管理", + "i18n_751a79afde":"30分鐘", + "i18n_7527da8954":"普通使用者", + "i18n_7548ea6316":"點選可以摺疊左側選單欄", + "i18n_75528c19c7":"自動重啟", + "i18n_7561bc005e":"構建過程請求,非必填,GET請求", + "i18n_75769d1ac8":"讀", + "i18n_757a730c9e":"無法連線", + "i18n_758edf4666":"從頭搜尋、檔案前2行、檔案後3行", + "i18n_75c63f427a":"此選項為一個實驗屬性實際效果基本無差異", + "i18n_75fc7de737":"路由", + "i18n_7617455241":"檔案中如果存在:MemAvailable、MemTotal 這兩個欄位,那麼 oshi 直接使用,所以本系統 中記憶體佔用計算方式:記憶體佔用=(total-available)/total", + "i18n_762e05a901":"差異釋出是指對應構建產物和專案資料夾裡面的檔案是否存在差異,如果存在增量差異那麼上傳或者覆蓋檔案。", + "i18n_7650487a87":"地址", + "i18n_76530bff27":"請輸入私人令牌", + "i18n_7653297de3":"跳轉", + "i18n_765592aa05":"如果未掛載容器資料目錄請提前備份資料後再使用此功能。", + "i18n_765d09eea5":"當前檔案不可讀,需要配置可讀檔案授權", + "i18n_767fa455bb":"目錄", + "i18n_768e843a3e":"類 192", + "i18n_769d88e425":"完成", + "i18n_76aebf3cc6":"日誌大小", + "i18n_76ebb2be96":"1分鐘", + "i18n_77017a3140":"關聯容器標籤", + "i18n_770a07d78f":"當目標工作空間不存在對應的 指令碼 時候將自動建立一個新的 指令碼", + "i18n_771d897d9a":"狀態碼", + "i18n_77373db7d8":"接收報警訊息,非必填,GET請求", + "i18n_7737f088de":"批量重新啟動", + "i18n_773b1a5ef6":"請選擇語言模式", + "i18n_775fde44cf":"程序埠快取:", + "i18n_7760785daf":"自由指令碼", + "i18n_7764df7ccc":"開啟差異釋出但不開啟清空釋出時相當於只做增量和變動更新", + "i18n_77688e95af":"容器重建是指使用已經建立的容器引數重新建立一個相同的容器。", + "i18n_7777a83497":"請輸入構建備註,長度小於 240", + "i18n_77834eb6f5":"您使用本系統", + "i18n_7785d9e038":"低版本專案資料未儲存節點ID,對應專案資料也將出來在孤獨資料中(此類資料不影響使用)", + "i18n_77b9ecc8b1":"備份名稱", + "i18n_77c1e73c08":"指令碼存放路徑:{'${user.home}'}/.jpom/xxxx.sh,執行指令碼路徑:{'${user.home}'},執行指令碼方式:bash {'${user.home}'}/.jpom/xxxx.sh par1 par2", + "i18n_77c262950c":"使用 Access Token 一次匯入多個專案", + "i18n_77e100e462":"還沒有狀態訊息", + "i18n_780afeac65":"是否開啟", + "i18n_780fb9f3d0":"更新時間:", + "i18n_7824ed010c":"真的取消當前釋出任務嗎?", + "i18n_7854b52a88":"啟用", + "i18n_787fdcca55":"系統配置", + "i18n_788a3afc90":"已失聯", + "i18n_78a4b837e3":"能通訊的IP", + "i18n_78b2da536d":"構建過程請求對應的地址,開始構建,構建完成,開始釋出,釋出完成,構建異常,釋出異常", + "i18n_78ba02f56b":"真的要徹底刪除分發資訊麼?刪除後節點下面的專案也都將徹底刪除,徹底專案會自動刪除專案相關檔案奧(包含專案日誌,日誌備份,專案檔案)", + "i18n_78caf7115c":"任務名稱", + "i18n_78dccb6e97":"所有節點(外掛端)", + "i18n_79076b6882":"真的要批量刪除這些構建資訊麼?刪除也將同步刪除所有的構建歷史記錄資訊,如果中途刪除失敗將終止刪除操作", + "i18n_7912615699":"連線狀態", + "i18n_791870de48":"倉庫密碼", + "i18n_791b6d0e62":"排名按照字母 a-z 排序", + "i18n_79698c57a2":"當前工作空間還沒有節點", + "i18n_798f660048":"模版節點", + "i18n_799ac8bf40":"支援變數引用:{'${TASK_ID}'}、{'${FILE_ID}'}、{'${FILE_NAME}'}、{'${FILE_EXT_NAME}'}", + "i18n_79a7072ee1":"令牌 url", + "i18n_79c6b6cff7":"關聯分組", + "i18n_79d3abe929":"複製", + "i18n_7a30792e2a":"編輯 SSH", + "i18n_7a3c815b1e":"檔案目錄", + "i18n_7a4ecc606c":"映象標籤,如:key1=values1&keyvalue2 使用 URL 編碼", + "i18n_7a5dd04619":"注意執行相關命令需要所在伺服器中存在對應的環境", + "i18n_7a7e25e9eb":"確定要將此資料下移嗎?下移操作可能因為列表後續資料沒有排序值操作無效!", + "i18n_7a811cc1e5":"複製 ", + "i18n_7a93e0a6ae":"選擇企業版本或者購買授權:", + "i18n_7aa81d1573":"請輸入檔名稱", + "i18n_7aaee3201a":"如果需要刪除需要提前備份或者已經確定對應檔案棄用後才能刪除 !!!!", + "i18n_7afb02ed93":"當前沒有可以引用的環境變數", + "i18n_7b2cbfada9":"釋出前停止:", + "i18n_7b36b18865":"分割槽ID", + "i18n_7b61408779":"# 專案檔案備份路徑", + "i18n_7b8e7d4abc":"真的要刪除執行記錄麼?", + "i18n_7b961e05d0":"表示月的最後一天", + "i18n_7bcbf81120":"接收包", + "i18n_7bcc3f169c":"# 內建變數 ${JPOM_WORKING_DIR} ${JPOM_BUILD_ID}", + "i18n_7bf62f7284":"手動取消分發", + "i18n_7c0ee78130":"構建日誌", + "i18n_7c223eb6e9":"釋出系統公告", + "i18n_7c9bb61536":"日誌專案名稱", + "i18n_7cb8d163bb":"變數名稱", + "i18n_7cc3bb7068":"不會真實請求節點刪除專案資訊", + "i18n_7ce511154f":"建立之後不能修改", + "i18n_7d23ca925c":"服務端時間", + "i18n_7d3f2fd640":"在檔案第 3 - 2147483647 行中搜尋", + "i18n_7ddbe15c84":"網路", + "i18n_7dde69267a":"未繫結叢集的分組:", + "i18n_7de5541032":"如果 ssh 沒有配置授權目錄是不能選擇的喲", + "i18n_7dfc7448ec":"真的要刪除倉庫資訊麼?", + "i18n_7dfcab648d":"產物", + "i18n_7e000409bb":"容易出現挖礦情況", + "i18n_7e1b283c57":" 新增", + "i18n_7e2b40fc86":"選擇節點", + "i18n_7e300e89b1":"分發成功", + "i18n_7e33f94952":",如果想要切換路徑後執行命令則需要", + "i18n_7e359f4b71":"硬碟總量:", + "i18n_7e58312632":"編輯日誌搜尋", + "i18n_7e866fece6":"請輸入兩步驗證碼", + "i18n_7e930b95ef":"釋出檔案", + "i18n_7e951d56d9":"操作時間", + "i18n_7e9f0d2606":"專案是指,節點中的某一個專案,需要提前在節點中建立專案", + "i18n_7ef30cfd31":"附加環境變數是指讀取倉庫指定環境變數檔案來新增到執行構建執行時", + "i18n_7f0abcf48d":"需要到編輯中去為一個節點繫結一個 ssh資訊才能啟用該功能", + "i18n_7f3809d36b":"構建結束", + "i18n_7f5bcd975b":"cpu佔用", + "i18n_7f7c624a84":"批量操作", + "i18n_7f7ee903da":"釋出隱藏檔案", + "i18n_7fb5bdb690":"軟體致謝", + "i18n_7fb62b3011":"批量刪除", + "i18n_7fbc0f9aae":"執行時間開始", + "i18n_7fc88aeeda":"修改密碼", + "i18n_800dfdd902":"今天", + "i18n_80198317ff":"並向您的朋友推薦或分享:", + "i18n_8023baf064":"通知狀態", + "i18n_80669da961":"CPU佔用", + "i18n_807ed6f5a6":"暫無任何資料", + "i18n_8086beecb3":"標籤名稱:", + "i18n_808c18d2bb":"值為 true 表示專案當前為執行中", + "i18n_809b12d6a0":"請耐心等待暫時不用重新整理頁面", + "i18n_80cfc33cbe":"確認重置", + "i18n_81301b6813":"開啟終端", + "i18n_81485b76d8":"請輸入主機地址", + "i18n_814dd5fb7d":"真的要刪除備份資訊麼?", + "i18n_815492fd8d":"舊版程式包占有空間", + "i18n_8160b4be4e":"異常關閉", + "i18n_819767ada1":"使用者名稱", + "i18n_8198e4461a":"專案:", + "i18n_81afd9e713":"佇列等待", + "i18n_81c1dff69c":"解決辦法", + "i18n_81d7d5cd8a":"命令詳細描述", + "i18n_81e4018e9d":"懸空型別", + "i18n_82416714a8":"需要測試的埠", + "i18n_824607be6b":"保留天數", + "i18n_824914133f":"沒有任何指令碼庫", + "i18n_8283f063d7":"專案完整目錄", + "i18n_828efdf4e5":"開啟MFA數", + "i18n_82915930eb":"併發執行", + "i18n_829706defc":"線上構建(構建關聯倉庫、構建歷史)", + "i18n_829abe5a8d":"分組", + "i18n_82b89bd049":"日誌彈窗會全屏開啟", + "i18n_82d2c66f47":"批量分配", + "i18n_8306971039":"所屬使用者", + "i18n_8309cec640":"請選擇節點專案,可能是節點中不存在任何專案,需要去節點中建立專案", + "i18n_833249fb92":"當前檔案用時", + "i18n_8347a927c0":"修改", + "i18n_835050418f":"確認要上傳最新的外掛包嗎?", + "i18n_83611abd5f":"釋出", + "i18n_8363193305":"請輸入回撥重定向 url [redirectUri]", + "i18n_8388c637f6":"自啟動", + "i18n_83aa7f3123":"分發id", + "i18n_83c61f7f9e":"請選擇監控使用者", + "i18n_83ccef50cd":"當目標工作空間已經存在 指令碼 時候將自動同步 指令碼內容、預設引數、定時執行、描述", + "i18n_83f25dbaa0":"繫結節點", + "i18n_8400529cfb":"重置自定義的程序名資訊", + "i18n_8432a98819":"操作功能", + "i18n_843f05194a":"顯示所有", + "i18n_84415a6bb1":"重置下載 token 資訊,重置後之前的下載 token 將失效", + "i18n_844296754e":"虛擬記憶體", + "i18n_84592cd99c":"可以理解為專案打包的目錄。 如 Jpom 專案執行(構建命令)", + "i18n_84597bf5bc":"阿里雲企業郵箱配置", + "i18n_84632d372f":"點選檢視詳情", + "i18n_84777ebf8b":"安全提醒", + "i18n_847afa1ff2":"請輸入IP授權,多個使用換行,0.0.0.0 是開放所有IP,支援配置IP段 192.168.1.1/192.168.1.254,192.168.1.0/24", + "i18n_848c07af9b":"管理面板", + "i18n_848e4e21da":"如:--server", + "i18n_8493205602":"開", + "i18n_84aa0038cf":"系統日誌", + "i18n_84b28944b7":"超時時間(S)", + "i18n_84d331a137":"秒 (值太小可能會取不到節點狀態)", + "i18n_84e12f7434":"會話已經關閉[ssh-terminal]", + "i18n_853d8ab485":"正在構建", + "i18n_85451d2eb5":"請輸入變數值", + "i18n_8580ad66b0":"真的要徹底刪除專案麼?徹底專案會自動刪除專案相關檔案奧(包含專案日誌,日誌備份,專案檔案)", + "i18n_85be08c99a":"未查詢到任何資料", + "i18n_85cfcdd88b":"本地構建是指直接在服務端中的伺服器執行構建命令", + "i18n_85da2e5bb1":"重啟中,請稍候...", + "i18n_85ec12ccd3":"延遲,容器升級間隔時間", + "i18n_85f347f9d0":"使用者限制使用者只能對應的工作空間裡面操作對應的功能", + "i18n_85fe5099f6":"叢集", + "i18n_86048b4fea":"移除", + "i18n_860c00f4f7":"每小時", + "i18n_863a95c914":"真的要儲存當前配置嗎?如果配置有誤,可能無法啟動服務需要手動還原奧!!!", + "i18n_867cc1aac4":" :範圍:0~23", + "i18n_869b506d66":"獨立的專案分發請到分發管理中去修改", + "i18n_869ec83e33":"未使用", + "i18n_86b7eb5e83":"刪除前需要將關聯資料都刪除後才能刪除當前工作空間?", + "i18n_86c1eb397d":"切換賬號", + "i18n_86cd8dcead":"啟動時間", + "i18n_86e9e4dd58":"倉庫lastcommit", + "i18n_86f3ec932c":"讀取大小", + "i18n_86fb7b5421":"節點賬號", + "i18n_8704e7bdb7":"請輸入令牌 url [accessTokenUri]", + "i18n_871cc8602a":"二級目錄", + "i18n_8724641ba8":"間隔(/) > 區間(-) > 列表(,)", + "i18n_8756efb8f4":"真的要刪除當前資料夾麼?", + "i18n_87659a4953":"確認要關閉 beta 計劃嗎?", + "i18n_8780e6b3d1":"檔案管理", + "i18n_878aebf9b2":"登入名稱", + "i18n_87d50f8e03":"容器ID", + "i18n_87db69bd44":"限制資源", + "i18n_87dec8f11e":"錯誤的工作空間資料", + "i18n_87e2f5bf75":"追加指令碼模板", + "i18n_87eb55155a":"行數:", + "i18n_8813ff5cf8":"如果是在啟動服務端後安裝並配置的環境變數需要通過終端命令來重啟服務端才能生效", + "i18n_883848dd37":"實際記憶體佔用", + "i18n_8844085e15":"凌晨0點", + "i18n_884ea031d3":"請輸入變數描述", + "i18n_8887e94cb7":"順序執行(有執行失敗將繼續)", + "i18n_888df7a89e":"不推薦", + "i18n_88ab27cfd0":"分組/標籤:", + "i18n_88b4b85562":"打包預釋出環境 npm i && npm run build:stage", + "i18n_88b79928e7":"證書丟失", + "i18n_88c5680d0d":"管理狀態:", + "i18n_88c85a2506":"新增的節點(外掛端)將自動", + "i18n_88e6615734":"解綁會檢查資料關聯性,同時將自動刪除節點專案和指令碼快取資訊,一般用於伺服器無法連線且已經確定不再使用", + "i18n_88f5c7ac4a":"請選擇排序欄位", + "i18n_8900539e06":"寫入大小", + "i18n_89050136f8":"釋出後操作", + "i18n_891db2373b":"自動重新整理", + "i18n_897d865225":"映象數:", + "i18n_89944d6ccb":"標籤限制為字母數字且長度 1-10", + "i18n_899dbd7b9a":"CPU型號", + "i18n_899fe0c5dd":"節點地址為外掛端的 IP:PORT 外掛端埠預設為:212", + "i18n_89a40a1a8b":"分發過程請求,非必填,GET請求", + "i18n_89cfb655e0":"容器名標籤", + "i18n_89d18c88a3":"請輸入檔案任務名", + "i18n_89f5ca6928":"支援萬用字元", + "i18n_8a1767a0d2":"開啟此選項後可以正常釋出隱藏檔案", + "i18n_8a3e316cd7":"不編碼", + "i18n_8a414f832f":"叢集地址", + "i18n_8a49e2de39":"QQ 郵箱配置", + "i18n_8a4dbe88b8":"點選進入節點管理", + "i18n_8a745296f4":"開機時間:", + "i18n_8aa25f5fbe":"釋出型別", + "i18n_8ae2b9915c":"請填寫第", + "i18n_8aebf966b2":"叢集訪問地址", + "i18n_8b1512bf3a":"如果埠", + "i18n_8b2e274414":"上次過載結果", + "i18n_8b3db55fa4":"叢集ID:", + "i18n_8b63640eee":"賬號被禁用", + "i18n_8b6e758e4c":"懸停到儀表盤上顯示具體含義", + "i18n_8b73b025c0":"簡單易用,但不支援金鑰匯出備份", + "i18n_8b83cd1f29":"要拉取的映象名稱", + "i18n_8ba971a184":"私人令牌", + "i18n_8ba977b4b7":"# 限制備份指定檔案字尾(支援正則)", + "i18n_8bd3f73502":"節點密碼", + "i18n_8be76af198":"163 郵箱配置", + "i18n_8be868ba1b":"類 10", + "i18n_8c0283435b":":表示連續區間,例如在分上,表示2,3,4,5,6,7,8分", + "i18n_8c24b5e19c":"請使用應用掃碼繫結令牌,然後輸入驗證碼確認繫結才生效", + "i18n_8c2da7cce9":"沒有任何證書", + "i18n_8c4db236e1":"請輸入指令碼標記,標記只能是字母或者數字長度需要小於 20 並且全域性唯一", + "i18n_8c61c92b4b":"備份型別", + "i18n_8c66392870":"需要使用 ssh-keygen -m PEM -t rsa -b 4096 -C", + "i18n_8c67370ee5":"如果產物同步到檔案中心,當前值會共享", + "i18n_8c7c7f3cfa":"服務端指令碼", + "i18n_8c7ce1da57":"開啟 dockerTag 版本遞增後將在每次構建時自動將版本號最後一位數字同步為構建序號ID, 如:當前構建為第 100 次構建 testtag:1.0 -> testtag:1.100,testtag:1.0.release -> testtag:1.100.release。如果沒有匹配到數字將忽略遞增操作", + "i18n_8c7d19b32a":"允許執行的記憶體節點 (MEM) (0-3, 0,1)。 僅在 NUMA 系統上有效。", + "i18n_8cae9cb626":"淺色 idea", + "i18n_8ccbbb95a4":"請填寫遠端URL", + "i18n_8cd628f495":"所有的IP:", + "i18n_8d0fa2ee2d":"請輸入埠號", + "i18n_8d1286cd2e":"沒有任何分發日誌", + "i18n_8d13037eb7":"狀態訊息:", + "i18n_8d202b890c":"批量觸發器地址", + "i18n_8d3d771ab6":"編輯叢集", + "i18n_8d5956ca2a":"以 yaml/yml 格式配置,scriptId 為專案路徑下的指令碼檔案的相對路徑或者服務端指令碼模版ID,可以到服務端指令碼模版編輯彈窗中檢視 scriptId", + "i18n_8d5c1335b6":"容器名稱數字字母,且長度大於1", + "i18n_8d62b202d9":"請選擇要使用的檔案", + "i18n_8d63ef388e":"暫停", + "i18n_8d6d47fbed":"# 在指定目錄執行: ./ 專案目錄 /root/ 特定目錄 預設在 {'${jpom_agent_data_path}'}/script_run_cache ", + "i18n_8d6f38b4b1":"檔案描述", + "i18n_8d90b15eaf":"# 宿主機檔案上傳到容器 /host:/container:true", + "i18n_8d92fb62a7":"請選擇模板節點", + "i18n_8d9a071ee2":"匯入", + "i18n_8da42dd738":"秒級別(預設未開啟秒級別,需要去修改配置檔案中:[system.timerMatchSecond])", + "i18n_8dbe0c2ffa":"佔用空間:", + "i18n_8dc09ebe97":"獲取", + "i18n_8dc8bbbc20":"檔案系統", + "i18n_8de2137776":"叢集任務", + "i18n_8e2ed8ae0d":"【獨立分發】", + "i18n_8e331a52de":"只允許訪問的 IP 地址", + "i18n_8e34aa1a59":"以此機器節點配置為模板", + "i18n_8e389298e4":"匯出映象", + "i18n_8e38d55231":"真的要徹底退出系統麼?徹底退出將退出登入和清空瀏覽器快取", + "i18n_8e54ddfe24":"啟動", + "i18n_8e6184c0d3":"專案可能支援關聯如下資料:", + "i18n_8e6a77838a":"請選擇要分發到的機器節點", + "i18n_8e872df7da":"注意是整行不能包含空格", + "i18n_8e89763d95":"宿主機ip", + "i18n_8e8bcfbb4f":"確定要修剪對應的資訊嗎?修剪會自動清理對應的資料", + "i18n_8e9bd127fb":"需要提前為工作空間配置授權目錄", + "i18n_8ea4c3f537":"從尾搜尋、檔案前100行、檔案後100行", + "i18n_8ea93ff060":"節點指令碼模版是儲存在節點中的命令指令碼用於線上管理一些指令碼命令,如初始化軟體環境、管理應用程式等", + "i18n_8ef0f6c275":"關閉 beta 計劃", + "i18n_8f0bab9a5a":"在讀取的日誌檔案數", + "i18n_8f0c429b46":"遷移操作不具有事務性質,如果流程被中斷或者限制條件不滿足可能產生冗餘資料!!!!", + "i18n_8f36f2ede7":"工作空間名稱:", + "i18n_8f3747c057":"服務名稱", + "i18n_8f40b41e89":"過期時間:", + "i18n_8f7a163ee9":"快速安裝外掛端", + "i18n_8f8f88654f":"暫無節點資訊", + "i18n_8fb7785809":"如果在生成私鑰的過程中有加密,那麼需要把加密密碼填充到上面的密碼框中", + "i18n_8fbcdbc785":"請輸入別名碼", + "i18n_8fd9daf8e9":"保證在內網中使用可以忽略 TLS 證書", + "i18n_8fda053c83":"寫入次數", + "i18n_8ffded102f":"如果需要定時自動構建則填寫,cron 表示式.預設未開啟秒級別,需要去修改配置檔案中:[system.timerMatchSecond])", + "i18n_900c70fa5f":"警告", + "i18n_9014d6d289":"備份列表", + "i18n_90154854b6":"請輸入host", + "i18n_901de97cdb":"方式請求介面引數傳入到請求體 ContentType 請使用:text/plain", + "i18n_903b25f64e":"未知狀態", + "i18n_904615588b":"檔案型別沒有控制檯功能", + "i18n_9057ac9664":"請選擇觸發型別", + "i18n_9065a208e8":"通過 URL 下載遠端檔案到專案資料夾,需要到對應的工作空間下授權目錄配置中配置允許的 HOST 授權", + "i18n_906f6102a7":"重啟成功", + "i18n_9086111cff":"關聯工作空間 docker", + "i18n_90b5a467c1":"重新整理目錄", + "i18n_90c0458a4c":"匯入備份", + "i18n_90eac06e61":"宿主機目錄", + "i18n_912302cb02":"瀏覽器", + "i18n_9136e1859a":"Docker映象", + "i18n_913ef5d129":"執行重啟", + "i18n_916cde39c4":"所有引數將拼接成字串以空格分隔形式執行指令碼,需要注意引數順序和未填寫值的引數將自動忽略", + "i18n_916ff9eddd":"請輸入暱稱", + "i18n_91985e3574":"自動探測", + "i18n_91a10b8776":" 指令碼庫 ", + "i18n_920f05031b":"狀態描述", + "i18n_922b76febd":"執行模式必填", + "i18n_923f8d2688":"釋出後命令", + "i18n_9255f9c68f":"會話已經關閉[tail-file]", + "i18n_92636e8c8f":"跳過", + "i18n_9282b1e5da":"企業微信掃碼", + "i18n_929e857766":"證書型別", + "i18n_92c6aa6db9":"如果您 SSH 機器中存在 docker 但是系統沒有監測到,您需要到【配置管理】-", + "i18n_92dde4c02b":"廣告投放", + "i18n_92e3a830ae":"幫助", + "i18n_92f0744426":"容器構建是指使用 docker 容器執行構建,這樣可以達到和宿主機環境隔離不用安裝依賴環境", + "i18n_92f3fdb65f":"倉庫:", + "i18n_92f9a3c474":"切換語言後頁面將自動重新整理", + "i18n_9300692fac":"標記引用", + "i18n_9302bc7838":"請輸入要檢查的埠", + "i18n_930882bb0a":"個", + "i18n_9308f22bf6":"單個觸發器地址中:第一個隨機字串為指令碼ID,第二個隨機字串為 token", + "i18n_930fdcdf90":"配置名 (如:size)", + "i18n_9324290bfe":"如:key1", + "i18n_932b4b7f79":"注意:在每一個子表示式中優先順序:", + "i18n_934156d92c":"建立分發專案", + "i18n_9341881037":"確定要取批量構建嗎?注意:同時執行多個構建將佔用較大的資源,請慎重使用批量構建,如果批量構建的數量超多構建任務佇列等待數,構建任務將自動取消", + "i18n_935b06789f":"您還未執行操作", + "i18n_9362e6ddf8":"危險操作!!!", + "i18n_938dd62952":"執行路徑", + "i18n_939d5345ad":"提交", + "i18n_93e1df604a":"機器分組", + "i18n_93e894325d":"批量啟動", + "i18n_9402665a2c":" 持續搜尋(對話方塊不會自動關閉,按 Enter 查詢下一個,按 Shift-Enter 查詢上一個)", + "i18n_9412eb8f99":"請填寫平臺地址", + "i18n_9443399e7d":" ,範圍1970~2099,但是第7位不做解析,也不做匹配", + "i18n_94763baf5f":"可以到節點管理中的【外掛端配置】=>【授權配置】修改", + "i18n_947d983961":"溫馨提示", + "i18n_948171025e":"會話已經關閉[docker-log]", + "i18n_949934d97c":"超大", + "i18n_949a8b7bd2":"列設定", + "i18n_94aa195397":"證書檔案", + "i18n_94ca71ae7b":"請選擇要使用的證書", + "i18n_94d4fcca1b":"建立賬號", + "i18n_952232ca52":"構建歷史可能佔有較多硬碟空間,建議根據實際情況配置保留個數", + "i18n_953357d914":"忽略校驗 state", + "i18n_953ec2172b":"未重啟成功:", + "i18n_954fb7fa21":"真的要刪除專案麼?刪除專案不會刪除專案相關檔案奧,建議先清理專案相關檔案再刪除專案", + "i18n_956ab8a9f7":"配置檔案嗎?配置檔案一旦建立不能通過管理頁面刪除的奧?", + "i18n_957c1b1c50":"叢集節點", + "i18n_95a43eaa59":"建立人", + "i18n_95b351c862":"編輯", + "i18n_95c5c939e4":"可選擇的列表和專案授權目錄是一致的,即相同配置", + "i18n_95dbee0207":"遠端下載安全HOST", + "i18n_96283fc523":"備份檔案不存在", + "i18n_964d939a96":" 長名稱:", + "i18n_969098605e":"環境變數是指配置在系統中的一些固定引數值,用於指令碼執行時候快速引用。", + "i18n_96b78bfb6a":"請勿手動刪除資料目錄下面檔案 !!!!", + "i18n_96c1c8f4ee":"灰綠 abcdef", + "i18n_96c28c4f17":"加入到哪個叢集", + "i18n_96d46bd22e":"手動重新整理統計", + "i18n_96e6f43118":"容器 runtime", + "i18n_974be6600d":"密碼必須包含數字,字母,字元,且大於6位", + "i18n_977bfe8508":"標籤(TAG)", + "i18n_979b7d10b0":"構建中斷", + "i18n_97a19328a8":"立即開啟", + "i18n_97cb3c4b2e":"工作空間環境變數用於構建命令相關", + "i18n_97d08b02e7":"網路埠測試", + "i18n_97ecc1bbe9":"輸出流量", + "i18n_981cbe312b":"至", + "i18n_9829e60a29":"實時版本號", + "i18n_98357846a2":"表格檢視才能使用批量操作功能", + "i18n_983f59c9d4":"驗證碼", + "i18n_9878af9db5":"請到【系統管理】-> 【資產管理】-> 【Docker管理】新增Docker並建立叢集,或者將已存在的的 Docker 叢集授權關聯、分配到此工作空間", + "i18n_9880bd3ba1":"此工具用於檢查 cron 表示式是否正確,以及計劃執行時間", + "i18n_989f1f2b61":"真的要重啟專案麼?", + "i18n_98a315c0fc":"授權", + "i18n_98cd2bdc03":"表格檢視才能使用同步配置功能", + "i18n_98d69f8b62":"工作空間", + "i18n_98e115d868":"執行中的定時任務", + "i18n_9914219dd1":"從頭搜尋", + "i18n_9932551cd5":"記憶體", + "i18n_993a5c7eee":"#配置說明:https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerCreate", + "i18n_99593f7623":"客戶端ID", + "i18n_9964d6ed3f":"掛載", + "i18n_996dc32a98":"系統型別", + "i18n_9970ad0746":"主題", + "i18n_9971192b6a":"。如:http", + "i18n_9973159a4d":"匹配一個字元", + "i18n_998b7c48a8":"檢視容器", + "i18n_99b3c97515":"小時級別", + "i18n_99cba05f94":"真的要刪除 SSH 麼?當前 ssh 關聯的指令碼在刪除後均將失效", + "i18n_99d3e5c718":" 開始搜尋", + "i18n_99f0996c0a":"請選擇日誌目錄", + "i18n_9a00e13160":"單個觸發器地址中:第一個隨機字串為專案ID(服務端),第二個隨機字串為 token", + "i18n_9a0c5b150c":"編輯 命令", + "i18n_9a2ee7044f":"變數值", + "i18n_9a436e2a53":"修剪具有指定標籤的物件,多個使用逗號分隔", + "i18n_9a4b872895":"叢集操作", + "i18n_9a56bb830e":"使用者暱稱", + "i18n_9a77f3523e":"映象 tag", + "i18n_9a7b52fc86":"所有", + "i18n_9a8eb63daf":"配置工作空間許可權", + "i18n_9ab433e930":"其他配置", + "i18n_9ac4765895":"一個整數值,表示此容器相對於其他容器的相對 CPU 權重。", + "i18n_9adf43e181":"正在構建數", + "i18n_9ae40638d2":"部署證書", + "i18n_9af372557e":"服務端埠", + "i18n_9aff624153":"監控", + "i18n_9b0bc05511":"在檔案第 1 - 100 行中搜尋", + "i18n_9b1c5264a0":"上傳後", + "i18n_9b280a6d2d":"節點:", + "i18n_9b3e947cc9":"節點狀態:", + "i18n_9b5f172ebe":"繫結叢集", + "i18n_9b7419bc10":"QQ郵箱", + "i18n_9b74c734e5":"節點賬號密碼為外掛端的賬號密碼,並非使用者賬號(管理員)密碼", + "i18n_9b78491b25":"請輸入授權路徑,回車支援輸入多個路徑,系統會自動過濾 ../ 路徑、不允許輸入根路徑", + "i18n_9b7ada2613":" :範圍:1~31,", + "i18n_9b9e426d16":"在讀取的日誌檔案數:", + "i18n_9ba71275d3":",請耐心等待暫時不用重新整理頁面,升級成功後會自動重新整理", + "i18n_9baca0054e":"修改人", + "i18n_9bbb6b5b75":"真的要刪除日誌搜尋麼?", + "i18n_9bd451c4e9":"節點已經存在", + "i18n_9be8ff8367":"支援變數替換:{'${BUILD_ID}'}、{'${BUILD_NAME}'}、{'${BUILD_RESULT_FILE}'}、{'${BUILD_NUMBER_ID}'}", + "i18n_9bf4e3c9de":"建立分發專案是指,全新建立一個屬於節點分發到專案,建立成功後專案資訊將自動同步到對應的節點中,修改節點分發資訊也自動同步到對應的節點中", + "i18n_9bf5aa6672":"web socket 錯誤,請檢查是否開啟 ws 代理", + "i18n_9c19a424dc":"請輸入原密碼", + "i18n_9c2a917905":"搜尋命令", + "i18n_9c2f1d3f39":"內的root擁有真正的root許可權。", + "i18n_9c3a3e1b03":"父級不存在自動刪除", + "i18n_9c3a5e1dad":"請到【系統管理】-> 【資產管理】-> 【機器管理】新增節點,或者將已新增的機器授權關聯、分配到此工作空間", + "i18n_9c3c05d91b":"節點地址建議使用內網地址", + "i18n_9c55e8e0f3":"允許執行的 CPU(例如,0-3、0,1)。", + "i18n_9c66f7b345":"真的要刪除機器麼?刪除會檢查資料關聯性", + "i18n_9c84cd926b":"新機器還需要繫結工作空間,因為我們建議將不同叢集資源分配到不同的工作空間來管理", + "i18n_9c942ea972":"證書生成方式可以參考文件)來連線 docker 提升安全性", + "i18n_9c99e8bec9":"不填寫則釋出至專案的根目錄", + "i18n_9cac799f2f":"選擇分組名", + "i18n_9caecd931b":"欄位", + "i18n_9cd0554305":"如果不需要保留較多構建歷史資訊可以到服務端修改構建相關配置引數", + "i18n_9ce5d5202a":"執行的Jar包:", + "i18n_9d577fe51b":"檔案來源", + "i18n_9d5b1303e0":"建立叢集會將嘗試獲取 docker 中叢集資訊,如果存在叢集資訊將自動同步叢集資訊到系統,反之不存在叢集資訊將自動建立 swarm 叢集", + "i18n_9d7d471b77":"請選擇節點角色", + "i18n_9d89cbf245":"分發名稱", + "i18n_9dd62c9fa8":"終端命令無限制", + "i18n_9ddaa182bd":"外掛端時間:", + "i18n_9de72a79fe":"檢視檔案", + "i18n_9e09315960":"重建", + "i18n_9e2e02ef08":"分發型別", + "i18n_9e4ae8a24f":"釘釘掃碼", + "i18n_9e560a4162":"方式生成的 SSH key", + "i18n_9e5ffa068e":"基本資訊", + "i18n_9e6b699597":"nanoCPUs 最小 1000000", + "i18n_9e78f02aad":"引數描述,引數描述沒有實際作用,僅是用於提示引數的含義", + "i18n_9e96d9c8d3":"系統負載:", + "i18n_9e98fa5c0d":"檔名欄支援右鍵選單", + "i18n_9ec961d8cb":"選擇構建產物", + "i18n_9ee0deb3c8":"網路開了小差!請重試...:", + "i18n_9ee9d48699":"建立後不支援修改", + "i18n_9f01272a10":" 法律風險", + "i18n_9f0de3800b":"請填寫倉庫名稱", + "i18n_9f52492fbc":"配置詳情請參考配置示例", + "i18n_9f6090c819":"傳入引數有:buildId、buildName、type、statusMsg、triggerTime", + "i18n_9f6fa346d8":"請輸入 SSH 名稱", + "i18n_9f70e40e04":"執行時間", + "i18n_9fb12a2d14":"釋出後執行的命令(非阻塞命令),一般是啟動專案命令 如:ps -aux | grep java, 支援變數替換:{'${ BUILD_ID }'}、{'${ BUILD_NAME }'}、{'${ BUILD_RESULT_FILE }'}、{'${ BUILD_NUMBER_ID }'} ", + "i18n_9fb61a9936":"版本遞增", + "i18n_9fc2e26bfa":"請選擇專案", + "i18n_9fca7c455f":"登入時間", + "i18n_9febf31146":"請選擇檔案", + "i18n_9ff5504901":"傳入環境變數有:buildId、buildName、type、statusMsg、triggerTime、buildNumberId、buildSourceFile", + "i18n_a001a226fd":"更新時間", + "i18n_a03c00714f":"批量關閉", + "i18n_a03ea1e864":"請選擇分發到的節點", + "i18n_a04b7a8f5d":"單個觸發器請求支援將引數解析為環境變數傳入指令碼執行,比如傳入引數名為 abc=efg 在指令碼中引入則為:{'${trigger_abc}'}", + "i18n_a050cbc36d":"您的瀏覽器版本太低,不支援該功能", + "i18n_a056d9c4b3":"選擇指令碼", + "i18n_a05c1667ca":"構建歷史", + "i18n_a08cbeb238":"並且配置正確的環境變數", + "i18n_a09375d96c":"懸空", + "i18n_a093ae6a6e":"自動續期", + "i18n_a0a111cbbd":"分發專案-", + "i18n_a0a3d583b9":"總記憶體:", + "i18n_a0b9b4e048":"請輸入客戶端ID [clientId]", + "i18n_a0d0ebc519":"全域性代理", + "i18n_a0e31d89ff":"一般建議 10 秒以上", + "i18n_a0f1bfad78":"資料目錄大小包含:臨時檔案、線上構建檔案、資料庫檔案等", + "i18n_a11cc7a65b":"請輸入內容", + "i18n_a13d8ade6a":"關聯節點資料是非同步獲取有一定時間延遲", + "i18n_a14da34559":"資源監控異常", + "i18n_a156349591":" 檢視", + "i18n_a1638e78e8":"匹配包含 a 或者 b 的行", + "i18n_a17450a5ff":"選擇壓縮檔案", + "i18n_a17b5ab021":"當前檔案已經存在啦", + "i18n_a17bc8d947":"控制檯日誌只是啟動專案輸出的日誌資訊,並非專案日誌。可以關閉控制檯日誌備份功能:", + "i18n_a189314b9e":"不釋出", + "i18n_a1a3a7d853":"現成生成", + "i18n_a1b745fba0":"請輸入備份名稱", + "i18n_a1bd9760fc":"定時任務", + "i18n_a1c4a75c2d":"是一款開源軟體您使用這個專案並感覺良好,或是想支援我們繼續開發,您可以通過如下方式支援我們:", + "i18n_a1da57ab69":"支付寶轉賬", + "i18n_a1e24fe1f6":"使用者時間", + "i18n_a1f58b7189":"引數{count}值", + "i18n_a1fb7f1606":"指令碼管理", + "i18n_a20341341b":"顯示前N行", + "i18n_a24d80c8fa":"專案啟動,停止,重啟,檔案變動都將請求對應的地址", + "i18n_a25657422b":"變數名", + "i18n_a2a0f52afe":"不填將使用預設的 $HOME/.ssh 目錄中的配置,使用優先順序是:id_dsa>id_rsa>identity", + "i18n_a2ae15f8a7":"構建流程", + "i18n_a2e62165dc":"真的要儲存當前配置嗎?IP 授權請慎重配置奧( 授權是指只允許訪問的 IP ),配置後立馬生效 如果配置錯誤將出現無法訪問的情況,需要手動恢復奧!!!", + "i18n_a2ebd000e4":"不做任何操作", + "i18n_a3296ef4f6":"全屏終端", + "i18n_a33a2a4a90":"服務端同步的指令碼不能在此修改", + "i18n_a34545bd16":"構建引數,如:key1=values1&keyvalue2 使用 URL 編碼", + "i18n_a34b91cdd7":"環境變數還可以用於倉庫賬號密碼、ssh密碼引用", + "i18n_a34c24719b":"開始執行任務", + "i18n_a35740ae41":"操作提示", + "i18n_a3751dc408":" :12點的每分鐘執行", + "i18n_a37c573d7b":"可以是 Unix 時間戳、日期格式的時間戳或 Go 持續時間字串(例如 10m、1h30m),相對於守護程序機器的時間計算。", + "i18n_a38ed189a2":"上傳更新前請閱讀更新日誌裡面的說明和注意事項並且", + "i18n_a39340ec59":"禁止命令", + "i18n_a396da3e22":"當前工作空間還沒有專案並且也沒有任何節點", + "i18n_a3d0154996":"檔案狀態", + "i18n_a3f1390bf1":"修改後如果有原始關聯資料將失效,需要重新配置關聯", + "i18n_a4006e5c1e":"建立備份", + "i18n_a421ec6187":"編輯環境變數", + "i18n_a4266aea79":"真的要刪除此服務麼?", + "i18n_a436c94494":"飛書掃碼", + "i18n_a472019766":"節點Id", + "i18n_a497562c8e":"執行人", + "i18n_a4f5cae8d2":"開啟狀態", + "i18n_a4f629041c":"路徑需要配置絕對路徑", + "i18n_a50fbc5a52":"支援指定網絡卡名稱來繫結:", + "i18n_a51cd0898f":"容器名稱", + "i18n_a51d8375b7":"選擇靜態檔案", + "i18n_a52a10123f":"如果升級失敗需要手動恢復奧", + "i18n_a52aa984cd":"真的要刪除許可權組麼?", + "i18n_a53d137403":"會話已經關閉[free-script]", + "i18n_a5617f0369":"SSH連線資訊", + "i18n_a577822cdd":"儲存並構建", + "i18n_a59d075d85":"自定義克隆深度,避免大倉庫全部克隆", + "i18n_a5d550f258":"間隔時間", + "i18n_a5daa9be44":"上傳前請檢查包是否完整,否則可能出現更新後無法正常啟動的情況!!", + "i18n_a5e9874a96":"請選擇釋出到哪個 docker 叢集", + "i18n_a5f84fd99c":"非隱私", + "i18n_a6269ede6c":"管理節點", + "i18n_a62fa322b4":"證書將打包成 zip 檔案上傳到對應的資料夾", + "i18n_a637a42173":"選擇的構建歷史產物已經不存在啦", + "i18n_a63fe7b615":"分配到工作空間後還需要到關聯中進行配置對應工作空間才能完美使用奧", + "i18n_a657f46f5b":"周", + "i18n_a66644ff47":"類 172", + "i18n_a66fff7541":"遠端下載URL", + "i18n_a6bf763ede":"機器節點", + "i18n_a6fc9e3ae6":"上傳檔案", + "i18n_a74b62f4bb":"硬碟資訊", + "i18n_a75a5a9525":"釋出目錄,構建產物上傳到對應目錄", + "i18n_a75b96584d":"服務Id", + "i18n_a75f781415":"服務端選單", + "i18n_a7699ba731":"上傳成功", + "i18n_a76b4f5000":"管理員數", + "i18n_a77cc03013":"如果引用指令碼庫需要提前將對應指令碼分發到機器節點中才能正常使用", + "i18n_a795fa52cd":"徹底退出", + "i18n_a7a9a2156a":"請輸入確認密碼", + "i18n_a7c8eea801":"嘗試自動續簽成功", + "i18n_a7ddb00197":"阿里雲企業郵箱 SSL", + "i18n_a805615d15":"type 的值有:startReady、pull、executeCommand、release、done、stop、success、error", + "i18n_a810520460":"密碼", + "i18n_a823cfa70c":"容器標籤", + "i18n_a84a45b352":"升級策略", + "i18n_a8754e3e90":"填寫正確的IP地址", + "i18n_a87818b04f":"等待開始", + "i18n_a8920fbfad":"命令路徑請修改為您的伺服器中的實際路徑", + "i18n_a89646d060":"建立工作空間後還需要在對應工作空間中分別管理對應資料", + "i18n_a8f44c3188":"賬號是系統特定演示使用的賬號", + "i18n_a90cf0796b":"資訊:", + "i18n_a912a83e6f":"外掛版本", + "i18n_a918bde61d":"您還未構建", + "i18n_a91ce167c1":"檔案id", + "i18n_a9463d0f1a":"搜尋模式,預設檢視檔案最後多少行,從頭搜尋指從指定行往下搜尋,從尾搜尋指從檔案尾往上搜尋多少行", + "i18n_a94feac256":"載速度根據網速來確定,如果網路不佳下載會較慢", + "i18n_a952ba273f":"聯絡時請備註來意", + "i18n_a9795c06c8":"沒有SSH", + "i18n_a98233b321":"使用微軟全家桶的推薦", + "i18n_a9886f95b6":"確定要刪除此指令碼庫嗎?", + "i18n_a9add9b059":"資料儲存目錄:", + "i18n_a9b50d245b":"不繫結", + "i18n_a9c52ffd40":"嚴格執行:", + "i18n_a9c999e0bd":"建議在上傳後的指令碼中對檔案進行自定義更名,SSH 上傳預設為:{'${FILE_ID}'}.{'${FILE_EXT_NAME}'}", + "i18n_a9de52acb0":"操作方法", + "i18n_a9eed33cfb":"如果版本相差大需要重新初始化資料來保證和當前程式裡面欄位一致", + "i18n_a9f94dcd57":"部署", + "i18n_aa53a4b93a":"沒有任何網路介面資訊", + "i18n_aa9236568f":"統計趨勢", + "i18n_aabdc3b7c0":"專案路徑", + "i18n_aac62bc255":"點選檢視日誌", + "i18n_aad7450231":"請輸入選擇繫結的叢集", + "i18n_aadf9d7028":"用於下載遠端檔案來進行節點分發和檔案上傳", + "i18n_aaeb54633e":"過載", + "i18n_ab006f89e7":"手動刪除", + "i18n_ab13dd3381":"自建 Gitlab 賬號登入", + "i18n_ab3615a5ad":"下載安裝包", + "i18n_ab3725d06b":"會話已經關閉", + "i18n_ab7f78ba4c":"空間ID(全匹配)", + "i18n_ab968d842f":"構建映象嘗試去更新基礎映象的新版本", + "i18n_ab9a0ee5bd":"資料夾路徑 需要在倉庫裡面 dockerfile", + "i18n_ab9c827798":"沒有docker叢集", + "i18n_abb6b7260b":"如果多選 ssh 下面目錄只顯示選項中的第一項,但是授權目錄需要保證每項都配置對應目錄", + "i18n_abba4043d8":"從頭搜尋、檔案前20行、檔案後3行", + "i18n_abba4775e1":"命令引數", + "i18n_abd9ee868a":"網路模式:bridge、container:、host、container、none", + "i18n_abdd7ea830":"請輸入新密碼", + "i18n_abee751418":"容器Id: ", + "i18n_ac00774608":"第", + "i18n_ac0158db83":"任務id", + "i18n_ac2f4259f1":"新版本:", + "i18n_ac408e4b03":"請選擇證書型別", + "i18n_ac5f3bfa5b":"選擇要監控的專案,file 型別專案不可以監控", + "i18n_ac762710a5":"支援自定義排序欄位:sort", + "i18n_ac783bca36":"真的要退出並切換賬號登入麼?", + "i18n_acb4ce3592":"請選擇靜態檔案中的檔案", + "i18n_acd5cb847a":"失敗", + "i18n_ace71047a0":"請到【系統管理】-> 【資產管理】-> 【SSH管理】新增SSH,或者將已新增的SSH授權關聯、分配到此工作空間", + "i18n_acf14aad3c":"不用挨個配置。配置後會覆蓋之前的配置", + "i18n_ad209825b5":"請選擇修剪型別", + "i18n_ad311f3211":"請選擇倉庫", + "i18n_ad35f58fb3":"佔用空間", + "i18n_ad4b4a5b3b":"宿主", + "i18n_ad780debbc":"回滾策略", + "i18n_ad8b626496":"真的要刪除構建歷史記錄麼?", + "i18n_ad9788b17d":"異常恢復", + "i18n_ad9a677940":"指定 settings 檔案打包 mvn -s xxx/settings.xml clean package", + "i18n_adaf94c06b":"執行結果", + "i18n_adbec9b14d":"建立備份資訊", + "i18n_adcd1dd701":"返回列表", + "i18n_add91bb395":"邏輯節點", + "i18n_ae0d608495":"是否使用MFA", + "i18n_ae0fd9b9d2":"備份時間", + "i18n_ae12edc5bf":"點選複製檔案路徑", + "i18n_ae17005c0c":"未加入", + "i18n_ae35be7986":"token,全匹配", + "i18n_ae653ec180":"詳細描述", + "i18n_ae6838c0e6":"節點分發", + "i18n_ae809e0295":"字尾,精準搜尋", + "i18n_aeade8e979":"未初始化", + "i18n_aeb44d34e6":"一次性捐款贊助", + "i18n_aec7b550e2":"刪除工作空間確認", + "i18n_aed1dfbc31":"中", + "i18n_aefd8f9f27":"請選擇還原方式", + "i18n_af013dd9dc":"重啟成功後會自動重新整理", + "i18n_af0df2e295":"需要到 ssh 資訊中配置允許編輯的檔案字尾", + "i18n_af14cd6893":"請填寫構建 DSL 配置內容,可以點選上方切換 tab 檢視配置示例", + "i18n_af3a9b6303":"企業微信掃碼賬號登入", + "i18n_af427d2541":"資料更新時間", + "i18n_af4d18402a":"已經斷開連線啦", + "i18n_af51211a73":"頁面內容會出現滾動條", + "i18n_af708b659f":"記憶體:", + "i18n_af7c96d2b9":"同步機制採用容器 host 確定是同一個伺服器(docker)", + "i18n_af924a1a14":"下載異常", + "i18n_af98c31607":"物理節點專案數量:", + "i18n_afa8980495":"請輸入允許編輯檔案的字尾及檔案編碼,不設定編碼則預設取系統編碼,示例:設定編碼:txt{'@'}utf-8, 不設定編碼:txt", + "i18n_afb9fe400b":"使用率:", + "i18n_b04070fe42":"選擇代理型別", + "i18n_b04209e785":"關聯資料:", + "i18n_b05345caad":"所有者", + "i18n_b07a33c3a8":"請選擇分發節點", + "i18n_b0b9df58fd":"SSH節點", + "i18n_b0fa44acbb":"佔用率:", + "i18n_b10b082c25":"的值有:stop、beforeStop、start、beforeRestart", + "i18n_b1192f8f8e":"真的取消當前分發嗎?", + "i18n_b11b0c93fa":"如果在 Linux 中實際執行記憶體可能和您直接使用 free -h 命令查詢到 free 和 total 欄位計算出數值相差過大那麼此時就是您當前伺服器中的交換記憶體引起的", + "i18n_b12d003367":"隱私欄位", + "i18n_b153126fc2":"請輸入工作空間名稱", + "i18n_b15689296a":"風險提醒", + "i18n_b15d91274e":"關閉", + "i18n_b166a66d67":"確定要將此數上移嗎?", + "i18n_b17299f3fb":"外掛端程序ID:", + "i18n_b1785ef01e":"節點名稱", + "i18n_b186c667dc":"分發過程請求對應的地址,開始分發,分發完成,分發失敗,取消分發", + "i18n_b188393ea7":"釋出的SSH", + "i18n_b1a09cee8e":"清空還原", + "i18n_b1dae9bc5c":"管理員", + "i18n_b28836fe97":"分發 ID 等同於專案 ID", + "i18n_b28c17d2a6":" (MEM) (0-3, 0,1)。 僅在 NUMA 系統上有效。", + "i18n_b29fd18c93":"請選擇指定釋出的專案", + "i18n_b2f296d76a":"5分鐘", + "i18n_b30d07c036":"批量關閉啟動", + "i18n_b328609814":"管理員擁有:管理服務端的部分許可權", + "i18n_b339aa8710":"表格", + "i18n_b33c7279b3":"認證方式", + "i18n_b3401c3657":"容器目錄", + "i18n_b341f9a861":"任務時間", + "i18n_b343663a14":"清空釋出是指在上傳新檔案前,會將專案資料夾目錄裡面的所有檔案先刪除後再儲存新檔案", + "i18n_b36e87fe5b":"不執行,但是編譯測試用例 mvn clean package -DskipTests", + "i18n_b37b786351":"分組名", + "i18n_b384470769":"同步快取", + "i18n_b38d6077d6":"登入IP", + "i18n_b38d7db9b0":"下載構建日誌,如果按鈕不可用表示日誌檔案不存在,一般是構建歷史相關檔案被刪除", + "i18n_b3913b9bb7":"請輸入構建環境變數:xx=abc 多個變數回車換行即可", + "i18n_b399058f25":"強大安全的密碼管理付費應用", + "i18n_b39909964f":"請輸入郵箱賬號", + "i18n_b3b1f709d4":"剔除", + "i18n_b3bda9bf9e":"請選擇工作空間", + "i18n_b3ef35a359":"源倉庫", + "i18n_b3f9beb536":":3~18分,每5分鐘執行一次,即0:03, 0:08, 0:13, 0:18, 1:03, 1:08……", + "i18n_b3fe677b5f":"失敗率", + "i18n_b408105d69":"密碼欄位和金鑰欄位在編輯的時候不會返回,如果需要重置或者清空就請點我", + "i18n_b437a4d41d":"也支援 URL 引數格式:test_par=123abc&test_par2=abc21", + "i18n_b44479d4b8":"可用標籤", + "i18n_b4750210ef":"叢集修改時間:", + "i18n_b499798ec5":"禁用分發節點", + "i18n_b4a8c78284":"選擇工作空間", + "i18n_b4c83b0b56":"倉庫賬號", + "i18n_b4dd6aefde":"會話已經關閉[script-console]", + "i18n_b4e2b132cf":"外掛端執行埠預設使用:", + "i18n_b4fc1ac02c":"取消構建", + "i18n_b4fd7afd31":"個性配置", + "i18n_b513f53eb4":"超時時間 單位秒", + "i18n_b515d55aab":"可以通過證書管理中提前上傳或者點選後面選擇證書去選擇/匯入證書", + "i18n_b53dedd3e0":"釋出前執行的命令(非阻塞命令),一般是關閉專案命令", + "i18n_b55f286cba":"載前請閱讀更新日誌裡面的說明和注意事項並且", + "i18n_b56585aa18":"配置後可以控制想要在某個時間段禁止使用者操作某些功能,優先判斷禁用時段", + "i18n_b57647c5aa":"真的要解綁指令碼關聯的節點麼?", + "i18n_b57ecea951":"已經執行時間:", + "i18n_b5a1e1f2d1":"觸發型別:", + "i18n_b5a6a07e48":"週二", + "i18n_b5b51ff786":"上傳 SQL 檔案", + "i18n_b5c291805e":"初始化系統", + "i18n_b5c3770699":"控制檯", + "i18n_b5c5078a5d":"所有的IPV4列表", + "i18n_b5ce5efa6e":"叢集服務", + "i18n_b5d0091ae3":"構建ID", + "i18n_b5d2cf4a76":"當目標工作空間已經存在 指令碼 時候將自動同步 指令碼內容、預設引數、自動執行、描述", + "i18n_b5fdd886b6":"全屏檢視日誌", + "i18n_b60352bc4f":"虛擬", + "i18n_b6076a055f":"登入失敗", + "i18n_b61a7e3ace":"指令碼名稱:", + "i18n_b63c057330":"真的要刪除操作監控麼?", + "i18n_b650acd50b":"恢復預設名稱", + "i18n_b6728e74a4":"執行目錄:", + "i18n_b6a828205d":"快取構建", + "i18n_b6afcf9851":"禁止命令是不允許在終端執行的命令,多個逗號隔開。(超級管理員沒有任何限制)", + "i18n_b6c9619081":"埠:", + "i18n_b6e8fb4106":"平臺登入", + "i18n_b6ee682dac":"外掛數:", + "i18n_b714160f52":"分發專案 ID", + "i18n_b71a7e6aab":"本地命令", + "i18n_b7579706a3":"校驗", + "i18n_b7c139ed75":"如果專案目錄較大或者涉及到深目錄,建議關閉掃描避免獲取專案目錄掃描過長影響效能", + "i18n_b7cfa07d78":"確認繫結", + "i18n_b7df1586a9":"當目標工作空間已經存在節點時候將自動同步 docker 倉庫配置資訊", + "i18n_b7ea5e506c":"系統資訊", + "i18n_b7ec1d09c4":"服務ID", + "i18n_b7f770d80b":"需要先安裝依賴 npm i && npm run build", + "i18n_b8545de30e":"請至少選擇 1 個節點", + "i18n_b85b213579":"發件人名稱", + "i18n_b86224e030":"節點狀態", + "i18n_b87c9acca3":"真的要強制退出叢集嗎?", + "i18n_b8915a4933":"真的關閉當前使用者的兩步驗證麼?", + "i18n_b8ac664d98":"勾選資料表", + "i18n_b90a30dd20":"此處不填不會修改密碼", + "i18n_b91961bf0b":"傳入引數有:projectId、projectName、type、result", + "i18n_b922323119":"映象標籤,如:key1=value1&key2=value2", + "i18n_b939d47e23":"公鑰", + "i18n_b953d1a8f1":"不能關閉了", + "i18n_b96b07e2bb":"僅修剪未使用和未標記的映象", + "i18n_b9a4098131":"觸發器地址", + "i18n_b9af769752":"映象名稱必填", + "i18n_b9b176e37a":"請選擇指令碼", + "i18n_b9bcb4d623":"外掛:", + "i18n_b9c1616fd5":"SSH方式連線的 docker 不建議用於容器構建(SSH 方式用於構建非常不穩定)", + "i18n_b9c4cf7483":" 全部替換", + "i18n_b9c52d9a85":"檔名:", + "i18n_ba17b17ba2":"沒有任何SSH指令碼命令", + "i18n_ba1f68b5dd":"這樣使得", + "i18n_ba20f0444c":"強制刪除", + "i18n_ba311d8a6a":"指令碼", + "i18n_ba3a679655":"以 yaml/yml 格式配置", + "i18n_ba452d57f2":"使用率最大的分割槽:", + "i18n_ba52103711":"剩餘 inode 數", + "i18n_ba619a0942":"預設構建錯誤將自動忽略隱藏檔案", + "i18n_ba6e91fa9e":"許可權", + "i18n_ba6ea3d480":"頁面全屏,高度 100%。區域性區域可以滾動", + "i18n_ba8d1dca4a":"注意:", + "i18n_baafe06808":"安全組規則", + "i18n_bab17dc6b1":"選擇程序名", + "i18n_baef58c283":"請輸入標籤名 字母數字 長度 1-10", + "i18n_baefd3db91":"授權可以直接訪問的目錄,多個回車換行即可", + "i18n_bb316d9acd":"下載速度根據網速來確定,如果網路不佳下載會較慢", + "i18n_bb4409015b":"機器 ssh 名", + "i18n_bb4740c7a7":"執行 命令", + "i18n_bb5aac6004":"構建產物同步到檔案中心保留天數", + "i18n_bb667fdb2a":"未報警", + "i18n_bb7eeae618":"僅統計:", + "i18n_bb8d265c7e":"版本需要大於 18", + "i18n_bb9a581f48":"登入成功,需要驗證 MFA", + "i18n_bb9ef827bf":"禁止訪問", + "i18n_bba360b084":"真的要刪除對應的觸發器嗎?", + "i18n_bbbaeb32fc":"機器延遲", + "i18n_bbcaac136c":"表中的錯誤資料嗎?", + "i18n_bbd63a893c":"自動檢測服務端所在伺服器中是否存在 docker,如果存在將自動新增到列表中", + "i18n_bbf2775521":"映象名稱", + "i18n_bc2c23b5d2":"修剪操作會刪除相關資料,請謹慎操作。請您再確認本操作後果後再使用", + "i18n_bc2f1beb44":"真的要解鎖使用者麼?", + "i18n_bc4b0fd88a":"網路 Reachable 測試", + "i18n_bc8752e529":"分發專案", + "i18n_bcaf69a038":"請選擇一個節點", + "i18n_bcc4f9e5ca":"如", + "i18n_bcf48bf7a8":"授權 url", + "i18n_bcf83722c4":"變數描述", + "i18n_bd0362bed3":"新增分組", + "i18n_bd49bc196c":"編輯專案", + "i18n_bd4e9d0ee2":"原始名:", + "i18n_bd5d9b3e93":"使用哪個 docker 構建,填寫 docker 標籤( 標籤在 docker 編輯頁面配置) 預設查詢可用的第一個,如果tag 查詢出多個將依次構建", + "i18n_bd6c436195":"請輸入指令碼描述", + "i18n_bd7043cae3":"遠端下載", + "i18n_bd7c8c96bc":"手動上傳", + "i18n_bda44edeb5":"不能操作", + "i18n_bdc1fdde6c":"beta計劃:", + "i18n_bdd4cddd22":"將還原【", + "i18n_bdd87b63a6":"微信二維碼", + "i18n_bdd9d38d7e":"列寬", + "i18n_be166de983":"軟鏈的專案", + "i18n_be1956b246":"深色2 blackboard", + "i18n_be2109e5b1":"確定要重置使用者密碼嗎?", + "i18n_be24e5ffbe":"Java 專案(java -jar xxx)", + "i18n_be28f10eb6":"請選擇釋出的一級目錄和填寫二級目錄", + "i18n_be381ac957":"請選擇要使用的倉庫", + "i18n_be3a4d368e":"分發中、2:分發結束、3:已取消、4:分發失敗", + "i18n_be4b9241ec":"預設狀態碼為 200 表示執行成功", + "i18n_be5b6463cf":"語法參考", + "i18n_be5fbbe34c":"儲存", + "i18n_beafc90157":"分發到機器節點中的指令碼庫在節點指令碼支援使用 G{'@'}(\"xxxx\")格式來引用,當存在引用時系統會自動替換引用指令碼庫中的指令碼內容", + "i18n_bebcd7388f":"載入構建資料中", + "i18n_bec98b4d6a":"狀態:", + "i18n_becc848a54":"私鑰檔案絕對路徑(絕對路徑前面新增 file", + "i18n_bef1065085":"Chrome 擴充套件", + "i18n_bf0e1e0c16":"輸入倉庫名稱或者倉庫路徑進行{slot1}", + "i18n_bf77165638":"您確定要重啟當前容器嗎?", + "i18n_bf7da0bf02":"新密碼", + "i18n_bf91239ad7":"命令描述", + "i18n_bf93517805":"下列配置資訊僅在當前瀏覽器生效", + "i18n_bf94b97d1a":"修改時間:", + "i18n_bfacfcd978":"將取消自動載入環境變數", + "i18n_bfc04cfda7":"分支", + "i18n_bfda12336c":"搜尋檢視", + "i18n_bfe68d5844":"連結", + "i18n_bfe8fab5cd":"需要配置授權目錄(授權才能正常使用釋出),授權目錄主要是用於確定可以釋出到哪些目錄中", + "i18n_bfed4943c5":"引數值", + "i18n_c00fb0217d":"請填寫使用者暱稱", + "i18n_c03465ca03":"禁用數量", + "i18n_c0996d0a94":" :每週一和週二的11:59執行", + "i18n_c0a9e33e29":"請選擇構建對應的分支,必選", + "i18n_c0d19bbfb3":"請輸入 key 的值", + "i18n_c0d38f475f":"軟記憶體", + "i18n_c0d5d68f5f":"忽略", + "i18n_c0e498a259":"點選圖示檢視關聯的所有任務", + "i18n_c0f4a31865":"邏輯刪除", + "i18n_c11eb9deff":"檔案MD5", + "i18n_c12ba6ff43":"我們有權利追訴破壞開源並因此獲利的團隊個人的全部違法所得,也歡迎給我們提供侵權線索。", + "i18n_c163613a0d":"如果當前叢集還存在可能出現資料不一致問題奧", + "i18n_c1690fcca5":"匯入證書", + "i18n_c16ab7c424":"】 嗎?注意:取消/停止構建不一定能正常關閉所有關聯程序", + "i18n_c1786d9e11":"節點地址", + "i18n_c17aefeebf":"系統名:", + "i18n_c18455fbe3":"授權資訊錯誤", + "i18n_c195df6308":"異常", + "i18n_c1af35d001":"構建產物", + "i18n_c1b72e7ded":"為變數名稱", + "i18n_c23fbf156b":"未選擇ssh", + "i18n_c26e6aaabb":"擅自修改或者刪除版權資訊有法律風險", + "i18n_c2add44a1d":"一些例子:", + "i18n_c2b2f87aca":"指令碼孤獨資料", + "i18n_c2ee58c247":"構建命令", + "i18n_c2f11fde3a":"初始化系統賬戶", + "i18n_c31ea1e3c4":"沒有任何操作日誌", + "i18n_c325ddecb1":"CPU 週期的長度,以微秒為單位。", + "i18n_c32e7adb20":"請輸入遠端下載安全HOST,回車支援輸入多個路徑,示例 https://www.test.com 等", + "i18n_c34175dbef":"控制檯日誌備份路徑: ", + "i18n_c3490e81bf":"重啟建立之前會自動將之前的容器刪除掉", + "i18n_c34f1dc2b9":"引數中的 id 、token 和觸發構建一致", + "i18n_c3512a3d09":"請選選擇型別", + "i18n_c35c1a1330":"排序值", + "i18n_c360e994db":"排序", + "i18n_c36ab9a223":"為 docker bridge 上的容器建立一個新的網路堆疊", + "i18n_c37ac7f024":"清除程式碼", + "i18n_c3aeddb10d":"當前工作空間還沒有邏輯節點不能建立節點分發奧", + "i18n_c3f28b34bb":"叢集名稱", + "i18n_c446efd80d":"編輯 Script", + "i18n_c4535759ee":"系統提示", + "i18n_c46938460b":"系統使用 docker http 介面實現和 docker 通訊和管理,但是預設", + "i18n_c469afafe0":"您確定要刪除當前容器嗎?", + "i18n_c494fbec77":"手動新增", + "i18n_c4a61acace":"命令?", + "i18n_c4b5d36ff0":"節點狀態會自動識別伺服器中是否存在 java 環境,如果沒有 Java 環境不能快速安裝節點", + "i18n_c4cfe11e54":"如果上傳的壓縮檔案是否自動解壓 支援的壓縮包型別有 tar", + "i18n_c4e0c6b6fe":"篩選專案", + "i18n_c4e2cd2266":"還需要對相關資料都操作後才能達到預期排序", + "i18n_c5099cadcd":"外掛數", + "i18n_c53021f06d":"填寫【xxx", + "i18n_c530a094f9":"構建方式:", + "i18n_c538b1db4a":"軟鏈專案(類似於專案副本使用相關路徑的檔案)", + "i18n_c583b707ba":"個引數的描述", + "i18n_c5a2c23d89":"非全屏", + "i18n_c5aae76124":"繫結 SSH", + "i18n_c5bbaed670":"狀態碼錯誤", + "i18n_c5c3583bfc":"傳送驗證碼", + "i18n_c5c69827c5":"等網路埠限制", + "i18n_c5de93f9c7":"需要手動確認", + "i18n_c5e7257212":"當前節點ID:", + "i18n_c5f9a96133":"系統預設將對 demo 賬號限制很多許可權。非演示場景不建議使用 demo 賬號", + "i18n_c600eda869":"命令檔案將在 {'${資料目錄}'}/script/xxxx.sh、bat 執行", + "i18n_c618659cea":"自定義分支通配表示式", + "i18n_c6209653e4":"SMTP 伺服器域名", + "i18n_c68dc88c51":"請輸入監控名稱", + "i18n_c6a3ebf3c4":"接收大小", + "i18n_c6e4cddba0":"請選擇監控的功能", + "i18n_c6f1c6e062":"目錄不能編輯", + "i18n_c6f6a9b234":"遷移到其他工作空間", + "i18n_c704d971d6":"請填寫構建和產物", + "i18n_c7099dabf6":"正在上傳檔案", + "i18n_c71a67ab03":"分發後", + "i18n_c75b14a04e":"監控頻率可以到服務端配置檔案中修改", + "i18n_c75d0beca8":"開啟頁面操作引導、導航", + "i18n_c7689f4c9a":"這裡構建命令最終會在伺服器上執行。如果有多行命令那麼將", + "i18n_c76cfefe72":"埠", + "i18n_c7c4e4632f":",更新期間允許的失敗率", + "i18n_c7e0803a17":"密碼只會出現一次,關閉視窗後無法再次檢視密碼", + "i18n_c806d0fa38":"壓縮包", + "i18n_c83752739f":"支援的欄位可以通過介面返回的檢視", + "i18n_c840c88b7c":"真的要清空 【", + "i18n_c84ddfe8a6":"執行日誌", + "i18n_c8633b4b77":"通過私人令牌匯入倉庫", + "i18n_c87bd94cd7":"映象Id: ", + "i18n_c889b9f67d":"新增關聯專案", + "i18n_c89e9681c7":"臨時檔案佔用空間", + "i18n_c8a2447aa9":"加入", + "i18n_c8b2aabc07":"檔案中如果不存在:MemAvailable,那麼 MemAvailable = MemFree+Active(file)+Inactive(file)+SReclaimable,所以本系統 中記憶體佔用計算方式:記憶體佔用=(total-(MemFree+Active(file)+Inactive(file)+SReclaimable))/total", + "i18n_c8c452749e":"選擇 SQL 檔案", + "i18n_c8c45e8467":"請根據自身專案啟動時間來配置", + "i18n_c8c6e37071":"溫馨提醒", + "i18n_c8ce4b36cb":"重新命名", + "i18n_c90a1f37ce":"節點id", + "i18n_c96b442dfb":"通用郵箱 SSL", + "i18n_c96f47ec1b":"非同步請求不能保證有序性", + "i18n_c972010694":"產物目錄", + "i18n_c9744f45e7":"否", + "i18n_c97e6e823a":"重新啟動容器,除非它已被停止", + "i18n_c983743f56":"總記憶體", + "i18n_c996a472f7":"每天0/12點重新整理一次", + "i18n_c99a2f7ed8":"啟動命令", + "i18n_c9b0f8e8c8":"真的要刪除", + "i18n_c9b79a2b4f":"全域性代理配置後將對服務端的網路生效,代理實現方式:ProxySelector", + "i18n_c9daf4ad6b":"多執行緒", + "i18n_ca32cdfd59":"記憶體使用率:", + "i18n_ca69dad8fc":"流程執行完指令碼後,輸出的內容最後一行必須為:running", + "i18n_ca774ec5b4":"上次構建基於 commitId:", + "i18n_caa9b5cd94":"需要將標籤值配置到構建 DSL 中的", + "i18n_cab7517cb4":"節點地址:", + "i18n_cabdf0cd45":"選擇產物", + "i18n_cac26240b5":"容器日誌", + "i18n_cac6ff1d82":"批量獲取構建狀態地址", + "i18n_cad01fe13c":"黑白 ambiance-mobile", + "i18n_caf335a345":"java資訊", + "i18n_cb09b98416":"個性配置區", + "i18n_cb156269db":"單位秒,最小值 1 秒", + "i18n_cb25f04b46":"在檔案最後 3 行中搜尋", + "i18n_cb28aee4f0":"節點分發【暫不支援遷移】", + "i18n_cb46672712":"日誌閱讀 【暫不支援遷移】", + "i18n_cb93a1f4a5":"、root、manager", + "i18n_cb951984f2":"已存在", + "i18n_cb9b3ec760":"批量觸發引數 BODY json: [ { \"id\":\"1\", \"token\":\"a\",\"action\":\"status\" } ]", + "i18n_cbc44b5663":"執行時間結束", + "i18n_cbcc87b3d4":"、名稱、版權等", + "i18n_cbce8e96cf":"證書資訊", + "i18n_cbdc4f58f6":"請輸入機器的名稱", + "i18n_cbdcabad50":"釋放", + "i18n_cbee7333e4":"本地命令是指,在服務端本地執行多條命令來實現釋出", + "i18n_cc3a8457ea":"節點狀態是非同步獲取有一定時間延遲", + "i18n_cc42dd3170":"開啟", + "i18n_cc51f34aa4":"編輯服務", + "i18n_cc5dccd757":"工作空間中邏輯節點中指令碼模版數量:", + "i18n_cc617428f7":"隱私變數是指一些密碼欄位或者關鍵金鑰等重要資訊,隱私欄位只能修改不能檢視(編輯彈窗中無法看到對應值)。 隱私欄位一旦建立後將不能切換為非隱私欄位", + "i18n_cc637e17a0":"產物:", + "i18n_cc92cf1e25":"請填寫產物目錄", + "i18n_cc9a708364":"狀態碼錯誤 ", + "i18n_cca4454cf8":"請輸入公告內容", + "i18n_ccb2fdd838":"切換工作空間", + "i18n_ccb91317c5":"命令內容", + "i18n_ccea973fc7":"當前節點地址:", + "i18n_cd1aedc667":"在 設定 --> 應用 --> 生成令牌", + "i18n_cd649f76d4":"時間範圍", + "i18n_cd998f12fa":"當目標工作空間已經存在節點時候將自動同步節點授權資訊、代理配置資訊", + "i18n_cda84be2f6":"操作日誌", + "i18n_cdc478d90c":"系統名", + "i18n_ce043fac7d":"當前工作空間還沒有SSH", + "i18n_ce07501354":"點選數字檢視執行中的任務", + "i18n_ce1ecd8a5b":"還有更多相關依賴開源元件", + "i18n_ce23a42b47":"任務名", + "i18n_ce40cd6390":"關聯指令碼", + "i18n_ce559ba296":"執行命令", + "i18n_ce7e6e0ea9":"當前配置僅對選擇的工作空間生效,其他工作空間需要另行配置", + "i18n_ce84c416f9":"請輸入使用者資訊 url [userInfoUri]", + "i18n_ced3d28cd1":"不掃描", + "i18n_ceee1db95a":"容器埠", + "i18n_ceffe5d643":"兩步驗證應用", + "i18n_cf38e8f9fd":"當前為節點分發的授權路徑配置", + "i18n_cfa72dd73a":"請輸入要檢查的 cron 表示式", + "i18n_cfb00269fd":"執行指令碼", + "i18n_cfbb3341d5":"當前登入的賬號是:", + "i18n_cfd482e5ef":"暫無資料,請先新增節點專案資料", + "i18n_cfeea27648":"建立檔案 /xxx/xxx/xxx", + "i18n_cfeff30d2c":"目錄:", + "i18n_d00b485b26":"回滾", + "i18n_d0132b0170":"還原過程中不能操作哦...", + "i18n_d02a9a85df":"請選擇報警聯絡人", + "i18n_d047d84986":"個 / 共", + "i18n_d0874922f0":"繫結指定目錄可以線上管理,同時構建 ssh 釋出目錄也需要在此配置", + "i18n_d0a864909b":"方式生成公私鑰", + "i18n_d0b2958432":"版本號", + "i18n_d0b7462bdc":"此編輯僅能編輯當前 SSH 在此工作空間的名稱資訊", + "i18n_d0be2fcd05":":表示間隔時間,例如在分上,表示每兩分鐘,同樣*可以使用數字列表代替,逗號分隔", + "i18n_d0c06a0df1":"請輸入驗證碼", + "i18n_d0c879f900":"上傳前", + "i18n_d0eddb45e2":"私鑰", + "i18n_d0f53484dc":"指令碼ID:", + "i18n_d1498d9dbf":"構建備註", + "i18n_d159466d0a":"關聯資料名", + "i18n_d175a854a6":"構建目錄", + "i18n_d17eac5b5e":"我要加入", + "i18n_d18d658415":"指令碼ID", + "i18n_d19bae9fe0":"外掛端安裝並啟動成功後將主動上報節點資訊,如果上報的 IP+PROP 能正常通訊將新增節點資訊", + "i18n_d1aa9c2da9":"子網掩碼:", + "i18n_d1b8eaaa9e":"需要輸入驗證碼,確認繫結後才生效奧", + "i18n_d1f0fac71d":"沒有選擇節點不能儲存指令碼", + "i18n_d1f56b0a7e":"傳入引數有:monitorId、monitorName、nodeId、nodeName、projectId、projectName、title、content、runStatus", + "i18n_d263a9207f":"支援 html 格式", + "i18n_d27cf91998":"參考地址:", + "i18n_d2cac1245d":"是否將【", + "i18n_d2e2560089":"檔名稱", + "i18n_d2f484ff7e":"。如果輸出最後一行不是預期格式專案狀態將是未執行", + "i18n_d2f4a1550a":"沒有任何節點指令碼", + "i18n_d301fdfc20":"開啟自動建立使用者後第一次登入僅自動建立賬號,還需要管理員手動分配許可權組", + "i18n_d30b8b0e43":"未構建", + "i18n_d31d625029":"加入 beta 計劃可以及時獲取到最新的功能、一些優化功能、最快修復 bug 的版本,但是 beta 版也可能在部分新功能上存在不穩定的情況。", + "i18n_d324f8b5c9":" 替換", + "i18n_d35a9990f4":"已無更多節點專案,請先建立專案", + "i18n_d373338541":"支援IP 地址、域名、主機名", + "i18n_d3ded43cee":"檢視構建日誌", + "i18n_d3e480c8c0":"失敗次數", + "i18n_d3fb6a7c83":",2 秒後將自動跳轉到登入頁面", + "i18n_d40b511510":"證書", + "i18n_d438e83c16":"專案分組", + "i18n_d4744ce461":"還沒有配置許可權組,不能建立使用者", + "i18n_d47ea92b3a":"編輯證書", + "i18n_d4aea8d7e6":"執行次數", + "i18n_d4e03f60a9":"外掛端啟動的時候檢查專案狀態,如果專案狀態是未執行則嘗試執行啟動專案", + "i18n_d5269713c7":"表示構建產物為資料夾時將打包為", + "i18n_d57796d6ac":" :範圍:0~59", + "i18n_d584e1493b":"搜尋ssh名稱", + "i18n_d58a55bcee":"關", + "i18n_d5a73b0c7f":"上傳", + "i18n_d5c2351c0e":"請選擇可以執行的星期", + "i18n_d5c68a926e":"執行順序", + "i18n_d5d46dd79b":"分鐘級別", + "i18n_d615ea8e30":"選擇升級檔案", + "i18n_d61af4e686":"斷點/分片別名下載", + "i18n_d61b8fde35":"切換叢集", + "i18n_d64cf79bd4":"確認要加入 beta 計劃嗎?", + "i18n_d65d977f1d":"填寫執行引數", + "i18n_d679aea3aa":"執行中", + "i18n_d6937acda5":"專案儲存的資料夾,jar 包存放的資料夾", + "i18n_d6a5b67779":"系統程序", + "i18n_d6cdafe552":"會話已經關閉[project-console]", + "i18n_d6eab4107a":"Java 專案(java -jar Springboot war)【不推薦】", + "i18n_d72471c540":"瀏覽器標識", + "i18n_d731dc9325":"時間戳:", + "i18n_d7471c0261":"請選擇執行節點", + "i18n_d75c02d050":"停止專案", + "i18n_d7ac764d3a":"分發間隔時間 (順序重啟、完整順序重啟)方式才生效", + "i18n_d7ba18c360":"分發節點是指在編輯完指令碼後自動將指令碼內容同步節點的指令碼中", + "i18n_d7bebd0e5e":"狀態操作請到控制檯中控制", + "i18n_d7c077c6f6":"命令日誌", + "i18n_d7cc44bc02":"使用者資料", + "i18n_d7d11654a7":"不存在", + "i18n_d7ec2d3fea":"名稱", + "i18n_d7ee59f327":"私鑰,不填將使用預設的 $HOME/.ssh 目錄中的配置。支援配置檔案目錄:file:", + "i18n_d7ef19d05b":"還沒有配置模板節點", + "i18n_d81bb206a8":"無", + "i18n_d82ab35b27":"檔案前N行", + "i18n_d82b19148f":"請選擇要同步授權的機器節點", + "i18n_d83aae15b5":"線上構建檔案主要儲存,倉庫檔案,構建歷史產物等。不支援主動清除,如果檔案佔用過大可以配置保留規則和對單個構建配置是否儲存倉庫、產物檔案等", + "i18n_d84323ba8d":"倉庫自動遷移後可能會重複存在請手動解決", + "i18n_d87940854f":"計劃次數", + "i18n_d87f215d9a":"卡片", + "i18n_d88651584f":"剩餘空間", + "i18n_d8a36a8a25":"編輯 Docker 叢集", + "i18n_d8bf90b42b":"其他使用者可以配置許可權解除限制", + "i18n_d8c7e04c8e":"資訊", + "i18n_d8db440b83":"您需要根據您業務情況來評估是否可以加入 beta。", + "i18n_d911cffcd5":"下載地址", + "i18n_d921c4a0b6":"真的要刪除“", + "i18n_d926e2f58e":"取消任務", + "i18n_d937a135b9":"淺色 eclipse", + "i18n_d94167ab19":"網路埠", + "i18n_d9435aa802":"解析模式", + "i18n_d9531a5ac3":"還沒有配置許可權組不能建立使用者奧", + "i18n_d9569a5d3b":"多IP可以使用", + "i18n_d9657e2b5f":"請輸入專案資料夾", + "i18n_d9ac9228e8":"建立", + "i18n_d9c28e376c":"功能管理", + "i18n_da1abf0865":"驗證碼 6 為純數字", + "i18n_da1cb76e87":"請輸入指令碼內容", + "i18n_da317c3682":"【推薦】使用快速安裝方式匯入機器並自動新增邏輯節點", + "i18n_da4495b1b4":"郵箱:", + "i18n_da509a213f":"工作空間用於隔離資料,工作空間下面可以有不同資料,不同許可權,不同選單等來實現許可權控制", + "i18n_da671a4d16":"微信:", + "i18n_da79c2ec32":"配置示例", + "i18n_da89135649":"企業服務", + "i18n_da8cb77838":"線上升級", + "i18n_da99dbfe1f":"分發狀態", + "i18n_dab864ab72":"快速繫結", + "i18n_dabdc368f5":"請選擇分配型別", + "i18n_dacc2e0e62":"硬體硬碟", + "i18n_dadd4907c2":"】目錄,", + "i18n_daf783c8cd":"分", + "i18n_db06c78d1e":"測試", + "i18n_db094db837":"修改變數值地址", + "i18n_db2d99ed33":"還未配置叢集地址,不能切換叢集", + "i18n_db4470d98d":"報警狀態", + "i18n_db4b998fbd":"Oauth2 使用提示", + "i18n_db5cafdc67":"真的要解綁節點麼?", + "i18n_db686f0328":"公鑰,不填將使用預設的 $HOME/.ssh 目錄中的配置。支援配置檔案目錄:file:", + "i18n_db709d591b":"不同步", + "i18n_db732ecb48":"延遲", + "i18n_db81a464ba":"執行構建時會生成一個容器來執行,構建結束後會自動刪除對應的容器", + "i18n_db9296212a":"定時構建", + "i18n_dba16b1b92":"構建事件", + "i18n_dbad1e89f7":"兩步驗證", + "i18n_dbb166cf29":"服務id", + "i18n_dbb2df00cf":"釋出目錄", + "i18n_dbba7e107a":"釋出專案", + "i18n_dbc0b66ca4":"個結果", + "i18n_dc0d06f9c7":"請填寫釋出的二級目錄", + "i18n_dc2961a26f":"節點名:", + "i18n_dc2c61a605":"自建 Gitlab", + "i18n_dc32f465da":"容器地址 tcp", + "i18n_dc3356300f":"如果要配置 SSH 請到【系統管理】-> 【資產管理】-> 【SSH 管理】中去配置。", + "i18n_dc39b183ea":"確定要忽略繫結兩步驗證嗎?強烈建議超級管理員開啟兩步驗證來保證賬號安全性", + "i18n_dcc846e420":"批量構建全部引數舉例", + "i18n_dcd72e6014":"獲取單個構建狀態地址", + "i18n_dce5379cb9":"隱藏", + "i18n_dcf14deb0e":"代理地址 (127.0.0.1:8888)", + "i18n_dd1d14efd6":"檢視指令碼", + "i18n_dd23fdf796":"編輯過程中可以切換節點但是要注意資料是否匹配", + "i18n_dd4e55c39c":"未開始", + "i18n_dd95bf2d45":"正常登入", + "i18n_ddc7d28b7b":"變數", + "i18n_dddf944f5f":"重置頁面操作引導、導航成功", + "i18n_ddf0c97bce":"請注意備份資料防止資料丟失!!", + "i18n_ddf7d2a5ce":"命令", + "i18n_de17fc0b78":"硬碟最大的使用率:", + "i18n_de3394b14e":"沒有資產機器", + "i18n_de4cf8bdfa":"付費加入我們的技術交流群優先解答您所有疑問", + "i18n_de5dadc480":"pull日誌", + "i18n_de6bc95d3b":"清空當前目錄檔案", + "i18n_de78b73dab":"單個觸發器地址", + "i18n_debdfce084":"請輸入叢集名稱", + "i18n_decef97c7c":"服務端IP授權配置", + "i18n_deea5221aa":"標記", + "i18n_df011658c3":"範圍", + "i18n_df1da2dc59":"容器在一個 CPU 週期內可以獲得的 CPU 時間的微秒。", + "i18n_df3833270b":"地址:", + "i18n_df39e42127":"自動執行", + "i18n_df59a2804d":"禁止掃描", + "i18n_df5f80946d":"# node 映象源 https://registry.npmmirror.com/-/binary/node/", + "i18n_df9497ea98":"存在", + "i18n_df9d1fedc5":"節點分發是指,一個專案部署在多個節點中使用節點分發一步完成多個節點中的專案釋出操作", + "i18n_dfb8d511c7":"使用者名稱稱", + "i18n_dfcc9e3c45":"分發後操作", + "i18n_e039ffccc8":"】此檔案還原到專案目錄?", + "i18n_e049546ff3":"【系統配置目錄】中修改", + "i18n_e06497b0fb":"檢視當前可用容器", + "i18n_e06caa0060":"檔案修改時間", + "i18n_e074f6b6af":"SMTP 伺服器埠", + "i18n_e07cbb381c":"沒有任何倉庫", + "i18n_e09d0d8c41":"不建議使用常用名稱如", + "i18n_e0a0e26031":"使用當前映象建立一個容器", + "i18n_e0ae638e73":"保留產物是指對在構建完成後是否保留構建產物相關檔案,用於回滾", + "i18n_e0ba3b9145":"節點指令碼", + "i18n_e0ce74fcac":"自動滾動", + "i18n_e0d6976b48":"請選擇叢集關聯分組", + "i18n_e0ea800e34":"打包正式環境 npm i && npm run build:prod", + "i18n_e0ec07be7d":"客戶端金鑰", + "i18n_e0f937d57f":"臨時token", + "i18n_e0fcbca309":"工作空間ID:", + "i18n_e15f22df2d":"真的要清除構建資訊麼?", + "i18n_e166aa424d":"關於系統", + "i18n_e17a6882b6":"指令碼標記", + "i18n_e19cc5ed70":"同步節點授權", + "i18n_e1c965efff":"請選擇狀態", + "i18n_e1fefde80f":"節點賬號密碼預設由系統生成:可以通過外掛端資料目錄下 agent_authorize.json 檔案檢視(如果自定義配置了賬號密碼將沒有此檔案)", + "i18n_e222f4b9ad":"執行前需要檢查命令中的地址在對應的伺服器中是否可以訪問,如果無法訪問將不能自動繫結節點,", + "i18n_e257dd2607":"請選擇SSH連線資訊", + "i18n_e26dcacfb1":" 檢查 ", + "i18n_e2adcc679a":"單位 ns 秒", + "i18n_e2b0f27424":"專案ID僅會在機器節點中限制唯一,不同工作空間(相同的工作空間)下是允許相同的專案ID", + "i18n_e2be9bab6b":"複製下面任意一條命令到還未安裝外掛端的伺服器中去執行,執行前需要放行", + "i18n_e2f942759e":"會話異常", + "i18n_e30a93415b":"使用私人令牌,可以在你不輸入賬號密碼的情況下對你賬號內的倉庫進行管理,你可以在建立令牌時指定令牌所擁有的許可權。", + "i18n_e319a2a526":"重置為重新生成觸發地址,重置成功後之前的觸發器地址將失效,構建觸發器繫結到生成觸發器到操作人上,如果將對應的賬號刪除觸發器將失效", + "i18n_e31ca72849":"上傳壓縮檔案", + "i18n_e354969500":"令牌匯入", + "i18n_e362bc0e8a":"路徑:{source}(宿主機) => {destination}(容器)", + "i18n_e39de3376e":"分配", + "i18n_e39f4a69f4":"指令碼數", + "i18n_e39ffe99e9":"請輸入密碼", + "i18n_e3cf0abd35":"郵箱驗證碼", + "i18n_e3e85de50c":"請選擇構建方式", + "i18n_e3ead2bd0d":"常見構建命令示例", + "i18n_e3ee3ca673":"不追加指令碼模板", + "i18n_e4013f8b81":"機器名稱", + "i18n_e414392917":"叢集資訊:", + "i18n_e42b99d599":"月", + "i18n_e43359ca06":"請選擇 SSH節點", + "i18n_e44f59f2d9":"釋出前命令", + "i18n_e475e0c655":"證書共享", + "i18n_e48a715738":"新建檔案", + "i18n_e4b51d5cd0":"執行狀態", + "i18n_e4bea943de":"倉庫地址", + "i18n_e4bf491a0d":"正在下載,請稍等...", + "i18n_e4d0ebcd58":"選擇叢集", + "i18n_e5098786d3":"args 引數", + "i18n_e54029e15b":"退出叢集", + "i18n_e54c5ecb54":"編輯構建", + "i18n_e5915f5dbb":"(存在相容問題,實際使用中需要提前測試) python3 sdk 映象使用:https://repo.huaweicloud.com/python/{'${PYTHON3_VERSION}'}/Python-{'${PYTHON3_VERSION}'}.tar.xz", + "i18n_e5a63852fd":"節點密碼,請檢視節點啟動輸出的資訊", + "i18n_e5ae5b36db":"關鍵詞高亮,支援正則(正則可能影響效能請酌情使用)", + "i18n_e5f71fc31e":"搜尋", + "i18n_e60389f6d6":"當前前端打包時間:", + "i18n_e60725e762":"週三", + "i18n_e63fb95deb":"佇列數", + "i18n_e64d788d11":"升級成功", + "i18n_e6551a2295":"引用工作空間環境變數可以方便後面多處使用相同的密碼統一修改", + "i18n_e6bf31e8e6":"長期token", + "i18n_e6cde5a4bc":"沒有檢查到最新版", + "i18n_e6e453d730":"請輸入變數名稱", + "i18n_e6e5f26c69":"QQ郵箱 SSL", + "i18n_e703c7367c":"當前狀態:", + "i18n_e710da3487":"用時", + "i18n_e72a0ba45a":"使用者組", + "i18n_e72f2b8806":"輸入倉庫名稱或者倉庫路徑進行搜尋", + "i18n_e747635151":"Script 名稱", + "i18n_e76e6a13dd":"不引用環境變數", + "i18n_e78e4b2dc4":"級別", + "i18n_e7d83a24ba":"成功次數", + "i18n_e7e8d4c1fb":"斷點/分片下載", + "i18n_e7ffc33d05":"上傳後執行", + "i18n_e8073b3843":"請選擇使用者許可權組", + "i18n_e825ec7800":"協議型別", + "i18n_e8321f5a61":"釋出方式:", + "i18n_e83a256e4f":"確認", + "i18n_e84b981eb4":"配置值 (如:5g)", + "i18n_e8505e27f4":"升級前請閱讀更新日誌裡面的說明和注意事項並且", + "i18n_e8e3bfbbfe":"確認關閉", + "i18n_e8f07c2186":"如果未填寫將解析壓縮包裡面的 txt", + "i18n_e9290eaaae":"關閉左側", + "i18n_e930e7890f":"表示式類似於Linux的crontab表示式,表示式使用空格分成5個部分,按順序依次為:", + "i18n_e95f9f6b6e":"SSL 連線", + "i18n_e96705ead1":"如果按鈕不可用則表示當前節點已經關閉啦,需要去編輯中啟用", + "i18n_e976b537f1":"快取監控", + "i18n_e97a16a6d7":" :每兩分鐘執行", + "i18n_e9bd4484a7":"傳送方郵箱賬號", + "i18n_e9c2cb1326":"次要ID", + "i18n_e9e9373c6f":"執行任務中", + "i18n_e9ea1e7c02":"檔案儲存天數,預設 3650 天", + "i18n_e9ec2b0bee":"並等待上一個專案啟動完成才能關閉下一個專案", + "i18n_e9f2c62e54":",新增預設引數後在手動執行指令碼時需要填寫引數值", + "i18n_ea15ae2b7f":"選項", + "i18n_ea3c5c0d25":"臨時檔案佔用空間:", + "i18n_ea58a20cda":"機器DOCKER", + "i18n_ea7fbabfa1":"請輸入賬戶名", + "i18n_ea89a319ec":"# 宿主機目錄和容器目錄掛載 /host:/container:ro", + "i18n_ea8a79546f":"請輸入釋出的檔案id", + "i18n_ea9f824647":"拉取倉庫超時時間,單位秒", + "i18n_eaa5d7cb9b":"過期天數", + "i18n_eadd05ba6a":"中等", + "i18n_eaf987eea0":"權重(相對權重)。", + "i18n_eb164b696d":"排除釋出", + "i18n_eb5bab1c31":"非必填", + "i18n_eb79cea638":"週五", + "i18n_eb7f9ceb71":"指令碼庫:", + "i18n_eb969648aa":"請提前備份資料再操作奧", + "i18n_ebc2a1956b":"編輯監控", + "i18n_ebc96f0a5d":"總記憶體(記憶體 + 交換)。 設定為 -1 以禁用交換。", + "i18n_ec1f13ff6d":"總數:", + "i18n_ec219f99ee":"執行結束", + "i18n_ec22193ed1":"請選擇分組", + "i18n_ec537c957a":"{slot1}機目錄", + "i18n_ec6e39a177":"確認要下載更新最新版本嗎?", + "i18n_ec7ef29bdf":"請輸入靜態,回車支援輸入多個路徑,系統會自動過濾 ../ 路徑、不允許輸入根路徑", + "i18n_ec989813ed":"狀態資訊:", + "i18n_eca37cb072":"建立時間", + "i18n_ecdf9093d0":"同時展開多個", + "i18n_ecff77a8d4":"使用", + "i18n_ed145eba38":"硬碟佔用", + "i18n_ed19a6eb6f":"線上構建檔案佔用空間", + "i18n_ed367abd1a":"修改使用者資料", + "i18n_ed39deafd8":"編輯倉庫", + "i18n_ed40308fe9":"# maven 映象源 https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/", + "i18n_ed6a8ee039":" :表示多個定時表示式", + "i18n_ed8ea20fe6":"安裝ID", + "i18n_edb4275dcd":"gmail郵箱", + "i18n_edb881412a":"注意:為了避免不必要的事件執行指令碼,選擇的指令碼的備註中包含需要實現的事件引數關鍵詞,如果需要執行 success 事件,那麼選擇的指令碼的備註中需要包含 success 關鍵詞", + "i18n_edc1185b8e":"嘗試自動續簽失敗", + "i18n_edd716f524":"請選擇釋出的一級目錄", + "i18n_ede2c450d1":"沒有任何登入日誌", + "i18n_ee19907fad":"# 基礎映象 目前僅支援 ubuntu-latest", + "i18n_ee4fac2f3c":"為了避免部分節點不能及時響應造成監控阻塞,節點統計超時時間不受節點超時配置影響將採用預設超時時間(10秒)", + "i18n_ee6ce96abb":"s 秒", + "i18n_ee8ecb9ee0":"優先順序", + "i18n_ee9a51488f":"請輸入釋出目錄", + "i18n_eeb6908870":"上一步", + "i18n_eec342f34e":"預設賬號為:jpomAgent", + "i18n_eee6510292":"配置授權目錄", + "i18n_eee83a9211":"資源", + "i18n_eeef8ced69":"解綁會檢查資料關聯性,同時將自動刪除節點專案和指令碼快取資訊", + "i18n_eef3653e9a":"jvm{slot1},{slot2}.如:-Xms512m -Xmx512m", + "i18n_eef4dfe786":"Java 專案(java -Djava.ext.dirs=lib -cp conf:run.jar $MAIN_CLASS)", + "i18n_ef016ab402":"確認建立該", + "i18n_ef28d3bff2":"頁面內容自動撐開出現螢幕滾動條", + "i18n_ef651d15b0":"建立之後不能修改,分發 ID 等同於專案 ID", + "i18n_ef734bf850":"更多說明", + "i18n_ef7e3377a0":"允許時段", + "i18n_ef800ed466":"程式執行的 main 類(jar 模式執行可以不填)", + "i18n_ef8525efce":"分配到其他工作空間", + "i18n_ef9c90d393":"沒有任何的指令碼庫", + "i18n_efae7764ac":"賬號登入", + "i18n_efafd0cbd4":"密碼(6-18位數字、字母、符號組合)", + "i18n_efb88b3927":"系統執行時間", + "i18n_efd32e870d":"外掛構建時間", + "i18n_efe71e9bec":"真的要解綁當前節點分發麼?", + "i18n_efe9d26148":"真的要刪除該證書麼,刪除會將證書檔案一併刪除奧?", + "i18n_f038f48ce5":"編輯指令碼", + "i18n_f04a289502":"svn ssh 必填登入使用者", + "i18n_f05e3ec44d":"禁止訪問,當前IP限制訪問", + "i18n_f06f95f8e6":"孤獨資料", + "i18n_f087eb347c":"構建命令示例", + "i18n_f08afd1f82":"已選擇", + "i18n_f0a1428f65":"賬號支援引用工作空間變數:", + "i18n_f0aba63ae7":"頒發者", + "i18n_f0db5d58cb":"開啟兩步驗證使賬號更安全", + "i18n_f0eb685a84":"檔案來相容您的機器", + "i18n_f105c1d31d":"最後執行人", + "i18n_f113c10ade":"排空", + "i18n_f139c5cf32":"輸入新名稱", + "i18n_f175274df0":"登入名稱,賬號,建立之後不能修改", + "i18n_f1a2a46f52":"# 使用哪個 docker 構建,填寫 docker 標籤 預設查詢可用的第一個,如果 tag 查詢出多個也選擇第一個結果", + "i18n_f1b2828c75":"安裝外掛端", + "i18n_f1d8533c7f":"請輸入證書資訊或者選擇證書資訊,證書資訊填寫規則:序列號:證書型別", + "i18n_f1fdaffdf0":"後臺構建", + "i18n_f240f9d69c":"分支名稱:", + "i18n_f26225bde6":"詳情", + "i18n_f26ef91424":"下載", + "i18n_f282058f75":"靜態檔案專案(前端、日誌等)", + "i18n_f2d05944ad":"建立 Docker 叢集", + "i18n_f30f1859ba":"如果您基於 Jpom 二次開發修改了", + "i18n_f332f2c8df":"閘道器", + "i18n_f3365fbf4d":"未獲取到 Docker 或者禁用監控", + "i18n_f33db5e0b2":"點選重新整理構建資訊", + "i18n_f37f8407ec":"檔案ID:", + "i18n_f3947e6581":"開源不等同於免費", + "i18n_f3e93355ee":"重啟專案", + "i18n_f425f59044":"系統版本:", + "i18n_f49dfdace4":"許可權組", + "i18n_f4b7c18635":"密碼長度為6-20", + "i18n_f4baf7c6c0":"未啟動", + "i18n_f4bbbaf882":"分支/標籤", + "i18n_f4dd45fca9":"請輸入遠端地址", + "i18n_f4edba3c9d":"未知的表格型別", + "i18n_f4fb0cbecf":"還沒有任何結果", + "i18n_f5399c620e":"真的要在該叢集剔除此節點麼?", + "i18n_f562f75c64":"服務地址", + "i18n_f56c1d014e":"執行成功", + "i18n_f5c3795be5":"官方", + "i18n_f5d0b69533":"完整的私鑰內容 如", + "i18n_f5d14ee3f8":"磁碟佔用", + "i18n_f5f65044ea":"容器安裝的服務端不能使用本地構建(因為本地構建依賴啟動服務端本地的環境,容器方式安裝不便於管理本地依賴外掛)", + "i18n_f63345630c":"# 將容器中的 node_modules 檔案快取到 docker 卷中", + "i18n_f63870fdb0":"請填寫容器名稱", + "i18n_f652d8cca7":"嘗試自動續簽...", + "i18n_f66335b5bf":"錯誤資訊:", + "i18n_f66847edb4":"網頁應用ID", + "i18n_f668c8c881":"叢集名稱:", + "i18n_f685377a22":"指令碼庫 ", + "i18n_f68f9b1d1b":"最後心跳時間", + "i18n_f6d6ab219d":"更新完成後確實成功的時間", + "i18n_f6d96c1c8c":"為了相容Quartz表示式,同時支援6位和7位表示式,其中:", + "i18n_f6dee0f3ff":"分發 ID", + "i18n_f712d3d040":"備註示例:", + "i18n_f71316d0dd":"替換引用", + "i18n_f71a30c1b9":"資料目錄佔用空間", + "i18n_f7596f3159":"如果需要在其他工作空間需要提前切換生成命令", + "i18n_f76540a92e":"準備中", + "i18n_f782779e8b":"結束時間", + "i18n_f7b9764f0f":"專案啟動,停止,重啟都將請求對應的地址", + "i18n_f7e8d887d6":"工作空間環境變數", + "i18n_f7f340d946":"真的要清除 SSH 隱藏欄位資訊麼?(密碼,私鑰)", + "i18n_f8460626f0":"節點賬號,請檢視節點啟動輸出的資訊", + "i18n_f86324a429":"使用 ANT 表示式來實現在過濾指定目錄來實現釋出排除指定目錄", + "i18n_f89cc4807e":"授權路徑是指專案檔案存放到服務中的資料夾", + "i18n_f89fa9b6c6":"選擇倉庫", + "i18n_f8a613d247":"請選擇節點", + "i18n_f8b3165e0d":"當前專案被禁用", + "i18n_f8f20c1d1e":"修剪在此時間戳之前建立的物件 例如:24h", + "i18n_f8f456eb9a":"型別專案特有的 type:reload、restart", + "i18n_f932eff53e":"條資料", + "i18n_f9361945f3":"主機名 hostname", + "i18n_f967131d9d":"倉庫名稱", + "i18n_f976e8fcf4":"監控名稱", + "i18n_f97a4d2591":"請選擇要加入到哪個叢集", + "i18n_f9898595a0":"注意:同一個分組不建議被多個叢集繫結", + "i18n_f98994f7ec":"釋出方式", + "i18n_f99ead0a76":"映象名稱不正確 不能更新", + "i18n_f9ac4b2aa6":"操作人", + "i18n_f9c9f95929":"Java 專案(java -classpath)", + "i18n_f9cea44f02":"當前工作空間還沒有 Docker", + "i18n_f9f061773e":"不填寫則使用節點分發配置的二級目錄", + "i18n_fa2f7a8927":"失敗策略", + "i18n_fa4aa1b93b":"執行專案", + "i18n_fa57a7afad":"容器標籤,如:xxxx:latest 多個使用逗號隔開, 配置附加環境變數檔案支援載入倉庫目錄下 .env 檔案環境變數 如: xxxx:{'${VERSION}'}", + "i18n_fa624c8420":"禁用後該使用者不能登入平臺", + "i18n_fa7f6fccfd":"專案名稱:", + "i18n_fa7ffa2d21":"解鎖", + "i18n_fa8e673c50":"編輯工作空間", + "i18n_faa1ad5e5c":"協議", + "i18n_faaa995a8b":"可以關閉", + "i18n_faaadc447b":"序號", + "i18n_fabc07a4f1":"請選擇監控操作", + "i18n_fad1b9fb87":"新增指令碼模版需要到節點管理中去新增", + "i18n_fb1f3b5125":"當前工作空間關聯資料統計", + "i18n_fb3a2241bb":"狀態描述:", + "i18n_fb5bc565f3":"解析檔案失敗:", + "i18n_fb61d4d708":"真的要回滾該構建歷史記錄麼?", + "i18n_fb7b9876a6":"請輸入指令碼名稱", + "i18n_fb852fc6cc":"進行中", + "i18n_fb8fb9cc46":"統計說明", + "i18n_fb91527ce5":"節點可用性:", + "i18n_fb9d826b2f":"釋出後執行的命令(非阻塞命令),一般是啟動專案命令 如:ps -aux | grep java", + "i18n_fba5f4f19a":"DSL環境變數", + "i18n_fbd7ba1d9b":"最後分發時間", + "i18n_fbee13a873":"工作空間總數:", + "i18n_fbfa6c18bf":"已分配", + "i18n_fbfeb76b33":"左邊選單欄主題切換", + "i18n_fc06c70960":"您確定要刪除當前映象嗎?", + "i18n_fc4e2c6151":"登入使用者", + "i18n_fc5fb962da":"郵箱密碼或者授權碼", + "i18n_fc92e93523":"生效時間", + "i18n_fc954d25ec":"代理", + "i18n_fcaef5b17a":"重用另一個容器網路堆疊", + "i18n_fcb4c2610a":"通知異常", + "i18n_fcb7a47b70":"阿里雲企業郵箱", + "i18n_fcba60e773":"構建", + "i18n_fcbf0d0a55":"需要先安裝依賴 yarn && yarn run build", + "i18n_fcca8452fe":"叢集地址主要用於切換工作空間自動跳轉到對應的叢集", + "i18n_fcef976c7a":"私鑰內容", + "i18n_fd6e80f1e0":"正常", + "i18n_fd7b461411":"不清空", + "i18n_fd7e0c997d":"選擇檔案", + "i18n_fd93f7f3d7":"可以將指令碼分發到機器節點中在 DSL 專案中引用,達到多個專案共用相同指令碼", + "i18n_fda92d22d9":"關聯節點會自動識別伺服器中是否存在 java 環境,如果沒有 Java 環境不能快速安裝節點", + "i18n_fdba50ca2d":"如果埠暴露到公網很", + "i18n_fdbac93380":"SMTP 地址:smtp.mxhichina.com,埠使用 465 並且開啟 SSL,使用者名稱需要和郵件傳送人一致,密碼為郵箱的登入密碼", + "i18n_fdbc77bd19":"安全", + "i18n_fdcadf68a5":"SMTP 埠", + "i18n_fde1b6fb37":"需要提前為機器配置授權目錄", + "i18n_fdfd501269":"java sdk 映象使用:https://mirrors.tuna.tsinghua.edu.cn/ 支援版本有:8, 9, 10, 11, 12, 13, 14, 15, 16, 17", + "i18n_fe1b192913":"目錄建立成功後需要手動重新整理右邊樹才能顯示出來喲", + "i18n_fe231ff92f":"關閉頁面操作引導、導航", + "i18n_fe2df04a16":"版本", + "i18n_fe32def462":"活躍", + "i18n_fe7509e0ed":"值", + "i18n_fe828cefd9":"專案資料夾是專案實際存放的目錄名稱", + "i18n_fe87269484":"叢集修改時間", + "i18n_fea996d31e":"請填寫構建名稱", + "i18n_fec6151b49":"賬戶名稱", + "i18n_feda0df7ef":"賬號郵箱", + "i18n_ff17b9f9cd":"企業微信", + "i18n_ff1fda9e47":"禁止", + "i18n_ff39c45fbc":"使用容器內的主機網路堆疊。 注意:主機模式賦予容器對本地系統服務(如 D-bus)的完全訪問許可權,因此被認為是不安全的。", + "i18n_ff3bdecc5e":"檔案檢視(如果自定義配置了賬號密碼將沒有此檔案)", + "i18n_ff80d2671c":"秒後重新整理", + "i18n_ff9814bf6b":"觸發型別", + "i18n_ff9dffec4d":"搜尋模式", + "i18n_ffa9fd37b5":"工作空間管理", + "i18n_ffaf95f0ef":"啟動的容器 可以看到很多host上的裝置 可以執行mount。 可以在docker容器中啟動docker容器。", + "i18n_ffd67549cf":":範圍:1~12,同時支援不區分大小寫的別名:_##_jan\",\"feb\", \"mar\", \"apr\", \"may\",\"jun\", \"jul\", \"aug\",\"sep\",\"oct\", \"nov\", \"dec\"", + "i18n_fffd3ce745":"共享" +} \ No newline at end of file diff --git a/web-vue/src/interface/common.d.ts b/web-vue/src/interface/common.d.ts new file mode 100644 index 0000000000..1e68af455e --- /dev/null +++ b/web-vue/src/interface/common.d.ts @@ -0,0 +1,36 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +export interface IPageQuery { + page: number + limit: number + total: number + [key: string]: any +} +export interface SystemType { + disabledCaptcha: bollean + disabledGuide: bollean + inDocker: bollean + loginTitle: string + name: string + notificationPlacement: string + routerBase: string + subTitle: string +} + +export interface GlobalWindow { + routerBase: string + apiTimeout: string + uploadFileSliceSize: string + uploadFileConcurrent: string + oauth2Provide: string + transportEncryption: string + jpomDefaultLocale: string +} diff --git a/web-vue/src/interface/global.d.ts b/web-vue/src/interface/global.d.ts new file mode 100644 index 0000000000..fd0c8a0394 --- /dev/null +++ b/web-vue/src/interface/global.d.ts @@ -0,0 +1,13 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +declare module 'js-sha1' +declare module 'spark-md5' +declare module 'qs' diff --git a/web-vue/src/interface/request.d.ts b/web-vue/src/interface/request.d.ts new file mode 100644 index 0000000000..32d2346d2f --- /dev/null +++ b/web-vue/src/interface/request.d.ts @@ -0,0 +1,15 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +export interface IResponse { + data?: T + code: number + msg: string +} diff --git a/web-vue/src/main.js b/web-vue/src/main.js deleted file mode 100644 index 843d73d24d..0000000000 --- a/web-vue/src/main.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from "vue"; -import App from "./App.vue"; - -import Antd from "ant-design-vue"; -import "ant-design-vue/dist/antd.css"; -import "./assets/reset.css"; -import { Tree, Progress, Loading } from "element-ui"; -import "element-ui/lib/theme-chalk/index.css"; -const introJs = require("intro.js"); -import "intro.js/introjs.css"; -// import 'intro.js/themes/introjs-flattener.css'; -import "@/assets/intro-custom-themes.css"; - -import router from "./router"; -import store from "./store"; -import "./router/auth"; - -// debug routerBase -window.routerBase = window.routerBase === "" ? "" : window.routerBase; - -Vue.config.productionTip = false; -Vue.prototype.$loading = Loading; -Vue.prototype.$introJs = introJs; -Vue.use(Antd); -Vue.use(Tree); -Vue.use(Progress); - -new Vue({ - router, - store, - render: (h) => h(App), -}).$mount("#app"); diff --git a/web-vue/src/main.ts b/web-vue/src/main.ts new file mode 100644 index 0000000000..918c358df2 --- /dev/null +++ b/web-vue/src/main.ts @@ -0,0 +1,31 @@ +/// +/// Copyright (c) 2019 Of Him Code Technology Studio +/// Jpom is licensed under Mulan PSL v2. +/// You can use this software according to the terms and conditions of the Mulan PSL v2. +/// You may obtain a copy of Mulan PSL v2 at: +/// http://license.coscl.org.cn/MulanPSL2 +/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +/// See the Mulan PSL v2 for more details. +/// + +import '@/assets/style.less' +import '@/assets/reset.less' +import App from './App.vue' +import router from './router' +import '@/router/auth' +import i18n from './i18n' +import { changeLang, defaultLocale } from './i18n' + +changeLang(defaultLocale).then(() => { + //console.log('defaultLocale done', new Date().getTime()) + const pinia = createPinia() + + const app = createApp(App) + + app.use(router) + app.use(pinia) + app.use(i18n) + + app.mount('#app') +}) +//console.log('app done', new Date().getTime()) diff --git a/web-vue/src/pages/404/index.vue b/web-vue/src/pages/404/index.vue index 017336cff3..fe9c4bd56c 100644 --- a/web-vue/src/pages/404/index.vue +++ b/web-vue/src/pages/404/index.vue @@ -1,30 +1,37 @@ - \ No newline at end of file + diff --git a/web-vue/src/pages/build/details.vue b/web-vue/src/pages/build/details.vue new file mode 100644 index 0000000000..0e146d51a6 --- /dev/null +++ b/web-vue/src/pages/build/details.vue @@ -0,0 +1,337 @@ + + diff --git a/web-vue/src/pages/build/edit.vue b/web-vue/src/pages/build/edit.vue new file mode 100644 index 0000000000..521335bc8c --- /dev/null +++ b/web-vue/src/pages/build/edit.vue @@ -0,0 +1,1963 @@ + + diff --git a/web-vue/src/pages/build/history.vue b/web-vue/src/pages/build/history.vue index 61effc0cd4..c3d0026e9d 100644 --- a/web-vue/src/pages/build/history.vue +++ b/web-vue/src/pages/build/history.vue @@ -1,243 +1,564 @@